@jongh/cli 1.0.1 → 1.1.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.
Files changed (34) hide show
  1. package/.lintstagedrc.json +4 -0
  2. package/CHANGELOG.md +7 -0
  3. package/dist/components/Accordion/Accordion.tsx +21 -14
  4. package/dist/components/Accordion/useAccordionHeight.ts +13 -16
  5. package/dist/components/Button/index.tsx +11 -45
  6. package/dist/components/Primitive/index.tsx +58 -0
  7. package/dist/components/RovingIndex/RovingItem.tsx +47 -0
  8. package/dist/components/RovingIndex/RovingTabIndexRoot.tsx +92 -0
  9. package/dist/components/RovingIndex/index.ts +3 -0
  10. package/dist/components/RovingIndex/useRovingTabIndex.tsx +76 -0
  11. package/dist/components/Select/index.tsx +231 -0
  12. package/dist/components/Slider/index.tsx +141 -0
  13. package/dist/components/Slider/style.ts +28 -41
  14. package/dist/components/{Tab → Tabs}/Tab.tsx +11 -7
  15. package/dist/components/{Tab → Tabs}/TabContent.tsx +4 -2
  16. package/dist/components/Tabs/TabIndicator.tsx +6 -0
  17. package/dist/components/{Tab → Tabs}/TabList.tsx +20 -21
  18. package/dist/components/Tabs/index.ts +43 -0
  19. package/dist/components/{Tab → Tabs}/useRovingTabIndex.tsx +0 -1
  20. package/dist/components/index.ts +3 -3
  21. package/dist/index.js +1 -1
  22. package/package.json +1 -1
  23. package/src/{fetchComponent.ts → copyComponent.ts} +3 -1
  24. package/src/getConfig.ts +7 -4
  25. package/src/index.ts +2 -2
  26. package/tsup.config.ts +1 -1
  27. package/dist/components/Slider/Slider.tsx +0 -140
  28. package/dist/components/Tab/TabIndicator.tsx +0 -19
  29. package/dist/components/Tab/index.ts +0 -4
  30. package/dist/components/Tab/style.ts +0 -9
  31. package/dist/index.cjs +0 -2
  32. package/dist/index.d.cts +0 -1
  33. package/park-ui.json +0 -3
  34. /package/dist/components/{Tab → Tabs}/useTabContext.ts +0 -0
@@ -0,0 +1,4 @@
1
+ {
2
+ "**/*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"]
3
+ }
4
+
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @jongh/cli
2
+
3
+ ## 1.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - cb4aefa: test changeset ci build issue
@@ -1,7 +1,6 @@
1
1
  import * as React from "react"
2
2
  import { createContext } from "../../hooks/createContext"
3
3
  import { useControlledState } from "../../hooks/useControllableState"
4
- import { useAccordionHeight } from "./useAccordionHeight"
5
4
  import { useKeyboardEvent } from "../../hooks/useKeyboardEvent"
6
5
  import { Slot } from "@radix-ui/react-slot"
7
6
  import { composeRefs } from "src/hooks/useComposedRefs"
@@ -227,15 +226,14 @@ export const AccordionTrigger = ({
227
226
  }: AccordionTriggerProps) => {
228
227
  const { onToggle, isOpen, value } = useAccordionItemProvider("accordionItem")
229
228
  return (
230
- <h3 data-state={isOpen ? "open" : "close"} {...props}>
231
- <span
232
- onClick={onToggle}
229
+ <h3 data-state={isOpen ? "open" : "close"} onClick={onToggle} {...props}>
230
+ <button
233
231
  aria-expanded={isOpen ? "true" : "false"}
234
232
  aria-controls={`content-${value}`}
235
233
  id={`trigger-${value}`}
236
234
  >
237
- <span>{children}</span>
238
- </span>
235
+ {children}
236
+ </button>
239
237
  </h3>
240
238
  )
241
239
  }
