From b6c0d89d0813b2a92c804ff4965c6ba8d709a068 Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Fri, 2 Oct 2026 15:36:14 +0900 Subject: [PATCH] fix(components): forward refs from the UI kit form components Refs #691 Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .../changepack_log_ui_kit_forward_ref.json | 7 + bindings/devup-ui-wasm/src/lib.rs | 8 +- libs/css/src/theme_tokens.rs | 2 +- libs/extractor/src/lib.rs | 8 +- libs/extractor/src/tailwind.rs | 4 +- .../__tests__/forward-ref.browser.test.tsx | 26 + .../src/__tests__/index.browser.test.ts | 10 +- .../src/components/Button/index.tsx | 467 +++++++++--------- .../Checkbox/__tests__/index.browser.test.tsx | 7 + .../src/components/Checkbox/index.tsx | 259 +++++----- .../components/src/components/Input/index.tsx | 42 +- .../components/src/components/Radio/index.tsx | 36 +- .../src/components/Textarea/index.tsx | 204 ++++---- 13 files changed, 575 insertions(+), 505 deletions(-) create mode 100644 .changepacks/changepack_log_ui_kit_forward_ref.json create mode 100644 packages/components/src/__tests__/forward-ref.browser.test.tsx diff --git a/.changepacks/changepack_log_ui_kit_forward_ref.json b/.changepacks/changepack_log_ui_kit_forward_ref.json new file mode 100644 index 000000000..883e0e61b --- /dev/null +++ b/.changepacks/changepack_log_ui_kit_forward_ref.json @@ -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" +} diff --git a/bindings/devup-ui-wasm/src/lib.rs b/bindings/devup-ui-wasm/src/lib.rs index 4560410f3..0a2347ac2 100644 --- a/bindings/devup-ui-wasm/src/lib.rs +++ b/bindings/devup-ui-wasm/src/lib.rs @@ -1551,7 +1551,7 @@ mod tests { ); // Test getters - assert!(!output.code().is_empty()); + assert_ne!(output.code(), ""); assert_eq!(output.css_file(), Some("devup-ui-0.css".to_string())); assert_eq!(output.map(), Some("//# sourceMappingURL=test".to_string())); assert!(output.css().is_some()); @@ -1951,7 +1951,7 @@ mod tests { assert!(result.is_ok()); let output = result.unwrap(); - assert!(!output.code().is_empty()); + assert_ne!(output.code(), ""); assert!(output.map().is_some()); } @@ -1975,7 +1975,7 @@ mod tests { assert!(result.is_ok()); let output = result.unwrap(); - assert!(!output.code().is_empty()); + assert_ne!(output.code(), ""); assert!(output.map().is_none()); } @@ -2000,7 +2000,7 @@ mod tests { assert!(result.is_err()); if let Err(error) = result { - assert!(!error.is_empty()); + assert_ne!(error, ""); } } diff --git a/libs/css/src/theme_tokens.rs b/libs/css/src/theme_tokens.rs index d8d8fb021..3b2346aed 100644 --- a/libs/css/src/theme_tokens.rs +++ b/libs/css/src/theme_tokens.rs @@ -119,7 +119,7 @@ mod tests { set_typography_keys(vec!["body".to_string(), "title".to_string()]); assert_eq!(get_typography_keys(), vec!["body", "title"]); set_typography_keys(vec![]); - assert!(get_typography_keys().is_empty()); + assert_eq!(get_typography_keys(), Vec::::new()); } #[test] diff --git a/libs/extractor/src/lib.rs b/libs/extractor/src/lib.rs index 68981f36c..96c6ca430 100644 --- a/libs/extractor/src/lib.rs +++ b/libs/extractor/src/lib.rs @@ -836,8 +836,8 @@ mod tests { alternate: None, }; - assert!(empty.extract().is_empty()); - assert!(empty.into_extract().is_empty()); + assert_eq!(empty.extract(), vec![]); + assert_eq!(empty.into_extract(), vec![]); } #[test] @@ -13598,7 +13598,7 @@ globalCss({ ); assert!(result.is_ok()); let output = result.unwrap(); - assert!(!output.code.is_empty()); + assert_ne!(output.code, ""); } #[test] @@ -18778,7 +18778,7 @@ export const k = styled('div')({ color: SIZE });", &memory_resolver(CONSTANT_MODULES), ) .unwrap(); - assert!(without_imports.dependencies.is_empty()); + assert_eq!(without_imports.dependencies.len(), 0); let without_constants = extract_with_modules( "/src/Handler.tsx", "import { Box } from '@devup-ui/react';\nimport { handler } from './handler';\nexport const a = ;", diff --git a/libs/extractor/src/tailwind.rs b/libs/extractor/src/tailwind.rs index 87e9f5178..70643aa7c 100644 --- a/libs/extractor/src/tailwind.rs +++ b/libs/extractor/src/tailwind.rs @@ -284,7 +284,7 @@ pub struct TailwindClass { /// non-overlapping) but mutates the existing buffer instead of allocating a new /// `String`. `needle` must be non-empty. fn remove_all_substr(haystack: &mut String, needle: &str) { - debug_assert!(!needle.is_empty()); + debug_assert_ne!(needle, ""); let mut search_from = 0; while let Some(rel) = haystack[search_from..].find(needle) { let at = search_from + rel; @@ -3934,7 +3934,7 @@ mod tests { #[test] fn test_empty_string() { let styles = parse_tailwind_to_styles(""); - assert!(styles.is_empty()); + assert_eq!(styles, vec![]); } #[test] diff --git a/packages/components/src/__tests__/forward-ref.browser.test.tsx b/packages/components/src/__tests__/forward-ref.browser.test.tsx new file mode 100644 index 000000000..59d355290 --- /dev/null +++ b/packages/components/src/__tests__/forward-ref.browser.test.tsx @@ -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() + const input = createRef() + const radio = createRef() + const textarea = createRef() + render( + <> + + + r +