feat(dialog): add aria properties to improve accessibility
This commit is contained in:
parent
b56242c497
commit
14619f5311
@ -4,7 +4,7 @@ import {
|
|||||||
type VariantProps,
|
type VariantProps,
|
||||||
vcn,
|
vcn,
|
||||||
} from "@pswui-lib";
|
} from "@pswui-lib";
|
||||||
import React, { type ReactNode, useState } from "react";
|
import React, { type ReactNode, useId, useState } from "react";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -25,7 +25,10 @@ interface DialogRootProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DialogRoot = ({ children }: DialogRootProps) => {
|
const DialogRoot = ({ children }: DialogRootProps) => {
|
||||||
const state = useState<IDialogContext>(initialDialogContext);
|
const state = useState<IDialogContext>({
|
||||||
|
...initialDialogContext,
|
||||||
|
ids: { dialog: useId(), title: useId(), description: useId() },
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<DialogContext.Provider value={state}>{children}</DialogContext.Provider>
|
<DialogContext.Provider value={state}>{children}</DialogContext.Provider>
|
||||||
);
|
);
|
||||||
@ -42,15 +45,17 @@ interface DialogTriggerProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DialogTrigger = ({ children }: DialogTriggerProps) => {
|
const DialogTrigger = ({ children }: DialogTriggerProps) => {
|
||||||
const [_, setState] = useDialogContext();
|
const [{ ids }, setState] = useDialogContext();
|
||||||
const onClick = () => setState((p) => ({ ...p, opened: true }));
|
const onClick = () => setState((p) => ({ ...p, opened: true }));
|
||||||
|
|
||||||
const slotProps = {
|
return (
|
||||||
onClick,
|
<Slot
|
||||||
children,
|
onClick={onClick}
|
||||||
};
|
aria-controls={ids.dialog}
|
||||||
|
>
|
||||||
return <Slot {...slotProps} />;
|
{children}
|
||||||
|
</Slot>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,7 +85,7 @@ interface DialogOverlay
|
|||||||
|
|
||||||
const DialogOverlay = React.forwardRef<HTMLDivElement, DialogOverlay>(
|
const DialogOverlay = React.forwardRef<HTMLDivElement, DialogOverlay>(
|
||||||
(props, ref) => {
|
(props, ref) => {
|
||||||
const [{ opened }, setContext] = useDialogContext();
|
const [{ opened, ids }, setContext] = useDialogContext();
|
||||||
const [variantProps, otherPropsCompressed] = resolveDialogOverlayVariant({
|
const [variantProps, otherPropsCompressed] = resolveDialogOverlayVariant({
|
||||||
...props,
|
...props,
|
||||||
opened,
|
opened,
|
||||||
@ -93,6 +98,7 @@ const DialogOverlay = React.forwardRef<HTMLDivElement, DialogOverlay>(
|
|||||||
{ReactDOM.createPortal(
|
{ReactDOM.createPortal(
|
||||||
<div
|
<div
|
||||||
{...otherPropsExtracted}
|
{...otherPropsExtracted}
|
||||||
|
id={ids.dialog}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={dialogOverlayVariant(variantProps)}
|
className={dialogOverlayVariant(variantProps)}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@ -144,7 +150,7 @@ interface DialogContentProps
|
|||||||
|
|
||||||
const DialogContent = React.forwardRef<HTMLDivElement, DialogContentProps>(
|
const DialogContent = React.forwardRef<HTMLDivElement, DialogContentProps>(
|
||||||
(props, ref) => {
|
(props, ref) => {
|
||||||
const [{ opened }] = useDialogContext();
|
const [{ opened, ids }] = useDialogContext();
|
||||||
const [variantProps, otherPropsCompressed] = resolveDialogContentVariant({
|
const [variantProps, otherPropsCompressed] = resolveDialogContentVariant({
|
||||||
...props,
|
...props,
|
||||||
opened,
|
opened,
|
||||||
@ -154,6 +160,9 @@ const DialogContent = React.forwardRef<HTMLDivElement, DialogContentProps>(
|
|||||||
<div
|
<div
|
||||||
{...otherPropsExtracted}
|
{...otherPropsExtracted}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
role="dialog"
|
||||||
|
aria-labelledby={ids.title}
|
||||||
|
aria-describedby={ids.description}
|
||||||
className={dialogContentVariant(variantProps)}
|
className={dialogContentVariant(variantProps)}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@ -240,26 +249,28 @@ interface DialogTitleProps
|
|||||||
extends React.ComponentPropsWithoutRef<"h1">,
|
extends React.ComponentPropsWithoutRef<"h1">,
|
||||||
VariantProps<typeof dialogTitleVariant> {}
|
VariantProps<typeof dialogTitleVariant> {}
|
||||||
|
|
||||||
const [dialogSubtitleVariant, resolveDialogSubtitleVariant] = vcn({
|
const [dialogDescriptionVariant, resolveDialogDescriptionVariant] = vcn({
|
||||||
base: "text-sm opacity-60 font-normal",
|
base: "text-sm opacity-60 font-normal",
|
||||||
variants: {},
|
variants: {},
|
||||||
defaults: {},
|
defaults: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
interface DialogSubtitleProps
|
interface DialogDescriptionProps
|
||||||
extends React.ComponentPropsWithoutRef<"h2">,
|
extends React.ComponentPropsWithoutRef<"h2">,
|
||||||
VariantProps<typeof dialogSubtitleVariant> {}
|
VariantProps<typeof dialogDescriptionVariant> {}
|
||||||
|
|
||||||
const DialogTitle = React.forwardRef<HTMLHeadingElement, DialogTitleProps>(
|
const DialogTitle = React.forwardRef<HTMLHeadingElement, DialogTitleProps>(
|
||||||
(props, ref) => {
|
(props, ref) => {
|
||||||
const [variantProps, otherPropsCompressed] =
|
const [variantProps, otherPropsCompressed] =
|
||||||
resolveDialogTitleVariant(props);
|
resolveDialogTitleVariant(props);
|
||||||
const { children, ...otherPropsExtracted } = otherPropsCompressed;
|
const { children, ...otherPropsExtracted } = otherPropsCompressed;
|
||||||
|
const [{ ids }] = useDialogContext();
|
||||||
return (
|
return (
|
||||||
<h1
|
<h1
|
||||||
{...otherPropsExtracted}
|
{...otherPropsExtracted}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={dialogTitleVariant(variantProps)}
|
className={dialogTitleVariant(variantProps)}
|
||||||
|
id={ids.title}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</h1>
|
</h1>
|
||||||
@ -268,24 +279,30 @@ const DialogTitle = React.forwardRef<HTMLHeadingElement, DialogTitleProps>(
|
|||||||
);
|
);
|
||||||
DialogTitle.displayName = "DialogTitle";
|
DialogTitle.displayName = "DialogTitle";
|
||||||
|
|
||||||
const DialogSubtitle = React.forwardRef<
|
const DialogDescription = React.forwardRef<
|
||||||
HTMLHeadingElement,
|
HTMLHeadingElement,
|
||||||
DialogSubtitleProps
|
DialogDescriptionProps
|
||||||
>((props, ref) => {
|
>((props, ref) => {
|
||||||
const [variantProps, otherPropsCompressed] =
|
const [variantProps, otherPropsCompressed] =
|
||||||
resolveDialogSubtitleVariant(props);
|
resolveDialogDescriptionVariant(props);
|
||||||
const { children, ...otherPropsExtracted } = otherPropsCompressed;
|
const { children, ...otherPropsExtracted } = otherPropsCompressed;
|
||||||
|
const [{ ids }] = useDialogContext();
|
||||||
return (
|
return (
|
||||||
<h2
|
<h2
|
||||||
{...otherPropsExtracted}
|
{...otherPropsExtracted}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={dialogSubtitleVariant(variantProps)}
|
className={dialogDescriptionVariant(variantProps)}
|
||||||
|
id={ids.description}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</h2>
|
</h2>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
DialogSubtitle.displayName = "DialogSubtitle";
|
DialogDescription.displayName = "DialogDescription";
|
||||||
|
|
||||||
|
// renamed DialogSubtitle -> DialogDescription
|
||||||
|
// keep DialogSubtitle for backward compatibility
|
||||||
|
const DialogSubtitle = DialogDescription;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* =========================
|
* =========================
|
||||||
@ -309,13 +326,13 @@ const DialogFooter = React.forwardRef<HTMLDivElement, DialogFooterProps>(
|
|||||||
resolveDialogFooterVariant(props);
|
resolveDialogFooterVariant(props);
|
||||||
const { children, ...otherPropsExtracted } = otherPropsCompressed;
|
const { children, ...otherPropsExtracted } = otherPropsCompressed;
|
||||||
return (
|
return (
|
||||||
<div
|
<footer
|
||||||
{...otherPropsExtracted}
|
{...otherPropsExtracted}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={dialogFooterVariant(variantProps)}
|
className={dialogFooterVariant(variantProps)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</footer>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -357,6 +374,7 @@ export {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogSubtitle,
|
DialogSubtitle,
|
||||||
|
DialogDescription,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogController,
|
DialogController,
|
||||||
};
|
};
|
||||||
|
@ -13,9 +13,14 @@ import {
|
|||||||
|
|
||||||
export interface IDialogContext {
|
export interface IDialogContext {
|
||||||
opened: boolean;
|
opened: boolean;
|
||||||
|
ids: {
|
||||||
|
dialog: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialDialogContext: IDialogContext = { opened: false };
|
export const initialDialogContext: IDialogContext = { opened: false, id: "" };
|
||||||
export const DialogContext = createContext<
|
export const DialogContext = createContext<
|
||||||
[IDialogContext, Dispatch<SetStateAction<IDialogContext>>]
|
[IDialogContext, Dispatch<SetStateAction<IDialogContext>>]
|
||||||
>([
|
>([
|
||||||
|
Loading…
x
Reference in New Issue
Block a user