웹 접근성 조치

This commit is contained in:
2026-10-06 14:32:26 +09:00
parent 24c5dce24d
commit d3c39caefb
90 changed files with 365 additions and 277 deletions
@@ -13,13 +13,12 @@ import { cn } from "../../lib";
import { Button } from "../button";
import {
Table,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "../table";
import type { HTMLAttributes } from "react";
import type { HTMLAttributes, ReactNode } from "react";
declare module "@tanstack/react-table" {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface ColumnMeta<TData extends RowData, TValue> {
@@ -36,6 +35,7 @@ type DataTableProps<T> = {
viewPagination?: boolean;
onRowClick?: (row: T) => void;
caption?: string;
footerContent?: ReactNode;
};
function DataTable<T>({
@@ -45,11 +45,15 @@ function DataTable<T>({
viewPagination,
onRowClick,
caption,
footerContent,
}: DataTableProps<T>) {
return (
<>
<Table className={cn("w-full", className)} variant={variant}>
{caption && <TableCaption className="sr-only">{caption}</TableCaption>}
<Table
caption={caption}
className={cn("w-full", className)}
variant={variant}
>
<THead table={table} />
<tbody>
{table.getRowModel().rows.map((row) => {
@@ -87,6 +91,9 @@ function DataTable<T>({
})}
</tbody>
</Table>
{footerContent && (
<div className="mt-2 flex justify-start">{footerContent}</div>
)}
{viewPagination && <Pagination table={table} />}
</>
);
@@ -103,7 +103,8 @@ export function LNB({
control.tabIndex >= 0 &&
!control.hasAttribute("disabled") &&
control.getAttribute("aria-disabled") !== "true" &&
!control.closest('[hidden], [aria-hidden="true"]'),
!control.closest('[hidden], [aria-hidden="true"]') &&
control.getClientRects().length > 0,
);
const currentIndex = menuControls.findIndex(
(control) =>
@@ -120,6 +121,19 @@ export function LNB({
event.preventDefault();
nextControl.focus();
nextControl.scrollIntoView({ block: "nearest" });
} else if (
isMobile &&
!event.shiftKey &&
currentIndex === menuControls.length - 1
) {
const closeButton = event.currentTarget
.closest<HTMLElement>('[data-sidebar="sidebar"]')
?.querySelector<HTMLElement>(":scope > button");
if (closeButton) {
event.preventDefault();
closeButton.focus();
}
}
}
@@ -6,22 +6,41 @@ import { cn } from "../../lib/utils";
interface TableProps extends React.HTMLAttributes<HTMLTableElement> {
variant?: "default" | "secondary" | "line";
caption?: string;
}
const Table = React.forwardRef<HTMLTableElement, TableProps>(
({ className, variant = "default", ...props }, ref) => (
<div className="relative w-full">
<table
ref={ref}
className={cn(
"table-ui w-full caption-bottom text-sm",
className,
variant,
)}
{...props}
/>
</div>
),
(
{
className,
variant = "default",
caption,
"aria-label": ariaLabel,
children,
...props
},
ref,
) => {
const captionText = caption ?? (ariaLabel ? `${ariaLabel} 표` : "테이블");
return (
<div className="relative w-full">
<table
ref={ref}
className={cn(
"table-ui w-full caption-bottom text-sm",
className,
variant,
)}
aria-label={ariaLabel}
{...props}
>
<caption className="sr-only">{captionText}</caption>
{children}
</table>
</div>
);
},
);
Table.displayName = "Table";
@@ -81,27 +100,28 @@ TableRow.displayName = "TableRow";
type TableHeadProps = React.ThHTMLAttributes<HTMLTableCellElement> & {
"data-required"?: boolean | string;
scope?: "col" | "row" | "colgroup" | "rowgroup";
};
const TableHead = React.forwardRef<HTMLTableCellElement, TableHeadProps>(
({ children, className, ...props }, ref) => {
const isRequired = props["data-required"] !== undefined;
({ children, className, scope = "col", ...props }, ref) => {
const isRequired = props["data-required"] !== undefined;
return (
<th
ref={ref}
scope="col"
className={cn("align-middle", className)}
{...props}
>
{children}
{isRequired && (
<span className="table-required-marker" title="필수 항목">
(*)
</span>
)}
</th>
);
return (
<th
ref={ref}
scope={scope}
className={cn("align-middle", className)}
{...props}
>
{children}
{isRequired && (
<span className="table-required-marker" title="필수 항목">
(*)
</span>
)}
</th>
);
},
);
TableHead.displayName = "TableHead";