feat(a11y): contrast tokens, accent-solid, responsive drawer, keyboard menus
- Contrast: --fg-muted reaches 4.5:1 on every surface in all nine themes (dark default was 2.55:1); status chips and accent text clear 4.5:1 on their own tints. New --accent-solid(/-hover) + tailwind `accent-solid` is the fill behind white text (.btn-primary, Switch on, Checkbox tick); --accent is now a text colour. Switch off / unchecked Checkbox get a >=3:1 edge. - <html lang> follows the chosen language; a global prefers-reduced-motion rule; index.html body uses a token instead of text-gray-200. - Layout: the sidebar starts collapsed below lg and becomes an off-canvas drawer below md (menu button, backdrop, Escape, useDialog). - ContextMenu: focus moves in, arrows/Home/End, Escape/Tab close, focus returns, a keyboard-opened menu anchors to its target. Select is a real listbox button (aria-expanded, activedescendant, type-ahead). PageTabs implements APG tabs with a tabpanel. New .btn-secondary/.btn-danger/ .btn-icon and a .select class for native selects, all with focus rings. - StatusBar announces connection and "N need you" politely; Sidebar's connection modal uses useDialog; EmptyState/Skeleton/AiToolIcon a11y. - api.ts request(): a non-JSON 200 throws a translated EBADRESPONSE instead of "Unexpected token '<'".
This commit is contained in:
@@ -65,13 +65,16 @@
|
||||
* ----------------------------------------------------------------------------- */
|
||||
|
||||
import type { CSSProperties } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface SkeletonProps {
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
/** Rounded shape variant. Defaults to "md". */
|
||||
rounded?: "sm" | "md" | "lg" | "full";
|
||||
/** Aria label for screen readers. Defaults to "Loading". */
|
||||
/** Aria label for screen readers. Defaults to the translated "Loading…";
|
||||
* `""` makes the block decorative (`aria-hidden`), for every block but one
|
||||
* in a group, so a reader hears "Loading" once, not once per shape. */
|
||||
label?: string;
|
||||
}
|
||||
|
||||
@@ -86,17 +89,15 @@ const ROUNDED_CLASS = {
|
||||
* Base shimmer block. Compose with width/height utilities for any shape:
|
||||
* <Skeleton className="h-6 w-24" />
|
||||
*/
|
||||
export function Skeleton({
|
||||
className = "",
|
||||
style,
|
||||
rounded = "md",
|
||||
label = "Loading",
|
||||
}: SkeletonProps) {
|
||||
export function Skeleton({ className = "", style, rounded = "md", label }: SkeletonProps) {
|
||||
const { t } = useTranslation();
|
||||
const hidden = label === "";
|
||||
return (
|
||||
<span
|
||||
role="status"
|
||||
aria-label={label}
|
||||
aria-busy="true"
|
||||
role={hidden ? undefined : "status"}
|
||||
aria-label={hidden ? undefined : (label ?? t("common:loading"))}
|
||||
aria-hidden={hidden || undefined}
|
||||
aria-busy={hidden ? undefined : "true"}
|
||||
className={`inline-block bg-surface-3/70 animate-pulse ${ROUNDED_CLASS[rounded]} ${className}`}
|
||||
style={style}
|
||||
/>
|
||||
@@ -133,7 +134,7 @@ export function TableRowSkeleton({ columns, widths }: { columns: number; widths?
|
||||
<tr className="border-b border-border/40">
|
||||
{Array.from({ length: columns }).map((_, i) => (
|
||||
<td key={i} className="px-5 py-4">
|
||||
<Skeleton className={`h-4 ${widths?.[i] ?? "w-24"}`} />
|
||||
<Skeleton className={`h-4 ${widths?.[i] ?? "w-24"}`} label={i === 0 ? undefined : ""} />
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user