@jongh/cli 1.0.11 → 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.
- package/.lintstagedrc.json +4 -0
- package/CHANGELOG.md +7 -0
- package/dist/components/Accordion/Accordion.tsx +21 -14
- package/dist/components/Accordion/useAccordionHeight.ts +13 -16
- package/dist/components/Button/index.tsx +11 -45
- package/dist/components/Primitive/index.tsx +58 -0
- package/dist/components/RovingIndex/RovingItem.tsx +47 -0
- package/dist/components/RovingIndex/RovingTabIndexRoot.tsx +92 -0
- package/dist/components/RovingIndex/index.ts +3 -0
- package/dist/components/RovingIndex/useRovingTabIndex.tsx +76 -0
- package/dist/components/Select/index.tsx +231 -0
- package/dist/components/Slider/index.tsx +141 -0
- package/dist/components/Slider/style.ts +28 -41
- package/dist/components/{Tab → Tabs}/Tab.tsx +11 -7
- package/dist/components/{Tab → Tabs}/TabContent.tsx +4 -2
- package/dist/components/Tabs/TabIndicator.tsx +6 -0
- package/dist/components/{Tab → Tabs}/TabList.tsx +20 -21
- package/dist/components/Tabs/index.ts +43 -0
- package/dist/components/{Tab → Tabs}/useRovingTabIndex.tsx +0 -1
- package/dist/components/index.ts +3 -3
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/{fetchComponent.ts → copyComponent.ts} +0 -1
- package/src/getConfig.ts +7 -4
- package/src/index.ts +2 -2
- package/dist/components/Slider/Slider.tsx +0 -140
- package/dist/components/Tab/TabIndicator.tsx +0 -19
- package/dist/components/Tab/index.ts +0 -4
- package/dist/components/Tab/style.ts +0 -9
- package/park-ui.json +0 -3
- /package/dist/components/{Tab → Tabs}/useTabContext.ts +0 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import {
|
|
2
|
+
forwardRef,
|
|
3
|
+
type ComponentPropsWithoutRef,
|
|
4
|
+
type CSSProperties,
|
|
5
|
+
type ReactNode,
|
|
6
|
+
} from "react"
|
|
7
|
+
import { createContext } from "../../hooks/createContext"
|
|
8
|
+
import { useControlledState } from "../../hooks/useControllableState"
|
|
9
|
+
import { Slot } from "@radix-ui/react-slot"
|
|
10
|
+
import { RovingTabIndexRoot } from "../RovingIndex/RovingTabIndexRoot"
|
|
11
|
+
import {
|
|
12
|
+
autoUpdate,
|
|
13
|
+
flip,
|
|
14
|
+
offset,
|
|
15
|
+
size,
|
|
16
|
+
useFloating,
|
|
17
|
+
} from "@floating-ui/react-dom"
|
|
18
|
+
import {
|
|
19
|
+
useRovingTabIndex,
|
|
20
|
+
useRovingTabIndexContext,
|
|
21
|
+
} from "../RovingIndex/useRovingTabIndex"
|
|
22
|
+
import {
|
|
23
|
+
getNextFocusableId,
|
|
24
|
+
getPrevFocusableId,
|
|
25
|
+
} from "../../utils/getFocusableId"
|
|
26
|
+
import { composeRefs } from "../../hooks/useComposedRefs"
|
|
27
|
+
import { Primitive } from "src/components/Primitive"
|
|
28
|
+
|
|
29
|
+
const CONTEXT_NAME = "select"
|
|
30
|
+
|
|
31
|
+
interface SelectContext {
|
|
32
|
+
isOpen: boolean //option들의 열림/닫힘 여부
|
|
33
|
+
onOpenChange: (Open: boolean) => void
|
|
34
|
+
selected: string | null //option들 중 선택된 value
|
|
35
|
+
onSelectChange: (value: string) => void
|
|
36
|
+
floatingStyles?: CSSProperties
|
|
37
|
+
setFloating: (node: HTMLElement) => void
|
|
38
|
+
setReference: (node: HTMLElement | null) => void
|
|
39
|
+
dir: "horizontal" | "vertical"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const [SelectContextProvider, useSelectContext] =
|
|
43
|
+
createContext<SelectContext>(CONTEXT_NAME)
|
|
44
|
+
|
|
45
|
+
export interface SelectProps extends ComponentPropsWithoutRef<"div"> {
|
|
46
|
+
isOpen?: boolean
|
|
47
|
+
onOpenChange?: (open: boolean) => void
|
|
48
|
+
defaultOpen?: boolean
|
|
49
|
+
selected?: string
|
|
50
|
+
onSelectChange?: (value: string) => void
|
|
51
|
+
defaultValue?: string
|
|
52
|
+
children: ReactNode
|
|
53
|
+
dir?: "horizontal" | "vertical"
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const SelectRoot = ({
|
|
57
|
+
isOpen,
|
|
58
|
+
onOpenChange,
|
|
59
|
+
defaultOpen,
|
|
60
|
+
selected,
|
|
61
|
+
onSelectChange,
|
|
62
|
+
defaultValue,
|
|
63
|
+
dir = "vertical",
|
|
64
|
+
children,
|
|
65
|
+
...props
|
|
66
|
+
}: SelectProps) => {
|
|
67
|
+
const [open = false, setIsOpen] = useControlledState({
|
|
68
|
+
prop: isOpen,
|
|
69
|
+
defaultProp: defaultOpen,
|
|
70
|
+
onChange: onOpenChange,
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
const [selectedValue, setSelectedValue] = useControlledState({
|
|
74
|
+
prop: selected,
|
|
75
|
+
defaultProp: defaultValue,
|
|
76
|
+
onChange: onSelectChange,
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const { refs, floatingStyles } = useFloating({
|
|
80
|
+
strategy: "absolute",
|
|
81
|
+
open: open,
|
|
82
|
+
middleware: [
|
|
83
|
+
offset(), //trigger와 얼마나 떨어져있는지
|
|
84
|
+
flip(), //화면 밖으로 나가지 않도록
|
|
85
|
+
size({
|
|
86
|
+
apply({ elements, availableHeight }) {
|
|
87
|
+
elements.floating.style.maxHeight = `${availableHeight}px`
|
|
88
|
+
},
|
|
89
|
+
}),
|
|
90
|
+
],
|
|
91
|
+
whileElementsMounted: autoUpdate,
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<SelectContextProvider
|
|
96
|
+
isOpen={open}
|
|
97
|
+
onOpenChange={setIsOpen}
|
|
98
|
+
selected={selectedValue || null}
|
|
99
|
+
onSelectChange={setSelectedValue}
|
|
100
|
+
setFloating={refs.setFloating}
|
|
101
|
+
setReference={refs.setReference}
|
|
102
|
+
dir={dir}
|
|
103
|
+
floatingStyles={floatingStyles}
|
|
104
|
+
>
|
|
105
|
+
<RovingTabIndexRoot active={selectedValue} dir="vertical">
|
|
106
|
+
<div tabIndex={-1} {...props}>
|
|
107
|
+
{children}
|
|
108
|
+
</div>
|
|
109
|
+
</RovingTabIndexRoot>
|
|
110
|
+
</SelectContextProvider>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface SelectTriggerProps
|
|
115
|
+
extends ComponentPropsWithoutRef<typeof Primitive.button> {
|
|
116
|
+
placeholder?: string
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export const SelectTrigger = forwardRef<
|
|
120
|
+
React.ElementRef<typeof Primitive.button>,
|
|
121
|
+
SelectTriggerProps
|
|
122
|
+
>((props, ref) => {
|
|
123
|
+
const { getOrderedItems } = useRovingTabIndexContext("rovingTabIndex")
|
|
124
|
+
|
|
125
|
+
const { setReference, onOpenChange, isOpen, selected } =
|
|
126
|
+
useSelectContext(CONTEXT_NAME)
|
|
127
|
+
|
|
128
|
+
const displayText = selected || props.placeholder || ""
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<Primitive.button
|
|
132
|
+
ref={composeRefs((node) => setReference(node), ref)}
|
|
133
|
+
onClick={(e) => {
|
|
134
|
+
e.preventDefault()
|
|
135
|
+
onOpenChange(!isOpen)
|
|
136
|
+
props?.onClick?.(e)
|
|
137
|
+
}}
|
|
138
|
+
onKeyDown={(e) => {
|
|
139
|
+
setTimeout(() => {
|
|
140
|
+
if (e.key === "ArrowDown") {
|
|
141
|
+
onOpenChange(true)
|
|
142
|
+
getOrderedItems()[0]?.element.focus()
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
}}
|
|
146
|
+
{...props}
|
|
147
|
+
>
|
|
148
|
+
<span>{displayText}</span>
|
|
149
|
+
</Primitive.button>
|
|
150
|
+
)
|
|
151
|
+
})
|
|
152
|
+
export interface SelectPortalProps {
|
|
153
|
+
children: ReactNode
|
|
154
|
+
}
|
|
155
|
+
export const SelectPortal = ({ children, ...props }: SelectPortalProps) => {
|
|
156
|
+
const { isOpen, floatingStyles, setFloating } = useSelectContext(CONTEXT_NAME)
|
|
157
|
+
|
|
158
|
+
if (!isOpen) {
|
|
159
|
+
return null
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<Slot
|
|
164
|
+
style={floatingStyles}
|
|
165
|
+
ref={(node) => {
|
|
166
|
+
if (node instanceof HTMLElement) {
|
|
167
|
+
setFloating(node)
|
|
168
|
+
}
|
|
169
|
+
}}
|
|
170
|
+
{...props}
|
|
171
|
+
>
|
|
172
|
+
{children}
|
|
173
|
+
</Slot>
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export interface SelectItemProps
|
|
178
|
+
extends ComponentPropsWithoutRef<typeof Primitive.li> {
|
|
179
|
+
value: string
|
|
180
|
+
}
|
|
181
|
+
export const SelectItem = forwardRef<
|
|
182
|
+
React.ElementRef<typeof Primitive.li>,
|
|
183
|
+
SelectItemProps
|
|
184
|
+
>(({ children, value, asChild, ...props }, ref) => {
|
|
185
|
+
const { onSelectChange, onOpenChange, dir } = useSelectContext(CONTEXT_NAME)
|
|
186
|
+
const { getOrderedItems, getRovingProps } = useRovingTabIndex(value)
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<Primitive.li
|
|
190
|
+
{...getRovingProps<typeof Primitive.li>({
|
|
191
|
+
onClick: () => {
|
|
192
|
+
onSelectChange?.(value)
|
|
193
|
+
onOpenChange(false)
|
|
194
|
+
},
|
|
195
|
+
onKeyDown: (e) => {
|
|
196
|
+
if (e.key === "Enter") {
|
|
197
|
+
onSelectChange?.(value)
|
|
198
|
+
onOpenChange(false)
|
|
199
|
+
}
|
|
200
|
+
const items = getOrderedItems()
|
|
201
|
+
let nextItem
|
|
202
|
+
if (dir === "horizontal") {
|
|
203
|
+
if (e.key === "ArrowRight") {
|
|
204
|
+
nextItem = getNextFocusableId(items, value)
|
|
205
|
+
} else if (e.key === "ArrowLeft") {
|
|
206
|
+
nextItem = getPrevFocusableId(items, value)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (dir === "vertical") {
|
|
210
|
+
if (e.key === "ArrowDown") {
|
|
211
|
+
nextItem = getNextFocusableId(items, value)
|
|
212
|
+
} else if (e.key === "ArrowUp") {
|
|
213
|
+
nextItem = getPrevFocusableId(items, value)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
nextItem?.element.focus()
|
|
217
|
+
},
|
|
218
|
+
})}
|
|
219
|
+
ref={ref}
|
|
220
|
+
{...props}
|
|
221
|
+
>
|
|
222
|
+
{children}
|
|
223
|
+
</Primitive.li>
|
|
224
|
+
)
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
export const Select = Object.assign(SelectRoot, {
|
|
228
|
+
Trigger: SelectTrigger,
|
|
229
|
+
Portal: SelectPortal,
|
|
230
|
+
Item: SelectItem,
|
|
231
|
+
})
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { useState, type ComponentPropsWithoutRef } from "react"
|
|
2
|
+
import { useControlledState } from "../../hooks/useControllableState"
|
|
3
|
+
import { createContext } from "../../hooks/createContext"
|
|
4
|
+
//TODO: props type 관련 정리 및 event composing 필요
|
|
5
|
+
const [SliderProvider, useSliderContext] = createContext<{
|
|
6
|
+
min: number
|
|
7
|
+
max: number
|
|
8
|
+
value: number
|
|
9
|
+
onChange: (value: number) => void
|
|
10
|
+
isDragging: boolean
|
|
11
|
+
onPointerDown: (e: React.PointerEvent) => void
|
|
12
|
+
onPointerMove: (e: React.PointerEvent) => void
|
|
13
|
+
onPointerUp: (e: React.PointerEvent) => void
|
|
14
|
+
}>("Slider")
|
|
15
|
+
|
|
16
|
+
type SliderRootProps = {
|
|
17
|
+
min: number
|
|
18
|
+
max: number
|
|
19
|
+
value: number
|
|
20
|
+
defaultValue?: number
|
|
21
|
+
onChange: (value: number) => void
|
|
22
|
+
children: React.ReactNode
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const getPercentage = (value: number, min: number, max: number) =>
|
|
26
|
+
((value - min) / (max - min)) * 100
|
|
27
|
+
|
|
28
|
+
const SliderRoot = ({
|
|
29
|
+
min = 0,
|
|
30
|
+
max = 100,
|
|
31
|
+
value,
|
|
32
|
+
onChange,
|
|
33
|
+
defaultValue,
|
|
34
|
+
children,
|
|
35
|
+
...props
|
|
36
|
+
}: SliderRootProps) => {
|
|
37
|
+
const [isDragging, setIsDragging] = useState(false)
|
|
38
|
+
|
|
39
|
+
const [innerValue = 0, setValue] = useControlledState({
|
|
40
|
+
prop: value,
|
|
41
|
+
defaultProp: defaultValue || min,
|
|
42
|
+
onChange,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const handlePointerDown = (e: React.PointerEvent) => {
|
|
46
|
+
e.preventDefault()
|
|
47
|
+
setIsDragging(true)
|
|
48
|
+
e.currentTarget.setPointerCapture(e.pointerId)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const handlePointerUp = (e: React.PointerEvent) => {
|
|
52
|
+
e.preventDefault()
|
|
53
|
+
setIsDragging(false)
|
|
54
|
+
e.currentTarget.releasePointerCapture(e.pointerId)
|
|
55
|
+
const { left, width } = e.currentTarget.getBoundingClientRect()
|
|
56
|
+
let percentage = (e.clientX - left) / width
|
|
57
|
+
percentage = Math.min(Math.max(percentage, 0), 1)
|
|
58
|
+
|
|
59
|
+
const newValue = min + percentage * (max - min)
|
|
60
|
+
setValue(newValue)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const handlePointerMove = (e: React.PointerEvent) => {
|
|
64
|
+
e.preventDefault()
|
|
65
|
+
if (!isDragging) return
|
|
66
|
+
|
|
67
|
+
const { left, width } = e.currentTarget.getBoundingClientRect()
|
|
68
|
+
let percentage = (e.clientX - left) / width
|
|
69
|
+
percentage = Math.min(Math.max(percentage, 0), 1)
|
|
70
|
+
const newValue = min + percentage * (max - min)
|
|
71
|
+
setValue(newValue)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<SliderProvider
|
|
76
|
+
min={min}
|
|
77
|
+
max={max}
|
|
78
|
+
value={innerValue}
|
|
79
|
+
onChange={setValue}
|
|
80
|
+
isDragging={isDragging}
|
|
81
|
+
onPointerDown={handlePointerDown}
|
|
82
|
+
onPointerMove={handlePointerMove}
|
|
83
|
+
onPointerUp={handlePointerUp}
|
|
84
|
+
>
|
|
85
|
+
<div
|
|
86
|
+
onPointerDown={handlePointerDown}
|
|
87
|
+
onPointerUp={handlePointerUp}
|
|
88
|
+
onPointerMove={handlePointerMove}
|
|
89
|
+
{...props}
|
|
90
|
+
>
|
|
91
|
+
{children}
|
|
92
|
+
</div>
|
|
93
|
+
</SliderProvider>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const SliderTrack = ({
|
|
98
|
+
children,
|
|
99
|
+
...props
|
|
100
|
+
}: {
|
|
101
|
+
children?: React.ReactNode
|
|
102
|
+
}) => {
|
|
103
|
+
return <span {...props}>{children}</span>
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const SliderRange = ({ ...props }: ComponentPropsWithoutRef<"span">) => {
|
|
107
|
+
const { min, max, value } = useSliderContext()
|
|
108
|
+
const percentage = getPercentage(value, min, max)
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<span
|
|
112
|
+
{...props}
|
|
113
|
+
style={{
|
|
114
|
+
width: `${percentage}%`,
|
|
115
|
+
}}
|
|
116
|
+
/>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const SliderThumb = ({ ...props }: ComponentPropsWithoutRef<"span">) => {
|
|
121
|
+
const { min, max, value } = useSliderContext()
|
|
122
|
+
const percentage = getPercentage(value, min, max)
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<span
|
|
126
|
+
{...props}
|
|
127
|
+
style={{
|
|
128
|
+
position: "absolute",
|
|
129
|
+
left: `${percentage}%`,
|
|
130
|
+
transform: "translateX(-50%)",
|
|
131
|
+
}}
|
|
132
|
+
/>
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export const Slider = Object.assign(SliderRoot, {
|
|
137
|
+
Root: SliderRoot,
|
|
138
|
+
Track: SliderTrack,
|
|
139
|
+
Range: SliderRange,
|
|
140
|
+
Thumb: SliderThumb,
|
|
141
|
+
})
|
|
@@ -1,42 +1,29 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Assign } from "../../types"
|
|
2
|
+
import { slider } from "@styled-system/recipes"
|
|
3
|
+
import type { ComponentProps, HTMLStyledProps } from "styled-system/types"
|
|
4
|
+
import { createStyleContext } from "../../utils/createStyleContext"
|
|
5
|
+
import { Slider } from "."
|
|
2
6
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
backgroundColor: "grey_300",
|
|
27
|
-
borderRadius: "rounded",
|
|
28
|
-
border: "none",
|
|
29
|
-
cursor: "pointer",
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
variants: {
|
|
33
|
-
orientation: {
|
|
34
|
-
horizontal: {
|
|
35
|
-
writingMode: "horizontal-tb",
|
|
36
|
-
},
|
|
37
|
-
vertical: {
|
|
38
|
-
writingMode: "vertical-rl",
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
})
|
|
7
|
+
const { withProvider, withContext } = createStyleContext(slider)
|
|
8
|
+
|
|
9
|
+
export const Root = withProvider<
|
|
10
|
+
HTMLDivElement,
|
|
11
|
+
Assign<HTMLStyledProps<"div">, ComponentProps<typeof Slider.Root>>
|
|
12
|
+
>(Slider.Root, "root")
|
|
13
|
+
|
|
14
|
+
export const Track = withContext<
|
|
15
|
+
HTMLSpanElement,
|
|
16
|
+
Assign<HTMLStyledProps<"span">, ComponentProps<typeof Slider.Track>>
|
|
17
|
+
>(Slider.Track, "track")
|
|
18
|
+
|
|
19
|
+
export const Range = withContext<
|
|
20
|
+
HTMLSpanElement,
|
|
21
|
+
Assign<HTMLStyledProps<"span">, ComponentProps<typeof Slider.Range>>
|
|
22
|
+
>(Slider.Range, "range")
|
|
23
|
+
|
|
24
|
+
export const Thumb = withContext<
|
|
25
|
+
HTMLSpanElement,
|
|
26
|
+
Assign<HTMLStyledProps<"span">, ComponentProps<typeof Slider.Thumb>>
|
|
27
|
+
>(Slider.Thumb, "thumb")
|
|
28
|
+
|
|
29
|
+
export const StyledSlider = Object.assign(Root, { Track, Range, Thumb })
|
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
import { forwardRef, useId, type ReactNode } from "react"
|
|
1
|
+
import { forwardRef, useId, type ReactNode, type ForwardedRef } from "react"
|
|
2
2
|
import { useControlledState } from "../../hooks/useControllableState"
|
|
3
3
|
import { Slot } from "@radix-ui/react-slot"
|
|
4
4
|
import { TabProvider } from "./useTabContext"
|
|
5
5
|
|
|
6
|
-
export
|
|
7
|
-
children
|
|
6
|
+
export type TabProps = {
|
|
7
|
+
children?: ReactNode
|
|
8
8
|
selected?: string
|
|
9
9
|
defaultValue?: string
|
|
10
10
|
onSelect?: (value: string) => void
|
|
11
11
|
asChild?: boolean
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
export const Tab = forwardRef(
|
|
15
|
+
(
|
|
16
|
+
{ children, selected, defaultValue, onSelect, asChild, ...props }: TabProps,
|
|
17
|
+
ref: ForwardedRef<HTMLDivElement>,
|
|
18
|
+
) => {
|
|
17
19
|
const Element = asChild ? Slot : "div"
|
|
18
20
|
|
|
19
21
|
const [value, setValue] = useControlledState({
|
|
@@ -28,7 +30,9 @@ export const Tab = forwardRef<any, TabProps>(
|
|
|
28
30
|
|
|
29
31
|
return (
|
|
30
32
|
<TabProvider selected={value} onSelect={onSelectItem} tabId={useId()}>
|
|
31
|
-
<Element ref={ref}
|
|
33
|
+
<Element ref={ref} {...props}>
|
|
34
|
+
{children}
|
|
35
|
+
</Element>
|
|
32
36
|
</TabProvider>
|
|
33
37
|
)
|
|
34
38
|
},
|
|
@@ -2,7 +2,7 @@ import { type ReactNode } from "react"
|
|
|
2
2
|
import { useTabContext } from "./useTabContext"
|
|
3
3
|
import { motion } from "framer-motion"
|
|
4
4
|
|
|
5
|
-
interface TabContentProps {
|
|
5
|
+
export interface TabContentProps {
|
|
6
6
|
children: ReactNode
|
|
7
7
|
value: string
|
|
8
8
|
}
|
|
@@ -35,7 +35,8 @@ const cardVariant = {
|
|
|
35
35
|
},
|
|
36
36
|
},
|
|
37
37
|
}
|
|
38
|
-
|
|
38
|
+
|
|
39
|
+
export const TabContent = ({ children, value, ...props }: TabContentProps) => {
|
|
39
40
|
const { selected, tabId } = useTabContext("tab")
|
|
40
41
|
|
|
41
42
|
const isSelected = selected === value
|
|
@@ -51,6 +52,7 @@ export const TabContent = ({ children, value }: TabContentProps) => {
|
|
|
51
52
|
variants={tabContentVariant}
|
|
52
53
|
animate={isSelected ? "active" : "inactive"}
|
|
53
54
|
initial="inactive"
|
|
55
|
+
{...props}
|
|
54
56
|
>
|
|
55
57
|
<motion.div key={value} variants={cardVariant}>
|
|
56
58
|
{isSelected && children}
|
|
@@ -6,9 +6,8 @@ import {
|
|
|
6
6
|
} from "react"
|
|
7
7
|
import { useTabContext } from "./useTabContext"
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
import isHotkey from "is-hotkey"
|
|
9
|
+
import { RovingTabIndexRoot, useRovingTabIndex } from "../RovingIndex"
|
|
10
|
+
|
|
12
11
|
import { Slot } from "@radix-ui/react-slot"
|
|
13
12
|
import { composeRefs } from "../../hooks/useComposedRefs"
|
|
14
13
|
import {
|
|
@@ -16,44 +15,44 @@ import {
|
|
|
16
15
|
getPrevFocusableId,
|
|
17
16
|
} from "../../utils/getFocusableId"
|
|
18
17
|
|
|
19
|
-
interface TabListProps {
|
|
18
|
+
export interface TabListProps {
|
|
20
19
|
children: ReactNode
|
|
21
20
|
className?: string
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
const setIndicatorStyle = (target: HTMLElement) => {
|
|
25
24
|
const targetRect = target.getBoundingClientRect()
|
|
26
|
-
const
|
|
27
|
-
|
|
25
|
+
const parent = target.parentElement //TabList
|
|
26
|
+
const parentRect = parent?.getBoundingClientRect()
|
|
27
|
+
|
|
28
|
+
if (!targetRect || !parentRect || !parent) {
|
|
28
29
|
return
|
|
29
30
|
}
|
|
31
|
+
const scrollLeft = parent.scrollLeft //overflow일때 고려
|
|
32
|
+
|
|
33
|
+
const indicatorLeft = targetRect.left - parentRect.left + scrollLeft
|
|
34
|
+
|
|
30
35
|
document.documentElement.style.setProperty(
|
|
31
36
|
"--indicator-left",
|
|
32
|
-
`${
|
|
37
|
+
`${indicatorLeft}px`,
|
|
33
38
|
)
|
|
34
39
|
document.documentElement.style.setProperty(
|
|
35
40
|
"--indicator-width",
|
|
36
|
-
`${
|
|
41
|
+
`${targetRect.width}px`,
|
|
37
42
|
)
|
|
38
43
|
}
|
|
39
44
|
|
|
40
|
-
export const TabList = ({ children,
|
|
45
|
+
export const TabList = ({ children, ...props }: TabListProps) => {
|
|
41
46
|
const { selected } = useTabContext("tab")
|
|
42
47
|
return (
|
|
43
|
-
<RovingTabIndexRoot
|
|
44
|
-
<div
|
|
45
|
-
role="tablist"
|
|
46
|
-
className={cx(
|
|
47
|
-
className,
|
|
48
|
-
css({ position: "relative", display: "flex" }),
|
|
49
|
-
)}
|
|
50
|
-
>
|
|
48
|
+
<RovingTabIndexRoot active={selected}>
|
|
49
|
+
<div role="tablist" {...props}>
|
|
51
50
|
{children}
|
|
52
51
|
</div>
|
|
53
52
|
</RovingTabIndexRoot>
|
|
54
53
|
)
|
|
55
54
|
}
|
|
56
|
-
|
|
55
|
+
export interface TabItemProps extends ComponentPropsWithRef<"button"> {
|
|
57
56
|
value: string
|
|
58
57
|
}
|
|
59
58
|
|
|
@@ -71,9 +70,9 @@ export const RovingItem = ({
|
|
|
71
70
|
onKeyDown: (e) => {
|
|
72
71
|
const items = getOrderedItems()
|
|
73
72
|
let nextItem
|
|
74
|
-
if (
|
|
73
|
+
if (e.key === "ArrowRight") {
|
|
75
74
|
nextItem = getNextFocusableId(items, value)
|
|
76
|
-
} else if (
|
|
75
|
+
} else if (e.key === "ArrowLeft") {
|
|
77
76
|
nextItem = getPrevFocusableId(items, value)
|
|
78
77
|
}
|
|
79
78
|
nextItem?.element.focus()
|
|
@@ -86,7 +85,7 @@ export const RovingItem = ({
|
|
|
86
85
|
}
|
|
87
86
|
|
|
88
87
|
export const TabItem = forwardRef<HTMLButtonElement, TabItemProps>(
|
|
89
|
-
({ children,
|
|
88
|
+
({ children, value, ...props }: TabItemProps, forwardRef) => {
|
|
90
89
|
const { selected, onSelect, tabId } = useTabContext("tab")
|
|
91
90
|
const isSelected = selected === value
|
|
92
91
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
import { Tab, TabProps } from "./Tab"
|
|
3
|
+
import { TabContent, TabContentProps } from "./TabContent"
|
|
4
|
+
import { TabIndicator, TabIndicatorProps } from "./TabIndicator"
|
|
5
|
+
import { TabList, TabListProps } from "./TabList"
|
|
6
|
+
import { TabItem, TabItemProps } from "./TabList"
|
|
7
|
+
import { type TabsVariantProps, tabs } from "styled-system/recipes"
|
|
8
|
+
import type { Assign, HTMLStyledProps } from "styled-system/types"
|
|
9
|
+
import { createStyleContext } from "../../utils/createStyleContext"
|
|
10
|
+
|
|
11
|
+
const { withProvider, withContext } = createStyleContext(tabs)
|
|
12
|
+
|
|
13
|
+
const Root = withProvider<
|
|
14
|
+
HTMLDivElement,
|
|
15
|
+
Assign<Assign<HTMLStyledProps<"div">, TabProps>, TabsVariantProps>
|
|
16
|
+
>(Tab, "root")
|
|
17
|
+
|
|
18
|
+
const Content = withContext<
|
|
19
|
+
HTMLDivElement,
|
|
20
|
+
Assign<HTMLStyledProps<"div">, TabContentProps>
|
|
21
|
+
>(TabContent, "content")
|
|
22
|
+
|
|
23
|
+
const Indicator = withContext<
|
|
24
|
+
HTMLDivElement,
|
|
25
|
+
Assign<HTMLStyledProps<"div">, TabIndicatorProps>
|
|
26
|
+
>(TabIndicator, "indicator")
|
|
27
|
+
|
|
28
|
+
const List = withContext<
|
|
29
|
+
HTMLDivElement,
|
|
30
|
+
Assign<HTMLStyledProps<"div">, TabListProps>
|
|
31
|
+
>(TabList, "list")
|
|
32
|
+
|
|
33
|
+
const Item = withContext<
|
|
34
|
+
HTMLButtonElement,
|
|
35
|
+
Assign<HTMLStyledProps<"button">, TabItemProps>
|
|
36
|
+
>(TabItem, "item")
|
|
37
|
+
|
|
38
|
+
export const Tabs = Object.assign(Root, {
|
|
39
|
+
Content,
|
|
40
|
+
List,
|
|
41
|
+
Item,
|
|
42
|
+
Indicator,
|
|
43
|
+
})
|
|
@@ -101,7 +101,6 @@ export const RovingTabIndexRoot = <T extends ElementType>({
|
|
|
101
101
|
{...{ [ROOT_SELECTOR]: true }}
|
|
102
102
|
// tabIndex={isShiftTabbing ? -1 : 0}
|
|
103
103
|
onFocus={(e) => {
|
|
104
|
-
console.log("parent focus")
|
|
105
104
|
if (e.target !== e.currentTarget) return
|
|
106
105
|
if (isShiftTabbing) return
|
|
107
106
|
const orderedItems = getOrderedItems()
|
package/dist/components/index.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
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();
|