@jongh/cli 1.0.11 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -1
- package/package.json +16 -3
- package/dist/components/Accordion/Accordion.tsx +0 -274
- package/dist/components/Accordion/index.tsx +0 -37
- package/dist/components/Accordion/useAccordionHeight.ts +0 -28
- package/dist/components/Avatar/Avatar.tsx +0 -18
- package/dist/components/Avatar/useAvatar.ts +0 -73
- package/dist/components/Button/index.tsx +0 -66
- package/dist/components/Slider/Slider.tsx +0 -140
- package/dist/components/Slider/style.ts +0 -42
- package/dist/components/Tab/Tab.tsx +0 -35
- package/dist/components/Tab/TabContent.tsx +0 -60
- package/dist/components/Tab/TabIndicator.tsx +0 -19
- package/dist/components/Tab/TabList.tsx +0 -118
- package/dist/components/Tab/index.ts +0 -4
- package/dist/components/Tab/style.ts +0 -9
- package/dist/components/Tab/useRovingTabIndex.tsx +0 -172
- package/dist/components/Tab/useTabContext.ts +0 -11
- package/dist/components/TagButton/TagButton.tsx +0 -75
- package/dist/components/TagButton/style.ts +0 -35
- package/dist/components/core/Polymorphic/index.ts +0 -26
- package/dist/components/index.ts +0 -5
- package/park-ui.json +0 -3
- package/src/fetchComponent.ts +0 -30
- package/src/getConfig.ts +0 -53
- package/src/index.ts +0 -46
- package/tsconfig.json +0 -14
- package/tsup.config.ts +0 -14
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import x from"yargs";import{hideBin as A}from"yargs/helpers";import*as r from"@clack/prompts";import{packageDirectory as j}from"pkg-dir";import u from"fs-extra";import h from"path";import d from"fast-glob";import v from"fs-extra";import{loadConfig as g}from"tsconfig-paths";function a(t){let i=g(t);if(i?.resultType==="failed"||!Object.entries(i?.paths).length)return{baseAlias:null,styledSystemAlias:null};let s=null,n=null;for(let[o,e]of Object.entries(i.paths))e.includes("./styled-system/*")&&(n=o.replace(/\/\*$/,"")),(e.includes("./*")||e.includes("./src/*")||e.includes("./app/*"))&&(s=o.replace(/\/\*$/,""));return s||(s=Object.keys(i?.paths)?.[0].replace(/\/\*$/,"")??null),n||(n="."),{baseAlias:s,styledSystemAlias:n}}async function c(t){let i=await d.glob(["panda.config.*"],{cwd:t});return i.length?i[0]:null}var p="@pandacss/dev";async function l(t){let i=JSON.parse(u.readFileSync(h.join(t,"package.json"),"utf-8")),s=Object.keys(i.devDependencies).includes(p)||Object.keys(i.dependencies).includes(p),n=await c(t);return s&&!!n}import y from"path";import w from"fs-extra";async function f(t){return await w.pathExists(y.join(t,"components.json"))}import k from"fs-extra";import b from"path";async function m(){let t=await j();if(!t)throw new Error("Failed to find package root");if(await f(t))throw new Error("Already initialized");if(await l(t))throw new Error("install pandacss");let{baseAlias:n,styledSystemAlias:o}=a(t);if(!n||!o)throw new Error("Failed to find tsconfig alias");let e={utils:`${n}/utils`,components:`${n}/components`,hooks:`${n}/hooks`,styledsystem:o};await k.writeFile(b.resolve(t,"components.json"),JSON.stringify(e),"utf-8")}var I=async()=>x(A(process.argv)).command("init","initialize the project",()=>{},async()=>{r.note("Initializing project ...");try{await m()}catch(t){t instanceof Error&&r.cancel("Failed to initialize project: "+t.message)}});I();
|
package/package.json
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jongh/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "",
|
|
5
|
-
"main": "index.js",
|
|
6
5
|
"bin": "./dist/index.js",
|
|
7
6
|
"type": "module",
|
|
8
7
|
"keywords": [],
|
|
@@ -11,16 +10,27 @@
|
|
|
11
10
|
"publishConfig": {
|
|
12
11
|
"access": "public"
|
|
13
12
|
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"package.json"
|
|
16
|
+
],
|
|
14
17
|
"dependencies": {
|
|
18
|
+
"@babel/generator": "^7.26.3",
|
|
19
|
+
"@babel/parser": "^7.26.3",
|
|
20
|
+
"@babel/traverse": "^7.26.4",
|
|
15
21
|
"@clack/prompts": "^0.7.0",
|
|
16
22
|
"@effect/platform": "^0.69.13",
|
|
17
23
|
"@effect/schema": "^0.75.5",
|
|
24
|
+
"@types/babel__traverse": "^7.20.6",
|
|
18
25
|
"effect": "^3.10.8",
|
|
26
|
+
"fast-glob": "^3.3.2",
|
|
19
27
|
"fs-extra": "^11.2.0",
|
|
20
28
|
"pkg-dir": "^8.0.0",
|
|
29
|
+
"tsconfig-paths": "^4.2.0",
|
|
21
30
|
"yargs": "^17.7.2"
|
|
22
31
|
},
|
|
23
32
|
"devDependencies": {
|
|
33
|
+
"@types/babel__generator": "^7.6.8",
|
|
24
34
|
"@types/fs-extra": "^11.0.4",
|
|
25
35
|
"@types/node": "^22.8.6",
|
|
26
36
|
"@types/yargs": "^17.0.33",
|
|
@@ -30,6 +40,9 @@
|
|
|
30
40
|
},
|
|
31
41
|
"scripts": {
|
|
32
42
|
"build": "tsup",
|
|
33
|
-
"release": "pnpm publish -no-git-checks"
|
|
43
|
+
"release": "pnpm publish -no-git-checks",
|
|
44
|
+
"lint": "eslint . --max-warnings 0",
|
|
45
|
+
"check-type": "tsc --noEmit",
|
|
46
|
+
"test": "vitest"
|
|
34
47
|
}
|
|
35
48
|
}
|
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { createContext } from "../../hooks/createContext"
|
|
3
|
-
import { useControlledState } from "../../hooks/useControllableState"
|
|
4
|
-
import { useAccordionHeight } from "./useAccordionHeight"
|
|
5
|
-
import { useKeyboardEvent } from "../../hooks/useKeyboardEvent"
|
|
6
|
-
import { Slot } from "@radix-ui/react-slot"
|
|
7
|
-
import { composeRefs } from "src/hooks/useComposedRefs"
|
|
8
|
-
|
|
9
|
-
const ACCORDION_KEYS = [
|
|
10
|
-
"Home",
|
|
11
|
-
"End",
|
|
12
|
-
"ArrowDown",
|
|
13
|
-
"ArrowUp",
|
|
14
|
-
"ArrowLeft",
|
|
15
|
-
"ArrowRight",
|
|
16
|
-
]
|
|
17
|
-
export type TAccordionContext = {
|
|
18
|
-
selected?: string[]
|
|
19
|
-
onItemOpen: (value: string) => void
|
|
20
|
-
onItemClose: (value: string) => void
|
|
21
|
-
refs?: HTMLElement[]
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const [AccordionProvider, useAccordionContext] =
|
|
25
|
-
createContext<TAccordionContext>("accordion")
|
|
26
|
-
|
|
27
|
-
export interface AccordionProps {
|
|
28
|
-
value?: string[]
|
|
29
|
-
defaultValue?: string[]
|
|
30
|
-
onValueChange?: (value: string[]) => void
|
|
31
|
-
asChild?: boolean
|
|
32
|
-
type?: "single" | "multi"
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export const Accordion = React.forwardRef<HTMLDivElement, AccordionProps>(
|
|
36
|
-
(props, ref) => {
|
|
37
|
-
const { type = "single", ...accordionProps } = props
|
|
38
|
-
if (type === "single") {
|
|
39
|
-
return <SingleAccordion {...accordionProps} ref={ref} />
|
|
40
|
-
}
|
|
41
|
-
if (type === "multi") {
|
|
42
|
-
return <MultiAccordion {...accordionProps} />
|
|
43
|
-
}
|
|
44
|
-
},
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
Accordion.displayName = "Accordion"
|
|
48
|
-
|
|
49
|
-
interface AccordionImplSingleProps {
|
|
50
|
-
value?: string[] // 현재 선택되어있는 item들을 담아놓는 용도
|
|
51
|
-
defaultValue?: string[]
|
|
52
|
-
onValueChange?: (value: string[]) => void
|
|
53
|
-
asChild?: boolean
|
|
54
|
-
children?: React.ReactNode
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const SingleAccordion = React.forwardRef<
|
|
58
|
-
HTMLDivElement,
|
|
59
|
-
AccordionImplSingleProps
|
|
60
|
-
>(
|
|
61
|
-
(
|
|
62
|
-
{ value, defaultValue = [], onValueChange, children, asChild, ...props },
|
|
63
|
-
forwardedRef,
|
|
64
|
-
) => {
|
|
65
|
-
const [selected = [], setSelected] = useControlledState({
|
|
66
|
-
prop: value,
|
|
67
|
-
defaultProp: defaultValue,
|
|
68
|
-
onChange: onValueChange,
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
const { refs: accordionRefs, handleKeyDown } = useKeyboardEvent({
|
|
72
|
-
keyList: ACCORDION_KEYS,
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
const Comp = asChild ? Slot : "div"
|
|
76
|
-
|
|
77
|
-
return (
|
|
78
|
-
<AccordionProvider
|
|
79
|
-
selected={selected}
|
|
80
|
-
onItemOpen={(value) => setSelected((_) => [value])}
|
|
81
|
-
onItemClose={() => setSelected((_) => [])}
|
|
82
|
-
refs={accordionRefs.current || []}
|
|
83
|
-
>
|
|
84
|
-
<Comp
|
|
85
|
-
ref={composeRefs((node) => {
|
|
86
|
-
accordionRefs.current = Array.from(
|
|
87
|
-
node?.children || [],
|
|
88
|
-
) as HTMLElement[]
|
|
89
|
-
}, forwardedRef)}
|
|
90
|
-
onKeyDown={(e) => {
|
|
91
|
-
handleKeyDown(e)
|
|
92
|
-
}}
|
|
93
|
-
{...props}
|
|
94
|
-
>
|
|
95
|
-
{children}
|
|
96
|
-
</Comp>
|
|
97
|
-
</AccordionProvider>
|
|
98
|
-
)
|
|
99
|
-
},
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
interface AccordionImplMultiProps {
|
|
103
|
-
value?: string[] // 현재 선택되어있는 item들을 담아놓는 용도
|
|
104
|
-
defaultValue?: string[]
|
|
105
|
-
onValueChange?: (value: string[]) => void
|
|
106
|
-
asChild?: boolean
|
|
107
|
-
children?: React.ReactNode
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const MultiAccordion = React.forwardRef<
|
|
111
|
-
HTMLDivElement,
|
|
112
|
-
AccordionImplMultiProps
|
|
113
|
-
>(
|
|
114
|
-
(
|
|
115
|
-
{ value, defaultValue, onValueChange, children, asChild, ...props },
|
|
116
|
-
forwardedRef,
|
|
117
|
-
) => {
|
|
118
|
-
const [selected, setSelected] = useControlledState({
|
|
119
|
-
prop: value,
|
|
120
|
-
defaultProp: defaultValue,
|
|
121
|
-
onChange: onValueChange,
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
const handleItemOpen = React.useCallback(
|
|
125
|
-
(item: string) => {
|
|
126
|
-
setSelected((prev = []) => [...prev, item])
|
|
127
|
-
},
|
|
128
|
-
[setSelected],
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
const handleItemClose = React.useCallback(
|
|
132
|
-
(item: string) =>
|
|
133
|
-
setSelected((prev = []) => prev.filter((value) => value !== item)),
|
|
134
|
-
[setSelected],
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
const { refs: accordionRefs, handleKeyDown } = useKeyboardEvent({
|
|
138
|
-
keyList: ACCORDION_KEYS,
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
const Comp = asChild ? Slot : "div"
|
|
142
|
-
|
|
143
|
-
return (
|
|
144
|
-
<AccordionProvider
|
|
145
|
-
selected={selected}
|
|
146
|
-
onItemOpen={handleItemOpen}
|
|
147
|
-
onItemClose={handleItemClose}
|
|
148
|
-
>
|
|
149
|
-
<Comp
|
|
150
|
-
ref={(node) => {
|
|
151
|
-
accordionRefs.current = Array.from(
|
|
152
|
-
node?.children || [],
|
|
153
|
-
) as HTMLElement[]
|
|
154
|
-
if (typeof forwardedRef === "function") {
|
|
155
|
-
forwardedRef(node)
|
|
156
|
-
} else if (forwardedRef) {
|
|
157
|
-
;(
|
|
158
|
-
forwardedRef as React.MutableRefObject<HTMLDivElement | null>
|
|
159
|
-
).current = node
|
|
160
|
-
}
|
|
161
|
-
}}
|
|
162
|
-
onKeyDown={handleKeyDown}
|
|
163
|
-
{...props}
|
|
164
|
-
>
|
|
165
|
-
{children}
|
|
166
|
-
</Comp>
|
|
167
|
-
</AccordionProvider>
|
|
168
|
-
)
|
|
169
|
-
},
|
|
170
|
-
)
|
|
171
|
-
//AccordionItem
|
|
172
|
-
interface TAccordionItemContext {
|
|
173
|
-
isOpen: boolean
|
|
174
|
-
onToggle: () => void
|
|
175
|
-
value?: string
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const [AccordionItemProvider, useAccordionItemProvider] =
|
|
179
|
-
createContext<TAccordionItemContext>("accordionItem")
|
|
180
|
-
export interface AccordionItemProps {
|
|
181
|
-
value: string //unique value
|
|
182
|
-
disabled?: boolean
|
|
183
|
-
children: React.ReactNode
|
|
184
|
-
asChild?: boolean
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
export const AccordionItem = React.forwardRef<
|
|
188
|
-
HTMLDivElement,
|
|
189
|
-
AccordionItemProps
|
|
190
|
-
>(({ value, disabled = false, children, asChild, ...props }, forwardedRef) => {
|
|
191
|
-
const { selected, onItemOpen, onItemClose } = useAccordionContext("accordion")
|
|
192
|
-
|
|
193
|
-
const isOpen = selected?.includes(value) && !disabled
|
|
194
|
-
|
|
195
|
-
const onToggle = () => {
|
|
196
|
-
if (disabled) {
|
|
197
|
-
return
|
|
198
|
-
}
|
|
199
|
-
isOpen ? onItemClose(value) : onItemOpen(value)
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const Comp = asChild ? Slot : "div"
|
|
203
|
-
|
|
204
|
-
return (
|
|
205
|
-
<AccordionItemProvider isOpen={!!isOpen} onToggle={onToggle} value={value}>
|
|
206
|
-
<Comp
|
|
207
|
-
ref={forwardedRef}
|
|
208
|
-
data-state={isOpen ? "open" : "close"}
|
|
209
|
-
tabIndex={0}
|
|
210
|
-
onKeyDown={(e) => {
|
|
211
|
-
if (e.key === "Enter") {
|
|
212
|
-
onToggle()
|
|
213
|
-
}
|
|
214
|
-
}}
|
|
215
|
-
{...props}
|
|
216
|
-
>
|
|
217
|
-
{children}
|
|
218
|
-
</Comp>
|
|
219
|
-
</AccordionItemProvider>
|
|
220
|
-
)
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
export interface AccordionTriggerProps extends React.PropsWithChildren {}
|
|
224
|
-
export const AccordionTrigger = ({
|
|
225
|
-
children,
|
|
226
|
-
...props
|
|
227
|
-
}: AccordionTriggerProps) => {
|
|
228
|
-
const { onToggle, isOpen, value } = useAccordionItemProvider("accordionItem")
|
|
229
|
-
return (
|
|
230
|
-
<h3 data-state={isOpen ? "open" : "close"} {...props}>
|
|
231
|
-
<span
|
|
232
|
-
onClick={onToggle}
|
|
233
|
-
aria-expanded={isOpen ? "true" : "false"}
|
|
234
|
-
aria-controls={`content-${value}`}
|
|
235
|
-
id={`trigger-${value}`}
|
|
236
|
-
>
|
|
237
|
-
<span>{children}</span>
|
|
238
|
-
</span>
|
|
239
|
-
</h3>
|
|
240
|
-
)
|
|
241
|
-
}
|
|
242
|
-
//AccordionContent
|
|
243
|
-
export interface AccordionContentProps extends React.PropsWithChildren {
|
|
244
|
-
duration?: number
|
|
245
|
-
asChild?: boolean
|
|
246
|
-
}
|
|
247
|
-
export const AccordionContent = ({
|
|
248
|
-
children,
|
|
249
|
-
duration = 150,
|
|
250
|
-
asChild,
|
|
251
|
-
...props
|
|
252
|
-
}: AccordionContentProps) => {
|
|
253
|
-
const Comp = asChild ? Slot : "div"
|
|
254
|
-
const { isOpen, value } = useAccordionItemProvider("accordionItem")
|
|
255
|
-
|
|
256
|
-
const contentRef = useAccordionHeight<HTMLDivElement>(isOpen, duration) //duration 초 뒤에 accordion을 열거나 닫아줌
|
|
257
|
-
|
|
258
|
-
if (!isOpen) {
|
|
259
|
-
return null
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
return (
|
|
263
|
-
<Comp
|
|
264
|
-
data-state={isOpen ? "open" : "close"}
|
|
265
|
-
id={`content-${value}`}
|
|
266
|
-
aria-labelledby={`trigger-${value}`}
|
|
267
|
-
role="region"
|
|
268
|
-
ref={contentRef}
|
|
269
|
-
{...props}
|
|
270
|
-
>
|
|
271
|
-
{children}
|
|
272
|
-
</Comp>
|
|
273
|
-
)
|
|
274
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { accordion, type AccordionVariantProps } from "@styled-system/recipes"
|
|
2
|
-
import { createStyleContext } from "../../utils/createStyleContext"
|
|
3
|
-
import type { ComponentProps, HTMLStyledProps } from "@styled-system/types"
|
|
4
|
-
import type { Assign } from "../../types"
|
|
5
|
-
import {
|
|
6
|
-
Accordion as AccordionProvider,
|
|
7
|
-
AccordionContent,
|
|
8
|
-
AccordionItem,
|
|
9
|
-
AccordionTrigger,
|
|
10
|
-
type AccordionContentProps,
|
|
11
|
-
type AccordionItemProps,
|
|
12
|
-
type AccordionProps,
|
|
13
|
-
type AccordionTriggerProps,
|
|
14
|
-
} from "./Accordion"
|
|
15
|
-
|
|
16
|
-
const { withProvider, withContext } = createStyleContext(accordion)
|
|
17
|
-
|
|
18
|
-
export type RootProviderProps = ComponentProps<typeof AccordionProvider>
|
|
19
|
-
export const Accordion = withProvider<
|
|
20
|
-
HTMLDivElement,
|
|
21
|
-
Assign<Assign<HTMLStyledProps<"div">, AccordionProps>, AccordionVariantProps>
|
|
22
|
-
>(AccordionProvider, "root")
|
|
23
|
-
|
|
24
|
-
export const Item = withContext<
|
|
25
|
-
HTMLDivElement,
|
|
26
|
-
Assign<HTMLStyledProps<"div">, AccordionItemProps>
|
|
27
|
-
>(AccordionItem, "item")
|
|
28
|
-
|
|
29
|
-
export const Trigger = withContext<
|
|
30
|
-
HTMLButtonElement,
|
|
31
|
-
Assign<HTMLStyledProps<"button">, AccordionTriggerProps>
|
|
32
|
-
>(AccordionTrigger, "trigger")
|
|
33
|
-
|
|
34
|
-
export const Content = withContext<
|
|
35
|
-
HTMLDivElement,
|
|
36
|
-
Assign<HTMLStyledProps<"div">, AccordionContentProps>
|
|
37
|
-
>(AccordionContent, "content")
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from "react"
|
|
2
|
-
const ACCORDION_HEIGHT = "--accordion-height" //CSS 변수명- accordion의 높이를 제어하여 animation과 sync 맞춤
|
|
3
|
-
export const useAccordionHeight = <T extends HTMLElement>(
|
|
4
|
-
isOpen: boolean,
|
|
5
|
-
duration = 150,
|
|
6
|
-
) => {
|
|
7
|
-
const ref = useRef<T>(null)
|
|
8
|
-
|
|
9
|
-
useEffect(() => {
|
|
10
|
-
const element = ref.current
|
|
11
|
-
if (element === null) {
|
|
12
|
-
return
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
if (isOpen) {
|
|
16
|
-
const height = element.style.getPropertyValue(ACCORDION_HEIGHT)
|
|
17
|
-
if (height === "0" || !height) {
|
|
18
|
-
element.style.setProperty(ACCORDION_HEIGHT, `${element.scrollHeight}px`)
|
|
19
|
-
}
|
|
20
|
-
} else {
|
|
21
|
-
setTimeout(() => {
|
|
22
|
-
element.style.setProperty(ACCORDION_HEIGHT, `${element.scrollHeight}px`)
|
|
23
|
-
}, duration)
|
|
24
|
-
}
|
|
25
|
-
}, [isOpen, duration])
|
|
26
|
-
|
|
27
|
-
return ref
|
|
28
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { Slot } from "@radix-ui/react-slot"
|
|
2
|
-
import { useAvatar, type UseAvatarStatusProps } from "./useAvatar"
|
|
3
|
-
import { forwardRef } from "react"
|
|
4
|
-
|
|
5
|
-
interface AvatarProps extends UseAvatarStatusProps {
|
|
6
|
-
asChild?: boolean
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export const Avatar = forwardRef<HTMLSpanElement, AvatarProps>((props, ref) => {
|
|
10
|
-
const { asChild, src, onStatusChange } = props
|
|
11
|
-
const { getRootProps, getImgProps } = useAvatar({ src, onStatusChange })
|
|
12
|
-
const Comp = asChild ? Slot : "span"
|
|
13
|
-
return (
|
|
14
|
-
<Comp ref={ref} {...getRootProps()}>
|
|
15
|
-
<img {...getImgProps()}></img>
|
|
16
|
-
</Comp>
|
|
17
|
-
)
|
|
18
|
-
})
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
useLayoutEffect,
|
|
3
|
-
useState,
|
|
4
|
-
type ComponentProps,
|
|
5
|
-
type HTMLAttributes,
|
|
6
|
-
} from "react"
|
|
7
|
-
|
|
8
|
-
type Status = "loading" | "loaded" | "error"
|
|
9
|
-
|
|
10
|
-
export interface UseAvatarStatusProps {
|
|
11
|
-
src?: string
|
|
12
|
-
onStatusChange?: (status: Status) => void
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const useAvatarStatus = ({
|
|
16
|
-
src,
|
|
17
|
-
onStatusChange,
|
|
18
|
-
}: UseAvatarStatusProps) => {
|
|
19
|
-
const [status, setStatus] = useState<Status>("loading")
|
|
20
|
-
|
|
21
|
-
useLayoutEffect(() => {
|
|
22
|
-
if (!src) {
|
|
23
|
-
setStatus("error")
|
|
24
|
-
return
|
|
25
|
-
}
|
|
26
|
-
let isMounted: boolean = true
|
|
27
|
-
const image = new window.Image()
|
|
28
|
-
|
|
29
|
-
const updateStatus = (status: Status) => () => {
|
|
30
|
-
if (!isMounted) return
|
|
31
|
-
setStatus(status)
|
|
32
|
-
onStatusChange?.(status)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
setStatus("loading")
|
|
36
|
-
|
|
37
|
-
image.src = src
|
|
38
|
-
image.onload = updateStatus("loaded")
|
|
39
|
-
image.onerror = updateStatus("error")
|
|
40
|
-
|
|
41
|
-
return () => {
|
|
42
|
-
isMounted = false
|
|
43
|
-
}
|
|
44
|
-
}, [src])
|
|
45
|
-
|
|
46
|
-
return status
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
type DataAttr = Record<`data-${string}`, string | undefined>
|
|
50
|
-
|
|
51
|
-
const elementProps = (props: HTMLAttributes<HTMLElement> & DataAttr) => props
|
|
52
|
-
|
|
53
|
-
const imgProps = (props: ComponentProps<"img"> & DataAttr) => props
|
|
54
|
-
|
|
55
|
-
interface UseAvatarProps extends UseAvatarStatusProps {}
|
|
56
|
-
export const useAvatar = ({ src, onStatusChange }: UseAvatarProps) => {
|
|
57
|
-
const status = useAvatarStatus({ src, onStatusChange })
|
|
58
|
-
const isLoaded = status === "loaded"
|
|
59
|
-
return {
|
|
60
|
-
getRootProps() {
|
|
61
|
-
return elementProps({
|
|
62
|
-
"data-loading-state": status,
|
|
63
|
-
})
|
|
64
|
-
},
|
|
65
|
-
getImgProps() {
|
|
66
|
-
return imgProps({
|
|
67
|
-
hidden: !isLoaded,
|
|
68
|
-
"data-visible": `${isLoaded}`,
|
|
69
|
-
src,
|
|
70
|
-
})
|
|
71
|
-
},
|
|
72
|
-
}
|
|
73
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
forwardRef,
|
|
3
|
-
ComponentPropsWithoutRef,
|
|
4
|
-
isValidElement,
|
|
5
|
-
cloneElement,
|
|
6
|
-
type ReactElement,
|
|
7
|
-
type MouseEventHandler,
|
|
8
|
-
} from "react"
|
|
9
|
-
import { button, type ButtonVariant } from "@styled-system/recipes"
|
|
10
|
-
import { Slot } from "@radix-ui/react-slot"
|
|
11
|
-
|
|
12
|
-
type ButtonProps = Partial<ButtonVariant> &
|
|
13
|
-
ComponentPropsWithoutRef<"button"> & { as?: string } & {
|
|
14
|
-
/**왼쪽에 올 아이콘 */
|
|
15
|
-
leftIcon?: ReactElement
|
|
16
|
-
/**오른쪽에 위치할 아이콘 */
|
|
17
|
-
rightIcon?: ReactElement
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
21
|
-
(
|
|
22
|
-
{
|
|
23
|
-
as,
|
|
24
|
-
id,
|
|
25
|
-
className,
|
|
26
|
-
children,
|
|
27
|
-
disabled = false,
|
|
28
|
-
leftIcon,
|
|
29
|
-
rightIcon,
|
|
30
|
-
onClick,
|
|
31
|
-
...rest
|
|
32
|
-
},
|
|
33
|
-
ref,
|
|
34
|
-
) => {
|
|
35
|
-
const Comp = as ? Slot : "button"
|
|
36
|
-
|
|
37
|
-
const wrapIcon = (icon: ReactElement<{ onClick?: MouseEventHandler }>) => {
|
|
38
|
-
if (isValidElement(icon)) {
|
|
39
|
-
const { onClick: onClickIcon } = icon.props
|
|
40
|
-
return cloneElement(icon, {
|
|
41
|
-
onClick: (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
42
|
-
e.stopPropagation()
|
|
43
|
-
onClickIcon ? onClickIcon(e) : onClick?.(e)
|
|
44
|
-
},
|
|
45
|
-
})
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return (
|
|
50
|
-
<Comp
|
|
51
|
-
role="button"
|
|
52
|
-
ref={ref}
|
|
53
|
-
disabled={disabled}
|
|
54
|
-
id={id}
|
|
55
|
-
data-testid={id}
|
|
56
|
-
onClick={onClick}
|
|
57
|
-
className={button({})}
|
|
58
|
-
{...rest}
|
|
59
|
-
>
|
|
60
|
-
{leftIcon && <span>{wrapIcon(leftIcon)}</span>}
|
|
61
|
-
{children}
|
|
62
|
-
{rightIcon && <span>{wrapIcon(rightIcon)}</span>}
|
|
63
|
-
</Comp>
|
|
64
|
-
)
|
|
65
|
-
},
|
|
66
|
-
)
|