Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
fd1783d
fix(react,components): support React 18 peers and drop React 19-only …
owjs3901 Oct 2, 2026
a641f0b
chore: satisfy clippy assert_is_empty lint on Rust 1.99
owjs3901 Oct 2, 2026
b6c0d89
fix(components): forward refs from the UI kit form components
owjs3901 Oct 2, 2026
fdd90fe
fix(components): make RadioGroup an accessible radiogroup that keeps …
owjs3901 Oct 2, 2026
465adca
fix(components): label Checkbox with the id it is given and pin test ids
owjs3901 Oct 2, 2026
c274eab
fix(components): make Toggle an accessible switch
owjs3901 Oct 2, 2026
3d87be9
fix(components): make Select an accessible listbox with keyboard support
owjs3901 Oct 2, 2026
00dd003
test(components): pin Checkbox and nested Select ids so snapshots ign…
owjs3901 Oct 2, 2026
d6453d8
fix(components): provide Select and Stepper context as React 18 does
owjs3901 Oct 3, 2026
c1273ad
Merge remote-tracking branch 'origin/fix/ui-kit-forward-ref' into fix…
owjs3901 Oct 3, 2026
f290cc8
Merge remote-tracking branch 'origin/fix/radio-group-a11y' into fix/u…
owjs3901 Oct 3, 2026
4aa4785
Merge remote-tracking branch 'origin/fix/toggle-switch' into fix/ui-k…
owjs3901 Oct 3, 2026
2d36d67
Merge remote-tracking branch 'origin/fix/select-a11y' into fix/ui-kit…
owjs3901 Oct 3, 2026
48fc98f
fix(components): make the UI kit, theme helpers and reset-css behave …
owjs3901 Oct 3, 2026
714539d
fix(components): call the Stepper setter as a member so a minified na…
owjs3901 Oct 3, 2026
d890376
fix(components): name Select options, container, trigger and confirm …
owjs3901 Oct 3, 2026
19437d1
Merge main b711ed1f into fix/react-18-support
owjs3901 Oct 8, 2026
b878357
Merge main 9a953607 into fix/react-18-support
owjs3901 Oct 8, 2026
2b5cc38
Merge main db631c6c into fix/react-18-support
owjs3901 Oct 8, 2026
820e7c1
Merge main 11790bef into fix/react-18-support
owjs3901 Oct 8, 2026
141596c
Merge main b711ed1f into fix/ui-kit-forward-ref
owjs3901 Oct 8, 2026
65dd129
Merge main 9a953607 into fix/ui-kit-forward-ref
owjs3901 Oct 8, 2026
cf33ef6
Merge main db631c6c into fix/ui-kit-forward-ref
owjs3901 Oct 8, 2026
583b735
Merge main 11790bef into fix/ui-kit-forward-ref
owjs3901 Oct 8, 2026
dabb7d4
Merge main a935315c prerequisite into fix/radio-group-a11y
owjs3901 Oct 8, 2026
26866af
Merge main b711ed1f into fix/radio-group-a11y
owjs3901 Oct 8, 2026
6e29afb
Merge main 9a953607 into fix/radio-group-a11y
owjs3901 Oct 8, 2026
b7c436b
Merge main db631c6c into fix/radio-group-a11y
owjs3901 Oct 8, 2026
b17ed50
Merge main 11790bef into fix/radio-group-a11y
owjs3901 Oct 8, 2026
ca82b67
Merge main a935315c prerequisite into fix/toggle-switch
owjs3901 Oct 8, 2026
5c88b4e
Merge main b711ed1f into fix/toggle-switch
owjs3901 Oct 8, 2026
594861a
Merge main 9a953607 into fix/toggle-switch
owjs3901 Oct 8, 2026
261eef3
Merge main db631c6c into fix/toggle-switch
owjs3901 Oct 8, 2026
a0f0973
Merge main 11790bef into fix/toggle-switch
owjs3901 Oct 8, 2026
4c7b237
Merge main a935315c prerequisite into fix/select-a11y
owjs3901 Oct 8, 2026
79a4a42
Merge main b711ed1f into fix/select-a11y
owjs3901 Oct 8, 2026
cb59592
Merge main 9a953607 into fix/select-a11y
owjs3901 Oct 8, 2026
79b6057
Merge main db631c6c into fix/select-a11y
owjs3901 Oct 8, 2026
03fcadd
Merge main 11790bef into fix/select-a11y
owjs3901 Oct 8, 2026
c7e4ac1
Merge updated #720 into fix/ui-kit-behavior
owjs3901 Oct 8, 2026
41843f9
Merge updated #721 into fix/ui-kit-behavior
owjs3901 Oct 8, 2026
d9b73a7
Merge updated #723 into fix/ui-kit-behavior
owjs3901 Oct 8, 2026
ebada76
Merge updated #724 into fix/ui-kit-behavior
owjs3901 Oct 8, 2026
a30fbb7
Merge updated #725 into fix/ui-kit-behavior
owjs3901 Oct 8, 2026
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
7 changes: 7 additions & 0 deletions .changepacks/changepack_log_radio_group_a11y.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"changes": {
"packages/components/package.json": "Patch"
},
"note": "RadioGroup is a radiogroup with an optional label, its radios share a name (generated when not given) so arrow keys move between them, and it keeps the typed values of its options: false, 0 and '' stay selectable and onChange receives the option's own value rather than a string. Button-variant radios are visually hidden instead of display:none, so they stay focusable and show a focus ring, and the default radio's focus outline is valid CSS and shown on :focus-visible. Checkbox uses an id it is given for its labels as well as its input, where the labels used to point at a generated id",
"date": "2026-10-01T00:00:00.000Z"
}
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"
}
7 changes: 7 additions & 0 deletions .changepacks/changepack_log_select_a11y.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"changes": {
"packages/components/package.json": "Patch"
},
"note": "Select follows the listbox pattern: the trigger has aria-haspopup, aria-controls and opens with ArrowDown/ArrowUp; the popup is a listbox (aria-multiselectable for checkbox selects) whose options carry role=option, aria-selected and aria-disabled; opening focuses the selected or first option, arrows/Home/End move between enabled options, Enter/Space choose one, and Escape or choosing closes the popup and returns focus to the trigger. An outside click on a controlled select now calls onOpenChange(false) instead of changing hidden internal state",
"date": "2026-10-01T00:00:00.000Z"
}
7 changes: 7 additions & 0 deletions .changepacks/changepack_log_toggle_switch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"changes": {
"packages/components/package.json": "Patch"
},
"note": "Toggle is an accessible switch: role=switch with aria-checked, focusable with a visible focus ring, toggled by Space and Enter, labelled through aria-label or aria-labelledby, and submitted under the name it is given; when disabled it leaves the tab order, ignores keys and its hidden input is disabled",
"date": "2026-10-01T00:00:00.000Z"
}
9 changes: 9 additions & 0 deletions .changepacks/changepack_log_ui_kit_behavior.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"changes": {
"packages/components/package.json": "Patch",
"packages/react/package.json": "Patch",
"packages/reset-css/package.json": "Patch"
},
"note": "Components behave for keyboard, screen readers and both React 18 and 19: Button, Input and Textarea no longer force generic aria-labels (the visible text or an associated label names them), Input and Textarea mark errors with aria-invalid and aria-describedby pointing at the message, a loading Button sets aria-busy, is disabled by default (disableWhileLoading) and announces loadingLabel, Input clear dispatches a real change event instead of a fabricated one, color customization variables sit on the container so every part sees them and every public color key matches its CSS custom property (new keys inputDisabledBackground, inputDisabledText, inputPlaceholder, primaryBackground, Textarea disabledBackground and disabledText), Select positions against the viewport and clears the opposite inset, SelectTrigger asChild composes the child onClick, onKeyDown and ref with its own, Stepper counts without a controlled value, keeps its bound and disabled safeguards and renders its text mode as an output element. Select no longer forces aria-labels over its trigger, options, confirm button and container: an option is named by its content, the listbox by its trigger (aria-labelledby) or by what the caller passes. ThemeScript takes nonce and other script props and never aborts on blocked storage or missing matchMedia, follows the system while no theme was chosen and themes saved in other tabs; getTheme, setTheme and initTheme are safe on the server and initTheme returns a function that stops following. reset-css clips accessible-hidden content as sanitize.css does. No new build errors.",
"date": "2026-10-01T00:00:00.000Z"
}
7 changes: 7 additions & 0 deletions .changepacks/changepack_log_ui_kit_forward_ref.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"changes": {
"packages/components/package.json": "Patch"
},
"note": "Button, Checkbox, Input, Radio and Textarea forward their ref to the button, input or textarea they render, so a ref reaches the form element on React 18 as well as React 19",
"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"
}
}
26 changes: 26 additions & 0 deletions packages/components/src/__tests__/forward-ref.browser.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { describe, expect, it } from 'bun:test'
import { render } from 'bun-test-env-dom'
import { createRef } from 'react'

