@jongh/cli 1.0.0

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.
@@ -0,0 +1,35 @@
1
+ import { forwardRef, useId, type ReactNode } from "react"
2
+ import { useControlledState } from "../../hooks/useControllableState"
3
+ import { Slot } from "@radix-ui/react-slot"
4
+ import { TabProvider } from "./useTabContext"
5
+
6
+ export interface TabProps {
7
+ children: ReactNode
8
+ selected?: string
9
+ defaultValue?: string
10
+ onSelect?: (value: string) => void
11
+ asChild?: boolean
12
+ }
13
+
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ export const Tab = forwardRef<any, TabProps>(
16
+ ({ children, selected, defaultValue, onSelect, asChild }, ref) => {
17
+ const Element = asChild ? Slot : "div"
18
+
19
+ const [value, setValue] = useControlledState({
20
+ prop: selected,
21
+ onChange: onSelect,
22
+ defaultProp: defaultValue,
23
+ })
24
+
25
+ const onSelectItem = (value: string) => {
26
+ setValue(value)
27
+ }
28
+
29
+ return (
30
+ <TabProvider selected={value} onSelect={onSelectItem} tabId={useId()}>
31
+ <Element ref={ref}>{children}</Element>
32
+ </TabProvider>
33
+ )
34
+ },
35
+ )
@@ -0,0 +1,60 @@
1
+ import { type ReactNode } from "react"
2
+ import { useTabContext } from "./useTabContext"
3
+ import { motion } from "framer-motion"
4
+
5
+ interface TabContentProps {
6
+ children: ReactNode
7
+ value: string
8
+ }
9
+
10
+ const tabContentVariant = {
11
+ active: {
12
+ display: "block",
13
+ transition: {
14
+ staggerChildren: 0.2,
15
+ },
16
+ },
17
+ inactive: {
18
+ display: "none",
19
+ },
20
+ } as const
21
+
22
+ const cardVariant = {
23
+ active: {
24
+ opacity: 1,
25
+ x: 0,
26
+ transition: {
27
+ duration: 0.3,
28
+ },
29
+ },
30
+ inactive: {
31
+ opacity: 0,
32
+ x: 10,
33
+ transition: {
34
+ duration: 0.3,
35
+ },
36
+ },
37
+ }
38
+ export const TabContent = ({ children, value }: TabContentProps) => {
39
+ const { selected, tabId } = useTabContext("tab")
40
+
41
+ const isSelected = selected === value
42
+
43
+ return (
44
+ <motion.div
45
+ role="tabpanel"
46
+ tabIndex={0}
47
+ id={tabId + "-tabpanel-" + value}
48
+ data-state={isSelected ? "active" : "inactive"}
49
+ aria-labelledby={tabId + "-tabitem-" + value}
50
+ key={tabId + "-tabpanel-" + value}
51
+ variants={tabContentVariant}
52
+ animate={isSelected ? "active" : "inactive"}
53
+ initial="inactive"
54
+ >
55
+ <motion.div key={value} variants={cardVariant}>
56
+ {isSelected && children}
57
+ </motion.div>
58
+ </motion.div>
59
+ )
60
+ }
@@ -0,0 +1,19 @@
1
+ import { css } from "@styled-system/css"
2
+ import { motion } from "framer-motion"
3
+
4
+ export const TabIndicator = () => {
5
+ return (
6
+ <motion.div
7
+ className={css({
8
+ width: "var(--indicator-width)",
9
+ height: "2px",
10
+ transform: "translateZ(0px)",
11
+ position: "absolute",
12
+ bottom: 0,
13
+ left: `var(--indicator-left)`,
14
+ background: "red_300",
15
+ borderRadius: "rounded",
16
+ })}
17
+ ></motion.div>
18
+ )
19
+ }
@@ -0,0 +1,118 @@
1
+ import {
2
+ type ReactNode,
3
+ type ReactElement,
4
+ type ComponentPropsWithRef,
5
+ forwardRef,
6
+ } from "react"
7
+ import { useTabContext } from "./useTabContext"
8
+
9
+ import { css, cx } from "@styled-system/css"
10
+ import { RovingTabIndexRoot, useRovingTabIndex } from "./useRovingTabIndex"
11
+ import isHotkey from "is-hotkey"
12
+ import { Slot } from "@radix-ui/react-slot"
13
+ import { composeRefs } from "../../hooks/useComposedRefs"
14
+ import {
15
+ getNextFocusableId,
16
+ getPrevFocusableId,
17
+ } from "../../utils/getFocusableId"
18
+
19
+ interface TabListProps {
20
+ children: ReactNode
21
+ className?: string
22
+ }
23
+
24
+ const setIndicatorStyle = (target: HTMLElement) => {
25
+ const targetRect = target.getBoundingClientRect()
26
+ const parentRect = target.parentElement?.getBoundingClientRect()
27
+ if (!targetRect || !parentRect) {
28
+ return
29
+ }
30
+ document.documentElement.style.setProperty(
31
+ "--indicator-left",
32
+ `${Math.abs(parentRect.left - targetRect.left)}px`,
33
+ )
34
+ document.documentElement.style.setProperty(
35
+ "--indicator-width",
36
+ `${Math.abs(targetRect.width)}px`,
37
+ )
38
+ }
39
+
40
+ export const TabList = ({ children, className }: TabListProps) => {
41
+ const { selected } = useTabContext("tab")
42
+ return (
43
+ <RovingTabIndexRoot as="div" active={selected}>
44
+ <div
45
+ role="tablist"
46
+ className={cx(
47
+ className,
48
+ css({ position: "relative", display: "flex" }),
49
+ )}
50
+ >
51
+ {children}
52
+ </div>
53
+ </RovingTabIndexRoot>
54
+ )
55
+ }
56
+ type TabItemProps = ComponentPropsWithRef<"button"> & {
57
+ value: string
58
+ }
59
+
60
+ export const RovingItem = ({
61
+ value,
62
+ children,
63
+ }: {
64
+ value: string
65
+ children: ReactElement
66
+ }) => {
67
+ const { getOrderedItems, getRovingProps } = useRovingTabIndex(value)
68
+ return (
69
+ <Slot
70
+ {...getRovingProps<"button">({
71
+ onKeyDown: (e) => {
72
+ const items = getOrderedItems()
73
+ let nextItem
74
+ if (isHotkey("right", e)) {
75
+ nextItem = getNextFocusableId(items, value)
76
+ } else if (isHotkey("left", e)) {
77
+ nextItem = getPrevFocusableId(items, value)
78
+ }
79
+ nextItem?.element.focus()
80
+ },
81
+ })}
82
+ >
83
+ {children}
84
+ </Slot>
85
+ )
86
+ }
87
+
88
+ export const TabItem = forwardRef<HTMLButtonElement, TabItemProps>(
89
+ ({ children, className, value, ...props }: TabItemProps, forwardRef) => {
90
+ const { selected, onSelect, tabId } = useTabContext("tab")
91
+ const isSelected = selected === value
92
+
93
+ const handleSelect = () => {
94
+ onSelect?.(value)
95
+ }
96
+
97
+ return (
98
+ <RovingItem value={value}>
99
+ <button
100
+ ref={composeRefs((node) => {
101
+ if (isSelected && node) {
102
+ setIndicatorStyle(node as HTMLElement)
103
+ }
104
+ }, forwardRef)}
105
+ onClick={handleSelect}
106
+ onFocus={handleSelect}
107
+ role="tab"
108
+ aria-selected={isSelected}
109
+ id={tabId + "-tabitem-" + value}
110
+ aria-controls={tabId + "-tabpanel-" + value}
111
+ {...props}
112
+ >
113
+ {children}
114
+ </button>
115
+ </RovingItem>
116
+ )
117
+ },
118
+ )
@@ -0,0 +1,4 @@
1
+ export * from "./Tab"
2
+ export * from "./TabList"
3
+ export * from "./TabContent"
4
+ export * from "./TabIndicator"
@@ -0,0 +1,9 @@
1
+ import { define } from "../../../dev"
2
+
3
+ export const tabRecipe = define.recipe({
4
+ className: "tab",
5
+ base: {
6
+ display: "flex",
7
+ flexDirection: "row",
8
+ },
9
+ })
@@ -0,0 +1,172 @@
1
+ import isHotkey from "is-hotkey"
2
+ import {
3
+ ReactNode,
4
+ useCallback,
5
+ useRef,
6
+ useState,
7
+ FocusEvent,
8
+ MouseEvent,
9
+ KeyboardEvent,
10
+ ComponentPropsWithoutRef,
11
+ ElementType,
12
+ MutableRefObject,
13
+ } from "react"
14
+ import { useControlledState } from "../../hooks/useControllableState"
15
+ import { createContext } from "../../hooks/createContext"
16
+ import { composeRefs } from "../../hooks/useComposedRefs"
17
+
18
+ export type RovingTabIndexItem = {
19
+ value: string
20
+ element: HTMLElement
21
+ }
22
+
23
+ function onFocusFirst(candidates: HTMLElement[]) {
24
+ const previousFocus = document.activeElement
25
+ while (document.activeElement === previousFocus && candidates.length > 0) {
26
+ candidates.shift()?.focus()
27
+ }
28
+ }
29
+
30
+ type RovingTabIndexContext = {
31
+ currentFocusedValue: string | null
32
+ setFocusableId: (id: string) => void
33
+ onShiftTab: () => void
34
+ getOrderedItems: () => RovingTabIndexItem[]
35
+ elements: MutableRefObject<Map<string, HTMLElement>>
36
+ }
37
+
38
+ const [RovingTabIndexProvider, useRovingTabIndexContext] =
39
+ createContext<RovingTabIndexContext>("rovingTabIndex")
40
+
41
+ const NODE_SELECTOR = "data-roving-tabindex-node"
42
+ const ROOT_SELECTOR = "data-roving-tabindex-root"
43
+ const NOT_FOCUSABLE_SELECTOR = "data-roving-tabindex-not-focusable"
44
+ const SELECTOR_ACTIVE = `:where([${NODE_SELECTOR}=true]):not(:where([${NOT_FOCUSABLE_SELECTOR}=true] *))`
45
+
46
+ type RovingTabIndexRootBaseProps<T> = {
47
+ children: ReactNode | ReactNode[]
48
+ active?: string
49
+ setActive?: (value: string) => void
50
+ as?: T
51
+ }
52
+
53
+ type RovingTabIndexRootProps<T extends ElementType> =
54
+ RovingTabIndexRootBaseProps<T> &
55
+ Omit<ComponentPropsWithoutRef<T>, keyof RovingTabIndexRootBaseProps<T>>
56
+
57
+ export const RovingTabIndexRoot = <T extends ElementType>({
58
+ children,
59
+ active,
60
+ setActive,
61
+ as,
62
+ ref,
63
+ ...props
64
+ }: RovingTabIndexRootProps<T>) => {
65
+ const Component = as || "div"
66
+ const [isShiftTabbing, setIsShiftTabbing] = useState(false)
67
+
68
+ const [value = null, setValue] = useControlledState({
69
+ prop: active,
70
+ onChange: setActive,
71
+ })
72
+
73
+ const rootRef = useRef<HTMLDivElement | null>(null)
74
+ const elementsRef = useRef<Map<string, HTMLElement>>(new Map())
75
+ const getOrderedItems = useCallback(() => {
76
+ if (!rootRef.current) return []
77
+ const activeElements = Array.from(
78
+ rootRef.current.querySelectorAll(SELECTOR_ACTIVE),
79
+ )
80
+
81
+ return Array.from(elementsRef.current)
82
+ .sort(
83
+ (a, b) => activeElements.indexOf(a[1]) - activeElements.indexOf(b[1]),
84
+ )
85
+ .map(([value, element]) => ({ value, element }))
86
+ }, [])
87
+
88
+ return (
89
+ <RovingTabIndexProvider
90
+ setFocusableId={(value: string) => {
91
+ setValue(value)
92
+ }}
93
+ onShiftTab={() => {
94
+ setIsShiftTabbing(true)
95
+ }}
96
+ currentFocusedValue={value}
97
+ getOrderedItems={getOrderedItems}
98
+ elements={elementsRef}
99
+ >
100
+ <Component
101
+ {...{ [ROOT_SELECTOR]: true }}
102
+ // tabIndex={isShiftTabbing ? -1 : 0}
103
+ onFocus={(e) => {
104
+ console.log("parent focus")
105
+ if (e.target !== e.currentTarget) return
106
+ if (isShiftTabbing) return
107
+ const orderedItems = getOrderedItems()
108
+ if (orderedItems.length === 0) return
109
+
110
+ const candidates = [
111
+ elementsRef.current.get(value ?? ""),
112
+ ...orderedItems.map((i) => i.element),
113
+ ].filter((element): element is HTMLElement => element != null)
114
+
115
+ onFocusFirst(candidates)
116
+ }}
117
+ onBlur={() => setIsShiftTabbing(false)}
118
+ ref={composeRefs(ref, rootRef)}
119
+ {...props}
120
+ >
121
+ {children}
122
+ </Component>
123
+ </RovingTabIndexProvider>
124
+ )
125
+ }
126
+
127
+ export const useRovingTabIndex = (value: string) => {
128
+ const {
129
+ currentFocusedValue,
130
+ setFocusableId,
131
+ onShiftTab,
132
+ getOrderedItems,
133
+ elements,
134
+ } = useRovingTabIndexContext("rovingTabIndex")
135
+
136
+ return {
137
+ getOrderedItems,
138
+ isFocusable: currentFocusedValue === value,
139
+ getRovingProps: <T extends ElementType>(
140
+ props?: ComponentPropsWithoutRef<T>,
141
+ ) => ({
142
+ ...props,
143
+ ref: (element: HTMLElement | null) => {
144
+ if (element) {
145
+ elements.current.set(value, element)
146
+ } else {
147
+ elements.current.delete(value)
148
+ }
149
+ },
150
+ onMouseDown: (e: MouseEvent) => {
151
+ props?.onMouseDown?.(e)
152
+ if (e.target !== e.currentTarget) return
153
+ setFocusableId(value)
154
+ },
155
+ onKeyDown: (e: KeyboardEvent) => {
156
+ props?.onKeyDown?.(e)
157
+ if (e.target !== e.currentTarget) return
158
+ if (isHotkey("shift+tab", e)) {
159
+ onShiftTab()
160
+ return
161
+ }
162
+ },
163
+ onFocus: (e: FocusEvent) => {
164
+ props?.onFocus?.(e)
165
+ if (e.target !== e.currentTarget) return
166
+ setFocusableId(value)
167
+ },
168
+ [NODE_SELECTOR]: true,
169
+ tabIndex: currentFocusedValue === value ? 0 : -1,
170
+ }),
171
+ }
172
+ }
@@ -0,0 +1,11 @@
1
+ import { createContext } from "../../hooks/createContext"
2
+
3
+ type Value = string
4
+
5
+ export interface TabContext {
6
+ selected?: Value
7
+ onSelect?: (index: Value) => void
8
+ tabId: string
9
+ }
10
+
11
+ export const [TabProvider, useTabContext] = createContext<TabContext>("tab")
@@ -0,0 +1,75 @@
1
+ // import { cx } from "@styled-system/css"
2
+ // // import { tagButton, type TagButtonVariant } from "@styled-system/recipes"
3
+ // import { forwardRef } from "react"
4
+ // import { useControlledState } from "../../hooks/useControllableState"
5
+
6
+ // type TagButtonProps = Partial<TagButtonVariant> & {
7
+ // /** 버튼 내부에 표시될 텍스트 */
8
+ // children: string
9
+
10
+ // /** 버튼의 비활성화 여부를 지정합니다. true일 경우 버튼이 비활성화됩니다. */
11
+ // disabled?: boolean
12
+
13
+ // /** 버튼에 적용할 추가적인 CSS 클래스명 */
14
+ // className?: string
15
+
16
+ // /** 버튼의 고유 식별자 */
17
+ // id?: string
18
+
19
+ // /**
20
+ // * 버튼 클릭 시 실행될 함수
21
+ // * @param e 선택적 boolean 매개변수
22
+ // */
23
+ // onClick?: (e?: boolean) => void
24
+
25
+ // /** 버튼의 클릭 상태를 나타냅니다. true일 경우 클릭된 상태를 의미합니다. */
26
+ // isClicked?: boolean
27
+
28
+ // /** 버튼의 초기 클릭 상태를 설정합니다. true일 경우 처음부터 클릭된 상태로 시작합니다. */
29
+ // defaultClick?: boolean
30
+ // }
31
+
32
+ // export const TagButton = forwardRef<HTMLSpanElement, TagButtonProps>(
33
+ // (
34
+ // {
35
+ // children,
36
+ // disabled = false,
37
+ // className,
38
+ // id,
39
+ // onClick,
40
+ // isClicked,
41
+ // defaultClick = false,
42
+ // ...rest
43
+ // },
44
+ // ref,
45
+ // ) => {
46
+ // const [clicked, setClicked] = useControlledState({
47
+ // prop: isClicked,
48
+ // defaultProp: defaultClick,
49
+ // onChange: onClick,
50
+ // })
51
+ // //
52
+ // return (
53
+ // <span
54
+ // role="button"
55
+ // ref={ref}
56
+ // id={id}
57
+ // className={cx(tagButton({ ...rest }), className)}
58
+ // aria-disabled={disabled}
59
+ // aria-pressed={clicked}
60
+ // {...(disabled && { "data-invalid": "true" })}
61
+ // data-testid={id}
62
+ // onClick={() => {
63
+ // if (disabled) {
64
+ // return
65
+ // }
66
+ // setClicked((prev) => !prev)
67
+ // }}
68
+ // >
69
+ // {children}
70
+ // </span>
71
+ // )
72
+ // },
73
+ // )
74
+
75
+ // TagButton.displayName = "TagButton"
@@ -0,0 +1,35 @@
1
+ import { define } from "../../../dev"
2
+ export const tagButtonRecipe = define.recipe({
3
+ className: "tagButton",
4
+ base: {
5
+ textAlign: "center",
6
+ borderRadius: "rounded",
7
+ padding: "4px 10px",
8
+ fontWeight: "normal",
9
+ cursor: "pointer",
10
+ border: "1px solid black",
11
+ color: "text_primary",
12
+ _hover: { boxShadow: "0px 0px 3px", boxShadowColor: "grey_300" },
13
+ _invalid: {
14
+ cursor: "not-allowed",
15
+ },
16
+ _pressed: {
17
+ borderWidth: "2px",
18
+ borderColor: "blue_300",
19
+ },
20
+ maxWidth: "screen",
21
+ },
22
+ variants: {
23
+ size: {
24
+ small: {
25
+ fontSize: "sm",
26
+ },
27
+ large: {
28
+ fontSize: "lg",
29
+ },
30
+ },
31
+ },
32
+ defaultVariants: {
33
+ size: "small",
34
+ },
35
+ })
@@ -0,0 +1,26 @@
1
+ // polymorphic.d.ts
2
+ import {
3
+ type ComponentPropsWithRef,
4
+ type ComponentPropsWithoutRef,
5
+ type ElementType,
6
+ } from "react"
7
+
8
+ type AsProp<C extends ElementType> = {
9
+ as?: C
10
+ }
11
+
12
+ export type AsRef<C extends ElementType> = ComponentPropsWithRef<C>["ref"]
13
+
14
+ export type AsComponentProps<
15
+ C extends ElementType,
16
+ Props = object,
17
+ > = AsProp<C> &
18
+ ComponentPropsWithoutRef<C> &
19
+ Props & {
20
+ ref?: AsRef<C>
21
+ }
22
+
23
+ export type AsComponentPropsWithRef<
24
+ C extends ElementType,
25
+ Props = object,
26
+ > = Props & { ref?: AsRef<C> }
@@ -0,0 +1,5 @@
1
+ export * from "./Button"
2
+ // export * from "./TagButton/TagButton"
3
+ export * from "./Accordion"
4
+ // export * from "./Tab/index"
5
+ // export * from "./Slider/Slider"
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ "use strict";var w=Object.create;var p=Object.defineProperty;var C=Object.getOwnPropertyDescriptor;var x=Object.getOwnPropertyNames;var P=Object.getPrototypeOf,j=Object.prototype.hasOwnProperty;var O=(o,t,n,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of x(t))!j.call(o,a)&&a!==n&&p(o,a,{get:()=>t[a],enumerable:!(i=C(t,a))||i.enumerable});return o};var r=(o,t,n)=>(n=o!=null?w(P(o)):{},O(t||!o||!o.__esModule?p(n,"default",{value:o,enumerable:!0}):n,o));var g=r(require("yargs")),y=require("yargs/helpers");var m=r(require("path")),c=r(require("fs-extra")),l=require("pkg-dir"),e=r(require("@clack/prompts"));async function f(){let o=(0,l.packageDirectorySync)()||process.cwd(),t=m.default.join(o,"park-ui.json");try{return await c.default.readJSON(t)}catch{e.note("\uC124\uC815 \uD30C\uC77C\uC774 \uC874\uC7AC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");let n=await b();return await c.default.outputJSON(t,{...n},{spaces:2}),n}}var b=async()=>e.group({outputPath:()=>e.text({message:"\uCEF4\uD3EC\uB10C\uD2B8 \uC800\uC7A5 \uACBD\uB85C\uB97C \uC124\uC815\uD574\uC8FC\uC138\uC694",initialValue:"./src/components/ui",validate:o=>{if(!o)return"Please enter a path.";if(!o.startsWith("."))return"Please enter a relative path to the project root."}})},{onCancel:()=>{e.cancel("Operation cancelled."),process.exit(0)}});var h=r(require("@clack/prompts"));var s=r(require("path")),d=r(require("fs-extra")),S=s.default.join(__dirname,"./components/Button/index.tsx");async function u(o,t){try{let n=S,i=s.default.join(process.cwd(),t,`${o}.tsx`);await d.default.copy(n,i),console.log(`${o} copied to ${t}`)}catch(n){throw new Error(`Failed to copy component: ${n}`)}}var $=async()=>{await(0,g.default)((0,y.hideBin)(process.argv)).command("components add [components..]","Add components to your project",o=>o.positional("components",{describe:"List of components to add",type:"string",array:!0,default:[]}).option("all",{type:"boolean",description:"Add all components",default:!1}),async o=>{if(o.components.length===0&&!o.all){h.note("Error: You need to specify at least one component or use the --all flag"),console.log('Run "cli --help" for more information');return}let t=await f();await u(o.components[0],t.outputPath)}).demandCommand(1,"You need at least one command before moving on").strict().help().argv};$();
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import g from"yargs";import{hideBin as y}from"yargs/helpers";import m from"node:path";import r from"fs-extra";import{packageDirectorySync as l}from"pkg-dir";import*as t from"@clack/prompts";async function a(){let o=l()||process.cwd(),n=m.join(o,"park-ui.json");try{return await r.readJSON(n)}catch{t.note("\uC124\uC815 \uD30C\uC77C\uC774 \uC874\uC7AC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");let e=await f();return await r.outputJSON(n,{...e},{spaces:2}),e}}var f=async()=>t.group({outputPath:()=>t.text({message:"\uCEF4\uD3EC\uB10C\uD2B8 \uC800\uC7A5 \uACBD\uB85C\uB97C \uC124\uC815\uD574\uC8FC\uC138\uC694",initialValue:"./src/components/ui",validate:o=>{if(!o)return"Please enter a path.";if(!o.startsWith("."))return"Please enter a relative path to the project root."}})},{onCancel:()=>{t.cancel("Operation cancelled."),process.exit(0)}});import*as s from"@clack/prompts";import i from"node:path";import d from"fs-extra";var u=i.join(__dirname,"./components/Button/index.tsx");async function c(o,n){try{let e=u,p=i.join(process.cwd(),n,`${o}.tsx`);await d.copy(e,p),console.log(`${o} copied to ${n}`)}catch(e){throw new Error(`Failed to copy component: ${e}`)}}var h=async()=>{await g(y(process.argv)).command("components add [components..]","Add components to your project",o=>o.positional("components",{describe:"List of components to add",type:"string",array:!0,default:[]}).option("all",{type:"boolean",description:"Add all components",default:!1}),async o=>{if(o.components.length===0&&!o.all){s.note("Error: You need to specify at least one component or use the --all flag"),console.log('Run "cli --help" for more information');return}let n=await a();await c(o.components[0],n.outputPath)}).demandCommand(1,"You need at least one command before moving on").strict().help().argv};h();
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@jongh/cli",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "bin": "./dist/index.js",
7
+ "keywords": [],
8
+ "author": "",
9
+ "license": "ISC",
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "dependencies": {
14
+ "@clack/prompts": "^0.7.0",
15
+ "@effect/platform": "^0.69.13",
16
+ "@effect/schema": "^0.75.5",
17
+ "effect": "^3.10.8",
18
+ "fs-extra": "^11.2.0",
19
+ "pkg-dir": "^8.0.0",
20
+ "yargs": "^17.7.2"
21
+ },
22
+ "devDependencies": {
23
+ "@types/fs-extra": "^11.0.4",
24
+ "@types/node": "^22.8.6",
25
+ "@types/yargs": "^17.0.33",
26
+ "ts-node": "^10.9.2",
27
+ "tsup": "^8.3.0",
28
+ "tsx": "^4.19.2"
29
+ },
30
+ "scripts": {
31
+ "build": "tsup",
32
+ "release": "pnpm publish -no-git-checks"
33
+ }
34
+ }
package/park-ui.json ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "outputPath": "./src/components/ui"
3
+ }
@@ -0,0 +1,27 @@
1
+ // src/copy-components.ts
2
+ import path from "node:path"
3
+ import fs from "fs-extra"
4
+
5
+ // __dirname은 현재 실행되는 스크립트의 디렉토리 경로 (우리 패키지 내부)
6
+ const COMPONENTS_DIR = path.join(__dirname, "./components/Button/index.tsx")
7
+
8
+ export async function copyComponent(componentName: string, outputPath: string) {
9
+ try {
10
+ // 1. 우리 패키지 안의 컴포넌트 경로
11
+ const sourceFile = COMPONENTS_DIR
12
+
13
+ // 2. 유저 프로젝트의 목적지 경로 (CLI 실행 위치 기준)
14
+ const targetFile = path.join(
15
+ process.cwd(),
16
+ outputPath,
17
+ `${componentName}.tsx`,
18
+ )
19
+
20
+ // 3. 복사 실행
21
+ await fs.copy(sourceFile, targetFile)
22
+
23
+ console.log(`${componentName} copied to ${outputPath}`)
24
+ } catch (error) {
25
+ throw new Error(`Failed to copy component: ${error}`)
26
+ }
27
+ }