Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changepacks/changepack_log_react_18_support.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"changes": {
"packages/react/package.json": "Patch",
"packages/components/package.json": "Patch"
},
"note": "@devup-ui/react and @devup-ui/components take React as a peer of ^18 || ^19 instead of `*` and no longer depend on React 19 themselves, so a React 18 app no longer gets a second React. Select and Stepper read their context with useContext instead of React 19's use() and provide it with Context.Provider instead of React 19's <Context value> (which crashes React 18), and Select repositions its container from an effect instead of a callback ref returning cleanup, which React 18 ignores",
"date": "2026-10-01T00:00:00.000Z"
}
8 changes: 4 additions & 4 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
"types": "./dist/index.d.ts",
"dependencies": {
"@devup-ui/react": "workspace:^",
"react": "^19.3.0",
"clsx": "^2.1"
},
"devDependencies": {
Expand All @@ -59,10 +58,11 @@
"typescript": "^7.0.2",
"@typescript/typescript6": "^6.0",
"vite": "^8.3",
"vite-plugin-dts": "^5.1"
"vite-plugin-dts": "^5.1",
"react": "^19.3.0"
},
"peerDependencies": {
"@devup-ui/react": "workspace:^",
"react": "*"
"react": "^18 || ^19"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,8 @@ describe('Select', () => {
// offsetWidth > 1024px
Object.defineProperty(selectContainer, 'offsetWidth', { value: 1100 })

// rerender
// the container repositions when the window resizes
window.dispatchEvent(new Event('resize'))
rerender(<Select>{children}</Select>)

expect(container).toMatchSnapshot()
Expand Down
97 changes: 51 additions & 46 deletions packages/components/src/components/Select/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@
}

return (
<SelectContext
<SelectContext.Provider

Check warning on line 113 in packages/components/src/components/Select/index.tsx

View workflow job for this annotation

GitHub Actions / publish

In React 19, you can render '<Context>' as a provider instead of '<Context.Provider>'
value={{
open: openProp ?? open,
setOpen: handleOpenChange,
Expand Down Expand Up @@ -163,7 +163,7 @@
children
)}
</Box>
</SelectContext>
</SelectContext.Provider>
)
}

Expand Down Expand Up @@ -230,54 +230,59 @@
...props
}: SelectContainerProps) {
const { open, setOpen, type, ref } = useSelect()
const containerRef = useRef<HTMLDivElement>(null)

if (!open) return null
return (
<VStack
ref={(el) => {
if (!ref.current || !el) return
const combobox = ref.current

// 요소가 움직일 때마다(스크롤, 리사이즈 등) 위치를 갱신하도록 이벤트를 등록합니다.
const updatePosition = () => {
const {
height,
x: comboboxX,
y: comboboxY,
top,
left,
} = combobox.getBoundingClientRect()

const isOverflowBottom =
el.offsetHeight + top + window.scrollY + height + y >
document.documentElement.scrollHeight

const isOverflowRight =
el.offsetWidth + left + window.scrollX + x >
document.documentElement.scrollWidth

if (isOverflowBottom)
el.style.bottom = `${window.innerHeight - comboboxY + 10}px`
else el.style.top = `${comboboxY + height + 10 + y}px`

if (isOverflowRight)
el.style.left = `${Math.max(comboboxX - el.offsetWidth + combobox.offsetWidth, 0) + x}px`
else el.style.left = `${comboboxX + x}px`
}
// An effect rather than a callback ref returning cleanup, which React 18 ignores
useEffect(() => {
const el = containerRef.current
if (!open || !ref.current || !el) return
const combobox = ref.current

// 최초 위치 설정
updatePosition()
// 요소가 움직일 때마다(스크롤, 리사이즈 등) 위치를 갱신하도록 이벤트를 등록합니다.
const updatePosition = () => {
const {
height,
x: comboboxX,
y: comboboxY,
top,
left,
} = combobox.getBoundingClientRect()

// 스크롤, 리사이즈, DOM 변경 등 요소 위치가 변할 수 있는 이벤트에 리스너 등록
window.addEventListener('scroll', updatePosition, true)
window.addEventListener('resize', updatePosition)
const isOverflowBottom =
el.offsetHeight + top + window.scrollY + height + y >
document.documentElement.scrollHeight

// 컴포넌트 언마운트 시 이벤트 해제
return () => {
window.removeEventListener('scroll', updatePosition, true)
window.removeEventListener('resize', updatePosition)
}
}}
const isOverflowRight =
el.offsetWidth + left + window.scrollX + x >
document.documentElement.scrollWidth

if (isOverflowBottom)
el.style.bottom = `${window.innerHeight - comboboxY + 10}px`
else el.style.top = `${comboboxY + height + 10 + y}px`

if (isOverflowRight)
el.style.left = `${Math.max(comboboxX - el.offsetWidth + combobox.offsetWidth, 0) + x}px`
else el.style.left = `${comboboxX + x}px`
}

// 최초 위치 설정
updatePosition()

// 스크롤, 리사이즈, DOM 변경 등 요소 위치가 변할 수 있는 이벤트에 리스너 등록
window.addEventListener('scroll', updatePosition, true)
window.addEventListener('resize', updatePosition)

// 컴포넌트 언마운트 시 이벤트 해제
return () => {
window.removeEventListener('scroll', updatePosition, true)
window.removeEventListener('resize', updatePosition)
}
}, [open, ref, x, y])

if (!open) return null
return (
<VStack
ref={containerRef}
aria-label="Select container"
bg="var(--inputBg, light-dark(#FFF,#2E2E2E))"
border="1px solid var(--border, light-dark(#E4E4E4,#434343))"
Expand Down
8 changes: 4 additions & 4 deletions packages/components/src/components/Stepper/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { css, Flex } from '@devup-ui/react'
import clsx from 'clsx'
import { ComponentProps, createContext, use, useState } from 'react'
import { ComponentProps, createContext, useContext, useState } from 'react'

import { Button } from '../Button'
import { Input } from '../Input'
Expand All @@ -20,7 +20,7 @@
const StepperContext = createContext<StepperContextType | null>(null)

export const useStepper = () => {
const context = use(StepperContext)
const context = useContext(StepperContext)

Check warning on line 23 in packages/components/src/components/Stepper/index.tsx

View workflow job for this annotation

GitHub Actions / publish

In React 19, 'use' is preferred over 'useContext' because it is more flexible
if (!context) {
throw new Error('useStepper must be used within a StepperProvider')
}
Expand Down Expand Up @@ -58,7 +58,7 @@
}

return (
<StepperContext
<StepperContext.Provider

Check warning on line 61 in packages/components/src/components/Stepper/index.tsx

View workflow job for this annotation

GitHub Actions / publish

In React 19, you can render '<Context>' as a provider instead of '<Context.Provider>'
value={{
value: valueProp ?? value,
setValue: handleChange,
Expand All @@ -68,7 +68,7 @@
}}
>
{children}
</StepperContext>
</StepperContext.Provider>
)
}

Expand Down
4 changes: 2 additions & 2 deletions packages/components/src/contexts/useSelect.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
'use client'
import { createContext, use } from 'react'
import { createContext, useContext } from 'react'

type SelectType = 'default' | 'radio' | 'checkbox'
type SelectValue<T extends SelectType> = T extends 'radio' ? string : string[]
Expand All @@ -14,7 +14,7 @@
} | null>(null)

export const useSelect = () => {
const context = use(SelectContext)
const context = useContext(SelectContext)

Check warning on line 17 in packages/components/src/contexts/useSelect.ts

View workflow job for this annotation

GitHub Actions / publish

In React 19, 'use' is preferred over 'useContext' because it is more flexible
if (!context) {
throw new Error('useSelect must be used within a Select')
}
Expand Down
6 changes: 3 additions & 3 deletions packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@
],
"types": "./dist/index.d.ts",
"dependencies": {
"react": "^19.3",
"csstype-extra": "latest"
},
"devDependencies": {
Expand All @@ -76,10 +75,11 @@
"typescript": "^7.0.2",
"@typescript/typescript6": "^6.0",
"@types/react": "^19.3",
"@types/node": "^26.6"
"@types/node": "^26.6",
"react": "^19.3"
},
"peerDependencies": {
"react": "*",
"react": "^18 || ^19",
"csstype-extra": "*"
}
}
Loading