@@ -244,31 +242,40 @@ export interface AccordionContentProps extends React.PropsWithChildren {
244
242
  duration?: number
245
243
  asChild?: boolean
246
244
  }
245
+
247
246
  export const AccordionContent = ({
248
247
  children,
249
- duration = 150,
250
248
  asChild,
251
249
  ...props
252
250
  }: AccordionContentProps) => {
253
251
  const Comp = asChild ? Slot : "div"
254
252
  const { isOpen, value } = useAccordionItemProvider("accordionItem")
253
+ const contentRef = React.useRef<HTMLDivElement>(null)
255
254
 
256
- const contentRef = useAccordionHeight<HTMLDivElement>(isOpen, duration) //duration 초 뒤에 accordion을 열거나 닫아줌
257
-
258
- if (!isOpen) {
259
- return null
260
- }
255
+ React.useEffect(() => {
256
+ if (contentRef.current) {
257
+ const element = contentRef.current
258
+ if (isOpen) {
259
+ element.style.setProperty(
260
+ "--accordion-height",
261
+ `${element.scrollHeight}px`,
262
+ )
263
+ } else {
264
+ element.style.setProperty("--accordion-height", "0px")
265
+ }
266
+ }
267
+ }, [isOpen])
261
268
 
262
269
  return (
263
270
  <Comp
271
+ ref={contentRef}
264
272
  data-state={isOpen ? "open" : "close"}
265
273
  id={`content-${value}`}
266
274
  aria-labelledby={`trigger-${value}`}
267
275
  role="region"
268
- ref={contentRef}
269
276
  {...props}
270
277
  >
271
- {children}
278
+ {isOpen && children}
272
279
  </Comp>
273
280
  )
274
281
  }
@@ -1,28 +1,25 @@
1
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
- ) => {
2
+
3
+ const ACCORDION_HEIGHT = "--accordion-height"
4
+
5
+ export const useAccordionHeight = <T extends HTMLElement>(isOpen: boolean) => {
7
6
  const ref = useRef<T>(null)
8
7
 
9
8
  useEffect(() => {
10
9
  const element = ref.current
11
- if (element === null) {
12
- return
13
- }
10
+ if (!element) return
14
11
 
15
12
  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
- }
13
+ // 열릴 높이를 scrollHeight로 설정
14
+ const newHeight = element.scrollHeight
15
+ element.style.setProperty(ACCORDION_HEIGHT, `${newHeight}px`)
16
+ element.style.height = `${newHeight}px`
20
17
  } else {
21
- setTimeout(() => {
22
- element.style.setProperty(ACCORDION_HEIGHT, `${element.scrollHeight}px`)
23
- }, duration)
18
+ // 닫힐 때 높이를 0으로 설정
19
+ element.style.setProperty(ACCORDION_HEIGHT, `0px`)
20
+ element.style.height = "0px"
24
21
  }
25
- }, [isOpen, duration])
22
+ }, [isOpen])
26
23
 
27
24
  return ref
28
25
  }
@@ -1,50 +1,19 @@
1
- import {
2
- forwardRef,
3
- ComponentPropsWithoutRef,
4
- isValidElement,
5
- cloneElement,
6
- type ReactElement,
7
- type MouseEventHandler,
8
- } from "react"
1
+ import { forwardRef, ComponentPropsWithoutRef, type ReactNode } from "react"
9
2
  import { button, type ButtonVariant } from "@styled-system/recipes"
10
3
  import { Slot } from "@radix-ui/react-slot"
4
+ import { cx } from "@styled-system/css"
11
5
 
12
- type ButtonProps = Partial<ButtonVariant> &
13
- ComponentPropsWithoutRef<"button"> & { as?: string } & {
14
- /**왼쪽에 올 아이콘 */
15
- leftIcon?: ReactElement
16
- /**오른쪽에 위치할 아이콘 */
17
- rightIcon?: ReactElement
18
- }
6
+ export type ButtonProps = ComponentPropsWithoutRef<"button"> & {
7
+ children: ReactNode
8
+ as?: boolean
9
+ disabled?: boolean
10
+ } & Partial<ButtonVariant>
19
11
 