import { Button, Input, Radio, Textarea } from '../index'

describe('forwardRef', () => {
it('hands each component ref to its form element', () => {
const button = createRef<HTMLButtonElement>()
const input = createRef<HTMLInputElement>()
const radio = createRef<HTMLInputElement>()
const textarea = createRef<HTMLTextAreaElement>()
render(
<>
<Button ref={button}>b</Button>
<Input ref={input} />
<Radio ref={radio}>r</Radio>
<Textarea ref={textarea} />
</>,
)
expect(button.current?.tagName).toBe('BUTTON')
expect(input.current?.tagName).toBe('INPUT')
expect(radio.current?.type).toBe('radio')
expect(textarea.current?.tagName).toBe('TEXTAREA')
})
})
58 changes: 29 additions & 29 deletions packages/components/src/__tests__/index.browser.test.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
import { describe, expect, it } from 'bun:test'
describe('export', () => {
it('should export components', async () => {
const index = await import('../index')
expect({ ...index }).toEqual({
Button: expect.any(Function),
Input: expect.any(Function),
Textarea: expect.any(Function),
Stepper: expect.any(Function),
Select: expect.any(Function),
Radio: expect.any(Function),
RadioGroup: expect.any(Function),
SelectContainer: expect.any(Function),
SelectDivider: expect.any(Function),
SelectOption: expect.any(Function),
SelectTrigger: expect.any(Function),
StepperContainer: expect.any(Function),
StepperDecreaseButton: expect.any(Function),
StepperIncreaseButton: expect.any(Function),
StepperInput: expect.any(Function),
useStepper: expect.any(Function),
SelectContext: expect.any(Object),
useSelect: expect.any(Function),
Toggle: expect.any(Function),
Checkbox: expect.any(Function),
})
})
})
import { describe, expect, it } from 'bun:test'

describe('export', () => {
it('should export components', async () => {
const index = await import('../index')
expect({ ...index }).toEqual({
Button: expect.any(Object),
Input: expect.any(Object),
Textarea: expect.any(Object),
Stepper: expect.any(Function),
Select: expect.any(Function),
Radio: expect.any(Object),
RadioGroup: expect.any(Function),
SelectContainer: expect.any(Function),
SelectDivider: expect.any(Function),
SelectOption: expect.any(Function),
SelectTrigger: expect.any(Object),
StepperContainer: expect.any(Function),
StepperDecreaseButton: expect.any(Function),
StepperIncreaseButton: expect.any(Function),
StepperInput: expect.any(Function),
useStepper: expect.any(Function),
SelectContext: expect.any(Object),
useSelect: expect.any(Function),
Toggle: expect.any(Function),
Checkbox: expect.any(Object),
})
})
})
Loading
Loading