VisuallyHidden and Portal utilities
This commit is contained in:
parent
b291ceab50
commit
697e80ef72
2
packages/core/src/utilities/portal/index.ts
Normal file
2
packages/core/src/utilities/portal/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { Portal } from "./portal";
|
||||
export type { PortalProps } from "./portal";
|
||||
25
packages/core/src/utilities/portal/portal.tsx
Normal file
25
packages/core/src/utilities/portal/portal.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import type { JSX } from "solid-js";
|
||||
import { isServer } from "solid-js/web";
|
||||
import { Portal as SolidPortal } from "solid-js/web";
|
||||
|
||||
export interface PortalProps {
|
||||
/** Target container. Defaults to document.body. */
|
||||
target?: Element | null;
|
||||
children: JSX.Element;
|
||||
}
|
||||
|
||||
/**
|
||||
* SSR-safe portal. During SSR, renders content inline.
|
||||
* On the client, moves content to the target container.
|
||||
*/
|
||||
export function Portal(props: PortalProps): JSX.Element {
|
||||
if (isServer) {
|
||||
return <>{props.children}</>;
|
||||
}
|
||||
|
||||
return (
|
||||
<SolidPortal mount={props.target ?? document.body}>
|
||||
{props.children}
|
||||
</SolidPortal>
|
||||
);
|
||||
}
|
||||
2
packages/core/src/utilities/visually-hidden/index.ts
Normal file
2
packages/core/src/utilities/visually-hidden/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { VisuallyHidden } from "./visually-hidden";
|
||||
export type { VisuallyHiddenProps } from "./visually-hidden";
|
||||
@ -0,0 +1,34 @@
|
||||
import type { JSX } from "solid-js";
|
||||
import { splitProps } from "solid-js";
|
||||
|
||||
export interface VisuallyHiddenProps extends JSX.HTMLAttributes<HTMLSpanElement> {
|
||||
children?: JSX.Element;
|
||||
}
|
||||
|
||||
const visuallyHiddenStyle: JSX.CSSProperties = {
|
||||
position: "absolute",
|
||||
border: "0",
|
||||
width: "1px",
|
||||
height: "1px",
|
||||
padding: "0",
|
||||
margin: "-1px",
|
||||
overflow: "hidden",
|
||||
clip: "rect(0, 0, 0, 0)",
|
||||
"white-space": "nowrap",
|
||||
"word-wrap": "normal",
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders content visually hidden but accessible to screen readers.
|
||||
*/
|
||||
export function VisuallyHidden(props: VisuallyHiddenProps): JSX.Element {
|
||||
const [local, rest] = splitProps(props, ["children", "style"]);
|
||||
return (
|
||||
<span
|
||||
style={{ ...visuallyHiddenStyle, ...(local.style as JSX.CSSProperties | undefined) }}
|
||||
{...rest}
|
||||
>
|
||||
{local.children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
23
packages/core/tests/utilities/portal.test.tsx
Normal file
23
packages/core/tests/utilities/portal.test.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import { render } from "@solidjs/testing-library";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { Portal } from "../../src/utilities/portal/portal";
|
||||
|
||||
describe("Portal", () => {
|
||||
it("renders children into document.body by default", () => {
|
||||
render(() => <Portal><div data-testid="portal-content">hello</div></Portal>);
|
||||
// Content should be in document.body, not the render container
|
||||
expect(document.body.querySelector("[data-testid='portal-content']")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders children into a custom target", () => {
|
||||
const target = document.createElement("div");
|
||||
document.body.appendChild(target);
|
||||
render(() => (
|
||||
<Portal target={target}>
|
||||
<div data-testid="custom-portal">hello</div>
|
||||
</Portal>
|
||||
));
|
||||
expect(target.querySelector("[data-testid='custom-portal']")).toBeTruthy();
|
||||
document.body.removeChild(target);
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user