20
12
  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"
13
+ ({ as, disabled, id, onClick, children, ...rest }, ref) => {
14
+ const [buttonProps] = button.splitVariantProps(rest)
36
15
 
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
- }
16
+ const Comp = as ? Slot : "button"
48
17
 
49
18
  return (
50
19
  <Comp
@@ -52,14 +21,11 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
52
21
  ref={ref}
53
22
  disabled={disabled}
54
23
  id={id}
55
- data-testid={id}
56
24
  onClick={onClick}
57
- className={button({})}
25
+ className={cx(button(buttonProps))}
58
26
  {...rest}
59
27
  >
60
- {leftIcon && <span>{wrapIcon(leftIcon)}</span>}
61
28
  {children}
62
- {rightIcon && <span>{wrapIcon(rightIcon)}</span>}
63
29
  </Comp>
64
30
  )
65
31
  },
@@ -0,0 +1,58 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ //radix ui Primitive를 참고하여 작성한 코드
3
+ import * as React from "react"
4
+ import { Slot } from "@radix-ui/react-slot"
5
+
6
+ const NODES = [
7
+ "a",
8
+ "button",
9
+ "div",
10
+ "form",
11
+ "h2",
12
+ "h3",
13
+ "img",
14
+ "input",
15
+ "label",
16
+ "li",
17
+ "nav",
18
+ "ol",
19
+ "p",
20
+ "span",
21
+ "svg",
22
+ "ul",
23
+ ] as const
24
+
25
+ type Primitives = {
26
+ [E in (typeof NODES)[number]]: PrimitiveForwardRefComponent<E>
27
+ }
28
+ type PrimitivePropsWithRef<E extends React.ElementType> =
29
+ React.ComponentPropsWithRef<E> & {
30
+ asChild?: boolean
31
+ }
32
+
33
+ interface PrimitiveForwardRefComponent<E extends React.ElementType>
34
+ extends React.ForwardRefExoticComponent<PrimitivePropsWithRef<E>> {}
35
+
36
+ /* -------------------------------------------------------------------------------------------------
37
+ * Primitive
38
+ * -----------------------------------------------------------------------------------------------*/
39
+
40
+ const Primitive = NODES.reduce((primitive, node) => {
41
+ const Node = React.forwardRef(
42
+ (props: PrimitivePropsWithRef<typeof node>, forwardedRef: any) => {
43
+ const { asChild, ...primitiveProps } = props
44
+ const Comp: any = asChild ? Slot : node
45
+
46
+ return <Comp {...primitiveProps} ref={forwardedRef} />
47
+ },
48
+ )
49
+
50
+ Node.displayName = `Primitive.${node}`
51
+
52
+ return { ...primitive, [node]: Node }
53
+ }, {} as Primitives)
54
+
55
+ const Root = Primitive
56
+
57
+ export { Primitive, Root }
58
+ export type { PrimitivePropsWithRef }
@@ -0,0 +1,47 @@
1
+ import type { ReactElement } from "react"
2
+ import { useRovingTabIndex } from "./useRovingTabIndex"
3
+ import { Slot } from "@radix-ui/react-slot"
4
+ import isHotkey from "is-hotkey"
5
+ import {
6
+ getNextFocusableId,
7
+ getPrevFocusableId,
8
+ } from "../../utils/getFocusableId"
9
+
10
+ export const RovingItem = ({
11
+ value,
12
+ children,
13
+ }: {
14
+ value: string
15
+ children: ReactElement
16
+ }) => {
17
+ const { getOrderedItems, getRovingProps, dir } = useRovingTabIndex(value)
18
+
19
+ return (
20
+ <Slot
21
+ {...getRovingProps<"button">({
22
+ onKeyDown: (e) => {
23
+ const items = getOrderedItems()
24
+ let nextItem
25
+ if (dir === "horizontal") {
26
+ if (isHotkey("right", e)) {
27
+ nextItem = getNextFocusableId(items, value)
28
+ } else if (isHotkey("left", e)) {
29
+ nextItem = getPrevFocusableId(items, value)
30
+ }
31
+ nextItem?.element.focus()
32
+ }
33
+ if (dir === "vertical") {
34
+ if (e.key === "ArrowDown") {
35
+ nextItem = getNextFocusableId(items, value)
36
+ } else if (e.key === "ArrowUp") {
37
+ nextItem = getPrevFocusableId(items, value)
38
+ }
39
+ nextItem?.element.focus()
40
+ }
41
+ },
42
+ })}
43
+ >
44
+ {children}
45
+ </Slot>
46
+ )
47
+ }
@@ -0,0 +1,92 @@
1
+ import { ReactNode, useCallback, useRef, useState } from "react"
2
+ import { useControlledState } from "../../hooks/useControllableState"
3
+ import { RovingTabIndexProvider } from "./useRovingTabIndex"
4
+ import { Slot } from "@radix-ui/react-slot"
5
+
6
+ export const NODE_SELECTOR = "data-roving-tabindex-node"
7
+ const ROOT_SELECTOR = "data-roving-tabindex-root"
8
+ const NOT_FOCUSABLE_SELECTOR = "data-roving-tabindex-not-focusable"
9
+ const SELECTOR_ACTIVE = `:where([${NODE_SELECTOR}=true]):not(:where([${NOT_FOCUSABLE_SELECTOR}=true] *))`
10
+
11
+ type RovingTabIndexRootBaseProps = {
12
+ children: ReactNode | ReactNode[]
13
+ active?: string
14
+ setActive?: (value: string) => void
15
+ asChild?: boolean
16
+ dir?: "horizontal" | "vertical"
17
+ }
18
+
19
+ type RovingTabIndexRootProps = RovingTabIndexRootBaseProps
20
+
21
+ function onFocusFirst(candidates: HTMLElement[]) {
22
+ const previousFocus = document.activeElement
23
+ while (document.activeElement === previousFocus && candidates.length > 0) {
24
+ candidates.shift()?.focus()
25
+ }
26
+ }
27
+
28
+ export const RovingTabIndexRoot = ({
29
+ children,
30
+ active,
31
+ setActive,
32
+ asChild,
33
+ dir = "horizontal",
34
+ ...props
35
+ }: RovingTabIndexRootProps) => {
36
+ const [isShiftTabbing, setIsShiftTabbing] = useState(false)
37
+
38
+ const [value = null, setValue] = useControlledState({
39
+ prop: active,
40
+ onChange: setActive,
41
+ })
42
+ const rootRef = useRef<HTMLDivElement | null>(null)
43
+ const elementsRef = useRef<Map<string, HTMLElement>>(new Map())
44
+ const getOrderedItems = useCallback(() => {
45
+ if (!rootRef.current) return []
46
+ const activeElements = Array.from(
47
+ rootRef.current.querySelectorAll(SELECTOR_ACTIVE),
48
+ )
49
+
50
+ return Array.from(elementsRef.current)
51
+ .sort(
52
+ (a, b) => activeElements.indexOf(a[1]) - activeElements.indexOf(b[1]),
53
+ )
54
+ .map(([value, element]) => ({ value, element }))
55
+ }, [])
56
+
57
+ return (
58
+ <RovingTabIndexProvider
59
+ setFocusableId={(value: string) => {
60
+ setValue(value)
61
+ }}
62
+ onShiftTab={() => {
63
+ setIsShiftTabbing(true)
64
+ }}
65
+ currentFocusedValue={value}
66
+ getOrderedItems={getOrderedItems}
67
+ elements={elementsRef}
68
+ dir={dir}
69
+ >
70
+ <Slot
71
+ {...{ [ROOT_SELECTOR]: true }}
72
+ tabIndex={isShiftTabbing ? -1 : 0}
73
+ onFocus={(e) => {
74
+ if (e.target !== e.currentTarget) return
75
+ if (isShiftTabbing) return
76
+ const orderedItems = getOrderedItems()
77
+ if (orderedItems.length === 0) return
78
+ const candidates = [
79
+ elementsRef.current.get(value ?? ""),
80
+ ...orderedItems.map((i) => i.element),
81
+ ].filter((element): element is HTMLElement => element != null)
82
+ onFocusFirst(candidates)
83
+ }}
84
+ onBlur={() => setIsShiftTabbing(false)}
85
+ ref={rootRef}
86
+ {...props}
87
+ >
88
+ {children}
89
+ </Slot>
90
+ </RovingTabIndexProvider>
91
+ )
92
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./RovingItem"
2
+ export * from "./useRovingTabIndex"
3
+ export * from "./RovingTabIndexRoot"
@@ -0,0 +1,76 @@
1
+ import {
2
+ FocusEvent,
3
+ MouseEvent,
4
+ KeyboardEvent,
5
+ ComponentPropsWithoutRef,
6
+ ElementType,
7
+ MutableRefObject,
8
+ } from "react"
9
+ import { createContext } from "../../hooks/createContext"
10
+ import { NODE_SELECTOR } from "./RovingTabIndexRoot"
11
+
12
+ export type RovingTabIndexItem = {
13
+ value: string
14
+ element: HTMLElement
15
+ }
16
+
17
+ type RovingTabIndexContext = {
18
+ currentFocusedValue: string | null
19
+ setFocusableId: (id: string) => void
20
+ onShiftTab: () => void
21
+ getOrderedItems: () => RovingTabIndexItem[]
22
+ elements: MutableRefObject<Map<string, HTMLElement>>
23
+ dir?: "horizontal" | "vertical"
24
+ }
25
+
26
+ export const [RovingTabIndexProvider, useRovingTabIndexContext] =
27
+ createContext<RovingTabIndexContext>("rovingTabIndex")
28
+
29
+ export const useRovingTabIndex = (value: string) => {
30
+ const {
31
+ currentFocusedValue,
32
+ setFocusableId,
33
+ onShiftTab,
34
+ getOrderedItems,
35
+ elements,
36
+ dir,
37
+ } = useRovingTabIndexContext("rovingTabIndex")
38
+
39
+ return {
40
+ getOrderedItems,
41
+ dir,
42
+ isFocusable: currentFocusedValue === value,
43
+ getRovingProps: <T extends ElementType>(
44
+ props?: ComponentPropsWithoutRef<T>,
45
+ ) => ({
46
+ ...props,
47
+ ref: (element: HTMLElement | null) => {
48
+ if (element) {
49
+ elements.current.set(value, element)
50
+ } else {
51
+ elements.current.delete(value)
52
+ }
53
+ },
54
+ onMouseDown: (e: MouseEvent) => {
55
+ props?.onMouseDown?.(e)
56
+ if (e.target !== e.currentTarget) return
57
+ setFocusableId(value)
58
+ },
59
+ onKeyDown: (e: KeyboardEvent) => {
60
+ props?.onKeyDown?.(e)
61
+ if (e.target !== e.currentTarget) return
62
+ if (e.shiftKey && e.key === "Tab") {
63
+ onShiftTab()
64
+ return
65
+ }
66
+ },
67
+ onFocus: (e: FocusEvent) => {
68
+ props?.onFocus?.(e)
69
+ if (e.target !== e.currentTarget) return
70
+ setFocusableId(value)
71
+ },
72
+ [NODE_SELECTOR]: true,
73
+ tabIndex: currentFocusedValue === value ? 0 : -1,
74
+ }),
75
+ }
76
+ }