Skip to content
Merged
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
26 changes: 26 additions & 0 deletions src/components/ui/button/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -587,4 +587,30 @@ describe("Button", () => {
// 実際のブラウザ環境やE2Eテストでのテストが推奨されます
});
});

describe("Ref Forwarding", () => {
it("forwards ref to the button element", () => {
// Given: ref を渡した Button
const ref = React.createRef<HTMLButtonElement>();
testContainer.render(<Button ref={ref}>Click me</Button>);

// Then: ref.current が DOM の button 要素に紐づく
expect(ref.current).toBeInstanceOf(HTMLButtonElement);
expect(ref.current?.tagName).toBe("BUTTON");
});

it("forwards ref to the slotted anchor element via asChild", () => {
// Given: asChild で <a> を子に持つ Button に ref を渡す
const ref = React.createRef<HTMLAnchorElement>();
testContainer.render(
<Button asChild ref={ref}>
<a href="/about">About</a>
</Button>
);

// Then: Slot 経由で ref が <a> 要素に届く
expect(ref.current).toBeInstanceOf(HTMLAnchorElement);
expect(ref.current?.tagName).toBe("A");
});
});
});
37 changes: 22 additions & 15 deletions src/components/ui/button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -397,20 +397,23 @@ export interface ButtonProps
*
* @param {ButtonProps} props
*/
function Button({
className,
variant,
size,
theme,
isLoading = false,
isDisabled = false,
asChild = false,
disabled,
prefixIcon,
suffixIcon,
children,
...props
}: ButtonProps) {
const Button = React.forwardRef<HTMLElement, ButtonProps>(function Button(
{
className,
variant,
size,
theme,
isLoading = false,
isDisabled = false,
asChild = false,
disabled,
prefixIcon,
suffixIcon,
children,
...props
},
ref
) {
const Comp = asChild ? SlotPrimitive.Slot : "button";

// disabled状態の管理(isDisabled、disabled、またはisLoadingがtrueの場合)
Expand Down Expand Up @@ -484,6 +487,10 @@ function Button({

return (
<Comp
// asChild ケースで <a> 等を受け入れるため公開 API は HTMLElement で広く受けるが、
// 内部の Comp は <button> 固定の union が含まれるためここで narrow する。
// en: Public ref is HTMLElement (covers asChild targets); inner Comp's button branch needs narrowing.
ref={ref as React.Ref<HTMLButtonElement>}
data-slot="button"
aria-busy={isLoading || undefined}
aria-disabled={asChild && isButtonDisabled ? true : undefined}
Expand Down Expand Up @@ -537,7 +544,7 @@ function Button({
)}
</Comp>
);
}
});

Button.displayName = "Button";

Expand Down
Loading