@jongh/cli 1.1.0 → 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.
Files changed (34) hide show
  1. package/dist/index.js +1 -1
  2. package/package.json +16 -3
  3. package/.lintstagedrc.json +0 -4
  4. package/CHANGELOG.md +0 -7
  5. package/dist/components/Accordion/Accordion.tsx +0 -281
  6. package/dist/components/Accordion/index.tsx +0 -37
  7. package/dist/components/Accordion/useAccordionHeight.ts +0 -25
  8. package/dist/components/Avatar/Avatar.tsx +0 -18
  9. package/dist/components/Avatar/useAvatar.ts +0 -73
  10. package/dist/components/Button/index.tsx +0 -32
  11. package/dist/components/Primitive/index.tsx +0 -58
  12. package/dist/components/RovingIndex/RovingItem.tsx +0 -47
  13. package/dist/components/RovingIndex/RovingTabIndexRoot.tsx +0 -92
  14. package/dist/components/RovingIndex/index.ts +0 -3
  15. package/dist/components/RovingIndex/useRovingTabIndex.tsx +0 -76
  16. package/dist/components/Select/index.tsx +0 -231
  17. package/dist/components/Slider/index.tsx +0 -141
  18. package/dist/components/Slider/style.ts +0 -29
  19. package/dist/components/Tabs/Tab.tsx +0 -39
  20. package/dist/components/Tabs/TabContent.tsx +0 -62
  21. package/dist/components/Tabs/TabIndicator.tsx +0 -6
  22. package/dist/components/Tabs/TabList.tsx +0 -117
  23. package/dist/components/Tabs/index.ts +0 -43
  24. package/dist/components/Tabs/useRovingTabIndex.tsx +0 -171
  25. package/dist/components/Tabs/useTabContext.ts +0 -11
  26. package/dist/components/TagButton/TagButton.tsx +0 -75
  27. package/dist/components/TagButton/style.ts +0 -35
  28. package/dist/components/core/Polymorphic/index.ts +0 -26
  29. package/dist/components/index.ts +0 -5
  30. package/src/copyComponent.ts +0 -29
  31. package/src/getConfig.ts +0 -56
  32. package/src/index.ts +0 -46
  33. package/tsconfig.json +0 -14
  34. package/tsup.config.ts +0 -14
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import P from"yargs";import{hideBin as C}from"yargs/helpers";import m from"node:path";import a from"fs-extra";import{packageDirectory as l}from"pkg-dir";import*as t from"@clack/prompts";var f="./src/components/ui",d="component.json";async function i(){let o=await l(),n=m.join(o||process.cwd(),d);try{return await a.readJSON(n)}catch{t.note("\uC124\uC815 \uD30C\uC77C\uC774 \uC874\uC7AC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");let e=await u();return await a.outputJSON(n,{...e},{spaces:2}),e}}var u=async()=>t.group({outputPath:()=>t.text({message:"\uCEF4\uD3EC\uB10C\uD2B8 \uC800\uC7A5 \uACBD\uB85C\uB97C \uC124\uC815\uD574\uC8FC\uC138\uC694",initialValue:f,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 r from"node:path";import g from"fs-extra";import{fileURLToPath as h}from"node:url";var y=r.dirname(h(import.meta.url)),w=r.join(y,"./components/Button/index.tsx");async function c(o,n){try{let e=w,p=r.join(process.cwd(),n,`${o}.tsx`);await g.copy(e,p),console.log(`${o} copied to ${n}`)}catch(e){throw new Error(`Failed to copy component: ${e}`)}}var j=async()=>{await P(C(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.cancel("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 i();await c(o.components[0],n.outputPath)}).demandCommand(1,"You need at least one command before moving on").strict().help().argv};j();
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.1.0",
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,4 +0,0 @@
1
- {
2
- "**/*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"]
3
- }
4
-
package/CHANGELOG.md DELETED
@@ -1,7 +0,0 @@
1
- # @jongh/cli
2
-
3
- ## 1.1.0
4
-
5
- ### Minor Changes
6
-
7
- - cb4aefa: test changeset ci build issue
@@ -1,281 +0,0 @@
1
- import * as React from "react"
2
- import { createContext } from "../../hooks/createContext"
3
- import { useControlledState } from "../../hooks/useControllableState"
4
- import { useKeyboardEvent } from "../../hooks/useKeyboardEvent"
5
- import { Slot } from "@radix-ui/react-slot"
6
- import { composeRefs } from "src/hooks/useComposedRefs"
7
-
8
- const ACCORDION_KEYS = [
9
- "Home",
10
- "End",
11
- "ArrowDown",
12
- "ArrowUp",
13
- "ArrowLeft",
14
- "ArrowRight",
15
- ]
16
- export type TAccordionContext = {
17
- selected?: string[]
18
- onItemOpen: (value: string) => void
19
- onItemClose: (value: string) => void
20
- refs?: HTMLElement[]
21
- }
22
-
23
- const [AccordionProvider, useAccordionContext] =
24
- createContext<TAccordionContext>("accordion")
25
-
26
- export interface AccordionProps {
27
- value?: string[]
28
- defaultValue?: string[]
29
- onValueChange?: (value: string[]) => void
30
- asChild?: boolean
31
- type?: "single" | "multi"
32
- }
33
-
34
- export const Accordion = React.forwardRef<HTMLDivElement, AccordionProps>(
35
- (props, ref) => {
36
- const { type = "single", ...accordionProps } = props
37
- if (type === "single") {
38
- return <SingleAccordion {...accordionProps} ref={ref} />
39
- }
40
- if (type === "multi") {
41
- return <MultiAccordion {...accordionProps} />
42
- }
43
- },
44
- )
45
-
46
- Accordion.displayName = "Accordion"
47
-
48
- interface AccordionImplSingleProps {
49
- value?: string[] // 현재 선택되어있는 item들을 담아놓는 용도
50
- defaultValue?: string[]
51
- onValueChange?: (value: string[]) => void
52
- asChild?: boolean
53
- children?: React.ReactNode
54
- }
55
-
56
- const SingleAccordion = React.forwardRef<
57
- HTMLDivElement,
58
- AccordionImplSingleProps
59
- >(
60
- (
61
- { value, defaultValue = [], onValueChange, children, asChild, ...props },
62
- forwardedRef,
63
- ) => {
64
- const [selected = [], setSelected] = useControlledState({
65
- prop: value,
66
- defaultProp: defaultValue,
67
- onChange: onValueChange,
68
- })
69
-
70
- const { refs: accordionRefs, handleKeyDown } = useKeyboardEvent({
71
- keyList: ACCORDION_KEYS,
72
- })
73
-
74
- const Comp = asChild ? Slot : "div"
75
-
76
- return (
77
- <AccordionProvider
78
- selected={selected}
79
- onItemOpen={(value) => setSelected((_) => [value])}
80
- onItemClose={() => setSelected((_) => [])}
81
- refs={accordionRefs.current || []}
82
- >
83
- <Comp
84
- ref={composeRefs((node) => {
85
- accordionRefs.current = Array.from(
86
- node?.children || [],
87
- ) as HTMLElement[]
88
- }, forwardedRef)}
89
- onKeyDown={(e) => {
90
- handleKeyDown(e)
91
- }}
92
- {...props}
93
- >
94
- {children}
95
- </Comp>
96
- </AccordionProvider>
97
- )
98
- },
99
- )
100
-
101
- interface AccordionImplMultiProps {
102
- value?: string[] // 현재 선택되어있는 item들을 담아놓는 용도
103
- defaultValue?: string[]
104
- onValueChange?: (value: string[]) => void
105
- asChild?: boolean
106
- children?: React.ReactNode
107
- }
108
-
109
- const MultiAccordion = React.forwardRef<
110
- HTMLDivElement,
111
- AccordionImplMultiProps
112
- >(
113
- (
114
- { value, defaultValue, onValueChange, children, asChild, ...props },
115
- forwardedRef,
116
- ) => {
117
- const [selected, setSelected] = useControlledState({
118
- prop: value,
119
- defaultProp: defaultValue,
120
- onChange: onValueChange,
121
- })
122
-
123
- const handleItemOpen = React.useCallback(
124
- (item: string) => {
125
- setSelected((prev = []) => [...prev, item])
126
- },
127
- [setSelected],
128
- )
129
-
130
- const handleItemClose = React.useCallback(
131
- (item: string) =>
132
- setSelected((prev = []) => prev.filter((value) => value !== item)),
133
- [setSelected],
134
- )
135
-
136
- const { refs: accordionRefs, handleKeyDown } = useKeyboardEvent({
137
- keyList: ACCORDION_KEYS,
138
- })
139
-
140
- const Comp = asChild ? Slot : "div"
141
-
142
- return (
143
- <AccordionProvider
144
- selected={selected}
145
- onItemOpen={handleItemOpen}
146
- onItemClose={handleItemClose}
147
- >
148
- <Comp
149
- ref={(node) => {
150
- accordionRefs.current = Array.from(
151
- node?.children || [],
152
- ) as HTMLElement[]
153
- if (typeof forwardedRef === "function") {
154
- forwardedRef(node)
155
- } else if (forwardedRef) {
156
- ;(
157
- forwardedRef as React.MutableRefObject<HTMLDivElement | null>
158
- ).current = node
159
- }
160
- }}
161
- onKeyDown={handleKeyDown}
162
- {...props}
163
- >
164
- {children}
165
- </Comp>
166
- </AccordionProvider>
167
- )
168
- },
169
- )
170
- //AccordionItem
171
- interface TAccordionItemContext {
172
- isOpen: boolean
173
- onToggle: () => void
174
- value?: string
175
- }
176
-
177
- const [AccordionItemProvider, useAccordionItemProvider] =
178
- createContext<TAccordionItemContext>("accordionItem")
179
- export interface AccordionItemProps {
180
- value: string //unique value
181
- disabled?: boolean
182
- children: React.ReactNode
183
- asChild?: boolean
184
- }
185
-
186
- export const AccordionItem = React.forwardRef<
187
- HTMLDivElement,
188
- AccordionItemProps
189
- >(({ value, disabled = false, children, asChild, ...props }, forwardedRef) => {
190
- const { selected, onItemOpen, onItemClose } = useAccordionContext("accordion")
191
-
192
- const isOpen = selected?.includes(value) && !disabled
193
-
194
- const onToggle = () => {
195
- if (disabled) {
196
- return
197
- }
198
- isOpen ? onItemClose(value) : onItemOpen(value)
199
- }
200
-
201
- const Comp = asChild ? Slot : "div"
202
-
203
- return (
204
- <AccordionItemProvider isOpen={!!isOpen} onToggle={onToggle} value={value}>
205
- <Comp
206
- ref={forwardedRef}
207
- data-state={isOpen ? "open" : "close"}
208
- tabIndex={0}
209
- onKeyDown={(e) => {
210
- if (e.key === "Enter") {
211
- onToggle()
212
- }
213
- }}
214
- {...props}
215
- >
216
- {children}
217
- </Comp>
218
- </AccordionItemProvider>
219
- )
220
- })
221
-
222
- export interface AccordionTriggerProps extends React.PropsWithChildren {}
223
- export const AccordionTrigger = ({
224
- children,
225
- ...props
226
- }: AccordionTriggerProps) => {
227
- const { onToggle, isOpen, value } = useAccordionItemProvider("accordionItem")
228
- return (
229
- <h3 data-state={isOpen ? "open" : "close"} onClick={onToggle} {...props}>
230
- <button
231
- aria-expanded={isOpen ? "true" : "false"}
232
- aria-controls={`content-${value}`}
233
- id={`trigger-${value}`}
234
- >
235
- {children}
236
- </button>
237
- </h3>
238
- )
239
- }
240
- //AccordionContent
241
- export interface AccordionContentProps extends React.PropsWithChildren {
242
- duration?: number
243
- asChild?: boolean
244
- }
245
-
246
- export const AccordionContent = ({
247
- children,
248
- asChild,
249
- ...props
250
- }: AccordionContentProps) => {
251
- const Comp = asChild ? Slot : "div"
252
- const { isOpen, value } = useAccordionItemProvider("accordionItem")
253
- const contentRef = React.useRef<HTMLDivElement>(null)
254
-
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])
268
-
269
- return (
270
- <Comp
271
- ref={contentRef}
272
- data-state={isOpen ? "open" : "close"}
273
- id={`content-${value}`}
274
- aria-labelledby={`trigger-${value}`}
275
- role="region"
276
- {...props}
277
- >
278
- {isOpen && children}
279
- </Comp>
280
- )
281
- }
@@ -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,25 +0,0 @@
1
- import { useEffect, useRef } from "react"
2
-
3
- const ACCORDION_HEIGHT = "--accordion-height"
4
-
5
- export const useAccordionHeight = <T extends HTMLElement>(isOpen: boolean) => {
6
- const ref = useRef<T>(null)
7
-
8
- useEffect(() => {
9
- const element = ref.current
10
- if (!element) return
11
-
12
- if (isOpen) {
13
- // 열릴 때 높이를 scrollHeight로 설정
14
- const newHeight = element.scrollHeight
15
- element.style.setProperty(ACCORDION_HEIGHT, `${newHeight}px`)
16
- element.style.height = `${newHeight}px`
17
- } else {
18
- // 닫힐 때 높이를 0으로 설정
19
- element.style.setProperty(ACCORDION_HEIGHT, `0px`)
20
- element.style.height = "0px"
21
- }
22
- }, [isOpen])
23
-
24
- return ref
25
- }
@@ -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,32 +0,0 @@
1
- import { forwardRef, ComponentPropsWithoutRef, type ReactNode } from "react"
2
- import { button, type ButtonVariant } from "@styled-system/recipes"
3
- import { Slot } from "@radix-ui/react-slot"
4
- import { cx } from "@styled-system/css"
5
-
6
- export type ButtonProps = ComponentPropsWithoutRef<"button"> & {
7
- children: ReactNode
8
- as?: boolean
9
- disabled?: boolean
10
- } & Partial<ButtonVariant>
11
-
12
- export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
13
- ({ as, disabled, id, onClick, children, ...rest }, ref) => {
14
- const [buttonProps] = button.splitVariantProps(rest)
15
-
16
- const Comp = as ? Slot : "button"
17
-
18
- return (
19
- <Comp
20
- role="button"
21
- ref={ref}
22
- disabled={disabled}
23
- id={id}
24
- onClick={onClick}
25
- className={cx(button(buttonProps))}
26
- {...rest}
27
- >
28
- {children}
29
- </Comp>
30
- )
31
- },
32
- )
@@ -1,58 +0,0 @@
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 }
@@ -1,47 +0,0 @@
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
- }