Fix a11y incomplete checks and glyph icons
This commit is contained in:
@@ -1,6 +1,19 @@
|
|||||||
import { Button } from "@ai-ui/ui";
|
import { Button } from "@ai-ui/ui";
|
||||||
import type { Meta, StoryObj } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
|
function FavoriteIcon() {
|
||||||
|
return (
|
||||||
|
<svg aria-hidden="true" className="size-4" fill="none" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="m8 2.4 1.67 3.4 3.76.55-2.72 2.64.64 3.73L8 10.98l-3.35 1.74.64-3.73L2.57 6.35l3.76-.55L8 2.4Z"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="1.35"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function getButtonFromCanvas(canvasElement: HTMLElement, name: string) {
|
function getButtonFromCanvas(canvasElement: HTMLElement, name: string) {
|
||||||
const buttons = canvasElement.querySelectorAll("button, a");
|
const buttons = canvasElement.querySelectorAll("button, a");
|
||||||
|
|
||||||
@@ -104,7 +117,7 @@ export const Sizes: Story = {
|
|||||||
<Button size="md">Medium</Button>
|
<Button size="md">Medium</Button>
|
||||||
<Button size="lg">Large</Button>
|
<Button size="lg">Large</Button>
|
||||||
<Button aria-label="Favorite" size="icon">
|
<Button aria-label="Favorite" size="icon">
|
||||||
☆
|
<FavoriteIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const meta = {
|
|||||||
title: "Components/Checkbox",
|
title: "Components/Checkbox",
|
||||||
component: Checkbox,
|
component: Checkbox,
|
||||||
args: {
|
args: {
|
||||||
|
"aria-label": "Checkbox example",
|
||||||
defaultChecked: true
|
defaultChecked: true
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
@@ -31,7 +32,9 @@ export default meta;
|
|||||||
|
|
||||||
type Story = StoryObj<typeof meta>;
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
export const Playground: Story = {};
|
export const Playground: Story = {
|
||||||
|
render: (args) => <Checkbox {...args} aria-label="Checkbox example" />
|
||||||
|
};
|
||||||
|
|
||||||
export const States: Story = {
|
export const States: Story = {
|
||||||
render: () => (
|
render: () => (
|
||||||
|
|||||||
@@ -39,6 +39,20 @@ const motionAccessibilityModes = [
|
|||||||
|
|
||||||
type MotionAccessibilityMode = (typeof motionAccessibilityModes)[number]["value"];
|
type MotionAccessibilityMode = (typeof motionAccessibilityModes)[number]["value"];
|
||||||
|
|
||||||
|
function ClosePreviewIcon() {
|
||||||
|
return (
|
||||||
|
<svg aria-hidden="true" className="size-4" fill="none" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="m4.5 4.5 7 7m0-7-7 7"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="1.75"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function RuntimePill({ children }: { children: React.ReactNode }) {
|
function RuntimePill({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<span className="rounded-full border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-1 text-xs uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]">
|
<span className="rounded-full border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-1 text-xs uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]">
|
||||||
@@ -71,7 +85,7 @@ function PanelPreview() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button aria-hidden="true" size="icon" tabIndex={-1} variant="ghost">
|
<Button aria-hidden="true" size="icon" tabIndex={-1} variant="ghost">
|
||||||
×
|
<ClosePreviewIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ describe("Checkbox", () => {
|
|||||||
expect(checkbox).toBeChecked();
|
expect(checkbox).toBeChecked();
|
||||||
expect(checkbox).toHaveAttribute("data-state", "checked");
|
expect(checkbox).toHaveAttribute("data-state", "checked");
|
||||||
expect(onCheckedChange).toHaveBeenCalledWith(true);
|
expect(onCheckedChange).toHaveBeenCalledWith(true);
|
||||||
expect(screen.getByText("✓")).toHaveAttribute("data-slot", "icon");
|
expect(checkbox.querySelector('[data-slot="icon"] svg')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("supports keyboard interaction", async () => {
|
it("supports keyboard interaction", async () => {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { forwardRef, type ComponentPropsWithoutRef, type ElementRef } from "reac
|
|||||||
import { checkboxIndicatorVariants, checkboxVariants } from "./checkbox.variants";
|
import { checkboxIndicatorVariants, checkboxVariants } from "./checkbox.variants";
|
||||||
import { cn } from "../lib/cn";
|
import { cn } from "../lib/cn";
|
||||||
import { createDataAttributes, createSlot } from "../lib/contracts";
|
import { createDataAttributes, createSlot } from "../lib/contracts";
|
||||||
|
import { CheckIcon } from "../lib/icons";
|
||||||
|
|
||||||
export type CheckboxProps = ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> & {
|
export type CheckboxProps = ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> & {
|
||||||
invalid?: boolean;
|
invalid?: boolean;
|
||||||
@@ -30,7 +31,7 @@ export const Checkbox = forwardRef<
|
|||||||
{...createSlot("icon")}
|
{...createSlot("icon")}
|
||||||
className={checkboxIndicatorVariants()}
|
className={checkboxIndicatorVariants()}
|
||||||
>
|
>
|
||||||
✓
|
<CheckIcon className="size-3.5" />
|
||||||
</CheckboxPrimitive.Indicator>
|
</CheckboxPrimitive.Indicator>
|
||||||
</CheckboxPrimitive.Root>
|
</CheckboxPrimitive.Root>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ describe("Combobox", () => {
|
|||||||
|
|
||||||
expect(trigger).toHaveTextContent("Design review");
|
expect(trigger).toHaveTextContent("Design review");
|
||||||
expect(trigger).toHaveAttribute("data-slot", "trigger");
|
expect(trigger).toHaveAttribute("data-slot", "trigger");
|
||||||
|
expect(trigger).not.toHaveAttribute("aria-controls");
|
||||||
|
|
||||||
await user.click(trigger);
|
await user.click(trigger);
|
||||||
|
|
||||||
@@ -65,6 +66,7 @@ describe("Combobox", () => {
|
|||||||
const option = within(listbox).getByRole("option", { name: /Legal review/i });
|
const option = within(listbox).getByRole("option", { name: /Legal review/i });
|
||||||
|
|
||||||
expect(listbox).toHaveAttribute("data-slot", "list");
|
expect(listbox).toHaveAttribute("data-slot", "list");
|
||||||
|
expect(trigger).toHaveAttribute("aria-controls", listbox.id);
|
||||||
expect(screen.getByText("Specialist lanes")).toHaveAttribute("data-slot", "label");
|
expect(screen.getByText("Specialist lanes")).toHaveAttribute("data-slot", "label");
|
||||||
|
|
||||||
await user.click(option);
|
await user.click(option);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
} from "./combobox.variants";
|
} from "./combobox.variants";
|
||||||
import { cn } from "../lib/cn";
|
import { cn } from "../lib/cn";
|
||||||
import { createDataAttributes, createSlot } from "../lib/contracts";
|
import { createDataAttributes, createSlot } from "../lib/contracts";
|
||||||
|
import { CheckIcon, ChevronDownIcon } from "../lib/icons";
|
||||||
import { useFieldContext } from "./field";
|
import { useFieldContext } from "./field";
|
||||||
|
|
||||||
function mergeIds(...ids: Array<string | undefined>) {
|
function mergeIds(...ids: Array<string | undefined>) {
|
||||||
@@ -143,6 +144,7 @@ export const Combobox = forwardRef<HTMLButtonElement, ComboboxProps>(function Co
|
|||||||
return haystack.includes(query);
|
return haystack.includes(query);
|
||||||
});
|
});
|
||||||
}, [items, resolvedSearchValue]);
|
}, [items, resolvedSearchValue]);
|
||||||
|
const listboxId = filteredItems.length > 0 ? `${controlId}-listbox` : undefined;
|
||||||
|
|
||||||
const groupedItems = useMemo(() => {
|
const groupedItems = useMemo(() => {
|
||||||
const groups = new Map<string, ComboboxItem[]>();
|
const groups = new Map<string, ComboboxItem[]>();
|
||||||
@@ -305,7 +307,7 @@ export const Combobox = forwardRef<HTMLButtonElement, ComboboxProps>(function Co
|
|||||||
invalid: resolvedInvalid,
|
invalid: resolvedInvalid,
|
||||||
placeholder: selectedItem ? undefined : true
|
placeholder: selectedItem ? undefined : true
|
||||||
})}
|
})}
|
||||||
aria-controls={`${controlId}-listbox`}
|
aria-controls={resolvedOpen ? listboxId : undefined}
|
||||||
aria-describedby={describedBy}
|
aria-describedby={describedBy}
|
||||||
aria-expanded={resolvedOpen}
|
aria-expanded={resolvedOpen}
|
||||||
aria-invalid={resolvedInvalid || undefined}
|
aria-invalid={resolvedInvalid || undefined}
|
||||||
@@ -326,7 +328,7 @@ export const Combobox = forwardRef<HTMLButtonElement, ComboboxProps>(function Co
|
|||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="shrink-0 text-[var(--color-muted-foreground)]"
|
className="shrink-0 text-[var(--color-muted-foreground)]"
|
||||||
>
|
>
|
||||||
▾
|
<ChevronDownIcon className="size-3.5" />
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</PopoverPrimitive.Trigger>
|
</PopoverPrimitive.Trigger>
|
||||||
@@ -359,7 +361,7 @@ export const Combobox = forwardRef<HTMLButtonElement, ComboboxProps>(function Co
|
|||||||
<div
|
<div
|
||||||
{...createSlot("list")}
|
{...createSlot("list")}
|
||||||
className={comboboxListVariants()}
|
className={comboboxListVariants()}
|
||||||
id={`${controlId}-listbox`}
|
id={listboxId}
|
||||||
role="listbox"
|
role="listbox"
|
||||||
>
|
>
|
||||||
{groupedItems.map(([group, groupItems]) => (
|
{groupedItems.map(([group, groupItems]) => (
|
||||||
@@ -405,11 +407,11 @@ export const Combobox = forwardRef<HTMLButtonElement, ComboboxProps>(function Co
|
|||||||
{...createSlot("icon")}
|
{...createSlot("icon")}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className={cn(
|
className={cn(
|
||||||
"mt-0.5 inline-flex size-4 shrink-0 items-center justify-center text-xs text-[var(--color-primary)]",
|
"mt-0.5 inline-flex size-4 shrink-0 items-center justify-center text-[var(--color-primary)]",
|
||||||
!isSelected && "opacity-0"
|
!isSelected && "opacity-0"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
✓
|
<CheckIcon className="size-3.5" />
|
||||||
</span>
|
</span>
|
||||||
<span className="min-w-0 flex-1">
|
<span className="min-w-0 flex-1">
|
||||||
<span className="block truncate">{item.label}</span>
|
<span className="block truncate">{item.label}</span>
|
||||||
|
|||||||
@@ -40,6 +40,11 @@ import { Spinner } from "./spinner";
|
|||||||
import { cn } from "../lib/cn";
|
import { cn } from "../lib/cn";
|
||||||
import type { VariantProps } from "../lib/cva";
|
import type { VariantProps } from "../lib/cva";
|
||||||
import { createDataAttributes, createSlot } from "../lib/contracts";
|
import { createDataAttributes, createSlot } from "../lib/contracts";
|
||||||
|
import {
|
||||||
|
SortAscendingIcon,
|
||||||
|
SortDescendingIcon,
|
||||||
|
SortUnsortedIcon
|
||||||
|
} from "../lib/icons";
|
||||||
import {
|
import {
|
||||||
dataTableBodyVariants,
|
dataTableBodyVariants,
|
||||||
dataTableCellVariants,
|
dataTableCellVariants,
|
||||||
@@ -546,9 +551,15 @@ function DataTableInner<TData>(
|
|||||||
<span>{flexRender(header.column.columnDef.header, header.getContext())}</span>
|
<span>{flexRender(header.column.columnDef.header, header.getContext())}</span>
|
||||||
<span
|
<span
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="text-xs text-[var(--color-muted-foreground)]"
|
className="inline-flex items-center justify-center text-[var(--color-muted-foreground)]"
|
||||||
>
|
>
|
||||||
{sortState === "asc" ? "↑" : sortState === "desc" ? "↓" : "↕"}
|
{sortState === "asc" ? (
|
||||||
|
<SortAscendingIcon className="size-3.5" />
|
||||||
|
) : sortState === "desc" ? (
|
||||||
|
<SortDescendingIcon className="size-3.5" />
|
||||||
|
) : (
|
||||||
|
<SortUnsortedIcon className="size-3.5" />
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span className="sr-only">
|
<span className="sr-only">
|
||||||
Sort by{" "}
|
Sort by{" "}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { dialogContentVariants, dialogFooterVariants, dialogHeaderVariants, dial
|
|||||||
import { cn } from "../lib/cn";
|
import { cn } from "../lib/cn";
|
||||||
import type { VariantProps } from "../lib/cva";
|
import type { VariantProps } from "../lib/cva";
|
||||||
import { createDataAttributes, createSlot } from "../lib/contracts";
|
import { createDataAttributes, createSlot } from "../lib/contracts";
|
||||||
|
import { CloseIcon } from "../lib/icons";
|
||||||
|
|
||||||
export const Dialog = DialogPrimitive.Root;
|
export const Dialog = DialogPrimitive.Root;
|
||||||
export const DialogTrigger = DialogPrimitive.Trigger;
|
export const DialogTrigger = DialogPrimitive.Trigger;
|
||||||
@@ -47,9 +48,7 @@ export const DialogContent = forwardRef<
|
|||||||
aria-label="Close dialog"
|
aria-label="Close dialog"
|
||||||
className="absolute right-4 top-4 inline-flex size-9 items-center justify-center rounded-[var(--ui-control-radius)] border border-transparent text-[var(--color-muted-foreground)] outline-none transition-colors duration-[var(--dur-fast)] ease-[var(--ease-standard)] hover:border-[var(--ui-control-border)] hover:bg-[var(--ui-control-bg)] hover:text-[var(--color-foreground)] hover:shadow-[var(--ui-control-shadow)] focus-visible:ring-2 focus-visible:ring-[var(--color-ring)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ui-panel-bg)]"
|
className="absolute right-4 top-4 inline-flex size-9 items-center justify-center rounded-[var(--ui-control-radius)] border border-transparent text-[var(--color-muted-foreground)] outline-none transition-colors duration-[var(--dur-fast)] ease-[var(--ease-standard)] hover:border-[var(--ui-control-border)] hover:bg-[var(--ui-control-bg)] hover:text-[var(--color-foreground)] hover:shadow-[var(--ui-control-shadow)] focus-visible:ring-2 focus-visible:ring-[var(--color-ring)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ui-panel-bg)]"
|
||||||
>
|
>
|
||||||
<span aria-hidden="true" className="text-lg leading-none">
|
<CloseIcon className="size-4" />
|
||||||
×
|
|
||||||
</span>
|
|
||||||
</DialogPrimitive.Close>
|
</DialogPrimitive.Close>
|
||||||
</DialogPrimitive.Content>
|
</DialogPrimitive.Content>
|
||||||
</DialogPortal>
|
</DialogPortal>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
import { cn } from "../lib/cn";
|
import { cn } from "../lib/cn";
|
||||||
import type { VariantProps } from "../lib/cva";
|
import type { VariantProps } from "../lib/cva";
|
||||||
import { createDataAttributes, createSlot } from "../lib/contracts";
|
import { createDataAttributes, createSlot } from "../lib/contracts";
|
||||||
|
import { CheckIcon, ChevronRightIcon, DotIcon } from "../lib/icons";
|
||||||
|
|
||||||
export const DropdownMenu = DropdownMenuPrimitive.Root;
|
export const DropdownMenu = DropdownMenuPrimitive.Root;
|
||||||
export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
|
export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
|
||||||
@@ -117,9 +118,11 @@ export const DropdownMenuCheckboxItem = forwardRef<
|
|||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
{...createSlot("icon")}
|
{...createSlot("icon")}
|
||||||
className="absolute left-2.5 inline-flex size-4 items-center justify-center text-xs"
|
className="absolute left-2.5 inline-flex size-4 items-center justify-center text-[var(--color-primary)]"
|
||||||
>
|
>
|
||||||
<DropdownMenuPrimitive.ItemIndicator>✓</DropdownMenuPrimitive.ItemIndicator>
|
<DropdownMenuPrimitive.ItemIndicator>
|
||||||
|
<CheckIcon className="size-3" />
|
||||||
|
</DropdownMenuPrimitive.ItemIndicator>
|
||||||
</span>
|
</span>
|
||||||
{children}
|
{children}
|
||||||
</DropdownMenuPrimitive.CheckboxItem>
|
</DropdownMenuPrimitive.CheckboxItem>
|
||||||
@@ -147,9 +150,11 @@ export const DropdownMenuRadioItem = forwardRef<
|
|||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
{...createSlot("icon")}
|
{...createSlot("icon")}
|
||||||
className="absolute left-2.5 inline-flex size-4 items-center justify-center text-xs"
|
className="absolute left-2.5 inline-flex size-4 items-center justify-center text-[var(--color-primary)]"
|
||||||
>
|
>
|
||||||
<DropdownMenuPrimitive.ItemIndicator>•</DropdownMenuPrimitive.ItemIndicator>
|
<DropdownMenuPrimitive.ItemIndicator>
|
||||||
|
<DotIcon className="size-2.5" />
|
||||||
|
</DropdownMenuPrimitive.ItemIndicator>
|
||||||
</span>
|
</span>
|
||||||
{children}
|
{children}
|
||||||
</DropdownMenuPrimitive.RadioItem>
|
</DropdownMenuPrimitive.RadioItem>
|
||||||
@@ -209,7 +214,7 @@ export const DropdownMenuSubTrigger = forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<span className="ml-auto text-xs text-[var(--color-muted-foreground)]">›</span>
|
<ChevronRightIcon className="ml-auto size-3 text-[var(--color-muted-foreground)]" />
|
||||||
</DropdownMenuPrimitive.SubTrigger>
|
</DropdownMenuPrimitive.SubTrigger>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
} from "./select.variants";
|
} from "./select.variants";
|
||||||
import { cn } from "../lib/cn";
|
import { cn } from "../lib/cn";
|
||||||
import { createDataAttributes, createSlot } from "../lib/contracts";
|
import { createDataAttributes, createSlot } from "../lib/contracts";
|
||||||
|
import { CheckIcon, ChevronDownIcon } from "../lib/icons";
|
||||||
import { useFieldContext } from "./field";
|
import { useFieldContext } from "./field";
|
||||||
|
|
||||||
function mergeIds(...ids: Array<string | undefined>) {
|
function mergeIds(...ids: Array<string | undefined>) {
|
||||||
@@ -57,7 +58,7 @@ export const SelectTrigger = forwardRef<
|
|||||||
{...createSlot("icon")}
|
{...createSlot("icon")}
|
||||||
className="text-[var(--color-muted-foreground)]"
|
className="text-[var(--color-muted-foreground)]"
|
||||||
>
|
>
|
||||||
▾
|
<ChevronDownIcon className="size-3.5" />
|
||||||
</SelectPrimitive.Icon>
|
</SelectPrimitive.Icon>
|
||||||
</SelectPrimitive.Trigger>
|
</SelectPrimitive.Trigger>
|
||||||
);
|
);
|
||||||
@@ -117,9 +118,11 @@ export const SelectItem = forwardRef<
|
|||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
{...createSlot("icon")}
|
{...createSlot("icon")}
|
||||||
className="absolute left-2.5 inline-flex size-4 items-center justify-center text-xs"
|
className="absolute left-2.5 inline-flex size-4 items-center justify-center text-[var(--color-primary)]"
|
||||||
>
|
>
|
||||||
<SelectPrimitive.ItemIndicator>✓</SelectPrimitive.ItemIndicator>
|
<SelectPrimitive.ItemIndicator>
|
||||||
|
<CheckIcon className="size-3" />
|
||||||
|
</SelectPrimitive.ItemIndicator>
|
||||||
</span>
|
</span>
|
||||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||||
</SelectPrimitive.Item>
|
</SelectPrimitive.Item>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
import { cn } from "../lib/cn";
|
import { cn } from "../lib/cn";
|
||||||
import type { VariantProps } from "../lib/cva";
|
import type { VariantProps } from "../lib/cva";
|
||||||
import { createDataAttributes, createSlot } from "../lib/contracts";
|
import { createDataAttributes, createSlot } from "../lib/contracts";
|
||||||
|
import { CloseIcon } from "../lib/icons";
|
||||||
|
|
||||||
export const Sheet = DialogPrimitive.Root;
|
export const Sheet = DialogPrimitive.Root;
|
||||||
export const SheetTrigger = DialogPrimitive.Trigger;
|
export const SheetTrigger = DialogPrimitive.Trigger;
|
||||||
@@ -64,9 +65,7 @@ export const SheetContent = forwardRef<
|
|||||||
aria-label="Close sheet"
|
aria-label="Close sheet"
|
||||||
className="absolute right-4 top-4 inline-flex size-9 items-center justify-center rounded-[var(--ui-control-radius)] border border-transparent text-[var(--color-muted-foreground)] outline-none transition-colors duration-[var(--dur-fast)] ease-[var(--ease-standard)] hover:border-[var(--ui-control-border)] hover:bg-[var(--ui-control-bg)] hover:text-[var(--color-foreground)] hover:shadow-[var(--ui-control-shadow)] focus-visible:ring-2 focus-visible:ring-[var(--color-ring)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ui-panel-bg)]"
|
className="absolute right-4 top-4 inline-flex size-9 items-center justify-center rounded-[var(--ui-control-radius)] border border-transparent text-[var(--color-muted-foreground)] outline-none transition-colors duration-[var(--dur-fast)] ease-[var(--ease-standard)] hover:border-[var(--ui-control-border)] hover:bg-[var(--ui-control-bg)] hover:text-[var(--color-foreground)] hover:shadow-[var(--ui-control-shadow)] focus-visible:ring-2 focus-visible:ring-[var(--color-ring)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ui-panel-bg)]"
|
||||||
>
|
>
|
||||||
<span aria-hidden="true" className="text-lg leading-none">
|
<CloseIcon className="size-4" />
|
||||||
×
|
|
||||||
</span>
|
|
||||||
</DialogPrimitive.Close>
|
</DialogPrimitive.Close>
|
||||||
</DialogPrimitive.Content>
|
</DialogPrimitive.Content>
|
||||||
</SheetPortal>
|
</SheetPortal>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { toastActionVariants, toastCloseVariants, toastVariants, toastViewportVa
|
|||||||
import { cn } from "../lib/cn";
|
import { cn } from "../lib/cn";
|
||||||
import type { VariantProps } from "../lib/cva";
|
import type { VariantProps } from "../lib/cva";
|
||||||
import { createDataAttributes, createSlot } from "../lib/contracts";
|
import { createDataAttributes, createSlot } from "../lib/contracts";
|
||||||
|
import { CloseIcon } from "../lib/icons";
|
||||||
|
|
||||||
export const ToastProvider = ToastPrimitive.Provider;
|
export const ToastProvider = ToastPrimitive.Provider;
|
||||||
|
|
||||||
@@ -95,9 +96,7 @@ export const ToastClose = forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
{children ?? (
|
{children ?? (
|
||||||
<span aria-hidden="true" className="text-lg leading-none">
|
<CloseIcon className="size-4" />
|
||||||
×
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
</ToastPrimitive.Close>
|
</ToastPrimitive.Close>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
import type { ComponentPropsWithoutRef, ReactNode } from "react";
|
||||||
|
|
||||||
|
import { cn } from "./cn";
|
||||||
|
|
||||||
|
type IconProps = ComponentPropsWithoutRef<"svg">;
|
||||||
|
|
||||||
|
function IconFrame({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
viewBox = "0 0 16 16",
|
||||||
|
...props
|
||||||
|
}: IconProps & {
|
||||||
|
children: ReactNode;
|
||||||
|
viewBox?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className={cn("size-4 shrink-0", className)}
|
||||||
|
fill="none"
|
||||||
|
viewBox={viewBox}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CheckIcon({ className, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<IconFrame className={className} {...props}>
|
||||||
|
<path
|
||||||
|
d="M3.5 8.5 6.5 11.5 12.5 5.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="1.75"
|
||||||
|
/>
|
||||||
|
</IconFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ChevronDownIcon({ className, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<IconFrame className={className} {...props}>
|
||||||
|
<path
|
||||||
|
d="m4.5 6.5 3.5 3 3.5-3"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="1.75"
|
||||||
|
/>
|
||||||
|
</IconFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ChevronRightIcon({ className, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<IconFrame className={className} {...props}>
|
||||||
|
<path
|
||||||
|
d="m6 4.5 3.5 3.5L6 11.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="1.75"
|
||||||
|
/>
|
||||||
|
</IconFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CloseIcon({ className, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<IconFrame className={className} {...props}>
|
||||||
|
<path
|
||||||
|
d="m4.5 4.5 7 7m0-7-7 7"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="1.75"
|
||||||
|
/>
|
||||||
|
</IconFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DotIcon({ className, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<IconFrame className={className} viewBox="0 0 8 8" {...props}>
|
||||||
|
<circle cx="4" cy="4" fill="currentColor" r="2.25" />
|
||||||
|
</IconFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SortAscendingIcon({ className, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<IconFrame className={className} {...props}>
|
||||||
|
<path
|
||||||
|
d="M8 12.5v-9m0 0L5.5 6m2.5-2.5L10.5 6"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
/>
|
||||||
|
</IconFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SortDescendingIcon({ className, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<IconFrame className={className} {...props}>
|
||||||
|
<path
|
||||||
|
d="M8 3.5v9m0 0-2.5-2.5M8 12.5l2.5-2.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
/>
|
||||||
|
</IconFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SortUnsortedIcon({ className, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<IconFrame className={className} {...props}>
|
||||||
|
<path
|
||||||
|
d="m5 6 3-3 3 3M11 10l-3 3-3-3"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
/>
|
||||||
|
</IconFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user