diff --git a/package.json b/package.json index 55ab11ac..598102a3 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,6 @@ "@octokit/plugin-throttling": "11.0.3", "@sentry/solid": "10.46.0", "@solidjs/router": "0.16.1", - "corvu": "0.7.2", "idb": "8.0.3", "solid-js": "1.9.11", "zod": "4.3.6" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 517be871..f69e6ddb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,9 +29,6 @@ importers: '@solidjs/router': specifier: 0.16.1 version: 0.16.1(solid-js@1.9.11) - corvu: - specifier: 0.7.2 - version: 0.7.2(solid-js@1.9.11) idb: specifier: 8.0.3 version: 8.0.3 @@ -231,56 +228,6 @@ packages: cpu: [x64] os: [win32] - '@corvu/accordion@0.2.5': - resolution: {integrity: sha512-oBZKDZZNN5mirmilSzWSrY+7PQGHQbXAlWm1SOvG1AAsIeX5osUdQnKjH0XzlkkAxCLKHb41178W4YNNPqH0PA==} - peerDependencies: - solid-js: ^1.8 - - '@corvu/calendar@0.1.2': - resolution: {integrity: sha512-Vbdns1iNNI0WTIwDZS7m9pHWtr/YTz7yrisXhHUCfl4mxDvb9W8MMmun651wiEjhL7+OrcNbbl2kTOmYSFIf4g==} - peerDependencies: - solid-js: ^1.8 - - '@corvu/dialog@0.2.4': - resolution: {integrity: sha512-n54vJq+fOy8GVrnYBdJpD6JXNuyx7LOeMrRxwzAvZnYGpW8+AA12tnb/P/2emJj/HjOO5otheGKb0breshdFlA==} - peerDependencies: - solid-js: ^1.8 - - '@corvu/disclosure@0.2.2': - resolution: {integrity: sha512-d2E9zS0w9peeOiBDc8/ZcqS56ZeOIiKJcwMFpLjuhlfrtVAepwyhbwFHNQTb48VeRHW8amfrGliymT//VQFF9g==} - peerDependencies: - solid-js: ^1.8 - - '@corvu/drawer@0.2.4': - resolution: {integrity: sha512-7jQoGZ8ROB9CmXam2nMY2wEskU3IoFwZQywkF/7vrBc/edGsPv7mOVQ1GN6G+4nd7nrMZ3UtqHAhmRh9V0azlw==} - peerDependencies: - solid-js: ^1.8 - - '@corvu/otp-field@0.1.4': - resolution: {integrity: sha512-3eG7OoUt6CfVqGujIYfqImdrhGR/s4DpKr5ZQT10zzw3nawIlcwVpqoHTam0v4cgv+NXXvl6I8DoA3J+WgW2YA==} - peerDependencies: - solid-js: ^1.8 - - '@corvu/popover@0.2.0': - resolution: {integrity: sha512-zynRWyRV7mk2uX7t/COQL4OEa6H6FBgOxxlXLQJ1BV8hTiBJwmfAN/yB/9jy+zlp0G9vp64E8VdsNn6gxUEUEQ==} - peerDependencies: - solid-js: ^1.8 - - '@corvu/resizable@0.2.5': - resolution: {integrity: sha512-psax38DraM9SXtaNh/sfvizGO6xLCL4sqT4rWYlMZP5ZsUM05Q30tvXgnTo2roQFNOz2e06WofhDsBXTjY6g4A==} - peerDependencies: - solid-js: ^1.8 - - '@corvu/tooltip@0.2.2': - resolution: {integrity: sha512-gyOBJ43GNAecDtmWc68A5YJvifXWwK0VZZdTOcFbX0rVQUA2ihBMltRE4oj7/lKidg2gXALjmFr71Blt5s3YuQ==} - peerDependencies: - solid-js: ^1.8 - - '@corvu/utils@0.3.2': - resolution: {integrity: sha512-ZWlyWEE8qV9+CB9OAyo2bTrZGXQN9ZeM+JfYv89zoR+lRACKTDuoOZEdiyL8Uc7U5dUSH1uTqKhTTnaHWb+wZA==} - peerDependencies: - solid-js: ^1.8 - '@corvu/utils@0.4.2': resolution: {integrity: sha512-Ox2kYyxy7NoXdKWdHeDEjZxClwzO4SKM8plAaVwmAJPxHMqA0rLOoAsa+hBDwRLpctf+ZRnAd/ykguuJidnaTA==} peerDependencies: @@ -879,11 +826,6 @@ packages: peerDependencies: solid-js: ^1.6.12 - '@solid-primitives/memo@1.4.5': - resolution: {integrity: sha512-dMfFShNsyX5virETyDv/Uoy2HP+PL4k8cUTTLb2r4TfoqJb010KIaOuURqp/Qbdznp4ZkDuP57b28d45kaOueQ==} - peerDependencies: - solid-js: ^1.6.12 - '@solid-primitives/props@3.2.3': resolution: {integrity: sha512-XzG6en9gSFwmvbKcATm2BxL63HegZ+BAG5fmHi8jyBppQHcaths7ffz+6vYvwYy3nlgLa20ufJLj7tst+PcHFA==} peerDependencies: @@ -904,11 +846,6 @@ packages: peerDependencies: solid-js: ^1.6.12 - '@solid-primitives/scheduled@1.5.3': - resolution: {integrity: sha512-oNwLE6E6lxJAWrc8QXuwM0k2oU1BnANnkChwMw82aK1j3+mWGJkG1IFe5gCwbV+afYmjI76t9JJV3md/8tLw+g==} - peerDependencies: - solid-js: ^1.6.12 - '@solid-primitives/static-store@0.1.3': resolution: {integrity: sha512-uxez7SXnr5GiRnzqO2IEDjOJRIXaG+0LZLBizmUA1FwSi+hrpuMzVBwyk70m4prcl8X6FDDXUl9O8hSq8wHbBQ==} peerDependencies: @@ -1182,11 +1119,6 @@ packages: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} - corvu@0.7.2: - resolution: {integrity: sha512-OpBHnX7NYceHif3/OPu4Tkv9JmoM+WJFI9SjHKciE/PnWKgADqbRhViX2GvfUH5WMNMlb3MVAYmfykYJDECwtA==} - peerDependencies: - solid-js: ^1.8 - csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} @@ -1496,34 +1428,14 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - solid-dismissible@0.1.1: - resolution: {integrity: sha512-9kcKBJIMdS+586cA1g63HYWxKh3h89leeNHbPZ1csYjuni+NvPBtNr11l0iEX2AKKEt6FHk6qNhc/gjoYAW1pA==} - peerDependencies: - solid-js: ^1.8 - - solid-focus-trap@0.1.9: - resolution: {integrity: sha512-LTyNki6GUJPRLXV5uMWPkYClB07SUMubbr2EkAddiR0CJCF/I283txilMU9RURSr/P8EewMfXWu2o3aWrK7A5A==} - peerDependencies: - solid-js: ^1.8 - solid-js@1.9.11: resolution: {integrity: sha512-WEJtcc5mkh/BnHA6Yrg4whlF8g6QwpmXXRg4P2ztPmcKeHHlH4+djYecBLhSpecZY2RRECXYUwIc/C2r3yzQ4Q==} - solid-list@0.3.0: - resolution: {integrity: sha512-t4hx/F/l8Vmq+ib9HtZYl7Z9F1eKxq3eKJTXlvcm7P7yI4Z8O7QSOOEVHb/K6DD7M0RxzVRobK/BS5aSfLRwKg==} - peerDependencies: - solid-js: ^1.8 - solid-presence@0.1.8: resolution: {integrity: sha512-pWGtXUFWYYUZNbg5YpG5vkQJyOtzn2KXhxYaMx/4I+lylTLYkITOLevaCwMRN+liCVk0pqB6EayLWojNqBFECA==} peerDependencies: solid-js: ^1.8 - solid-presence@0.2.0: - resolution: {integrity: sha512-YM92o+jvpzX3XGaD4rLYmq/Kc2ZVh47GSCLEufHBFQQIurvZTs8SoGJxO8BJGNDxBKdcS8F3dYhW1SDXp4BNjA==} - peerDependencies: - solid-js: ^1.8 - solid-prevent-scroll@0.1.10: resolution: {integrity: sha512-KplGPX2GHiWJLZ6AXYRql4M127PdYzfwvLJJXMkO+CMb8Np4VxqDAg5S8jLdwlEuBis/ia9DKw2M8dFx5u8Mhw==} peerDependencies: @@ -1534,11 +1446,6 @@ packages: peerDependencies: solid-js: ^1.3 - solid-transition-size@0.1.4: - resolution: {integrity: sha512-ocHVnbfy23CgfaH4cEUR/AFg0Y3CEL8Oh3n9Qv8OHFJgPh+zkmERKZQfi/xH5XvxDCizg8VjPrVUhiHB1Gza8g==} - peerDependencies: - solid-js: ^1.8 - source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -1927,71 +1834,6 @@ snapshots: '@cloudflare/workerd-windows-64@1.20260317.1': optional: true - '@corvu/accordion@0.2.5(solid-js@1.9.11)': - dependencies: - '@corvu/disclosure': 0.2.2(solid-js@1.9.11) - '@corvu/utils': 0.4.2(solid-js@1.9.11) - solid-js: 1.9.11 - solid-list: 0.3.0(solid-js@1.9.11) - - '@corvu/calendar@0.1.2(solid-js@1.9.11)': - dependencies: - '@corvu/utils': 0.4.2(solid-js@1.9.11) - solid-js: 1.9.11 - - '@corvu/dialog@0.2.4(solid-js@1.9.11)': - dependencies: - '@corvu/utils': 0.4.2(solid-js@1.9.11) - solid-dismissible: 0.1.1(solid-js@1.9.11) - solid-focus-trap: 0.1.9(solid-js@1.9.11) - solid-js: 1.9.11 - solid-presence: 0.2.0(solid-js@1.9.11) - solid-prevent-scroll: 0.1.10(solid-js@1.9.11) - - '@corvu/disclosure@0.2.2(solid-js@1.9.11)': - dependencies: - '@corvu/utils': 0.4.2(solid-js@1.9.11) - solid-js: 1.9.11 - solid-presence: 0.2.0(solid-js@1.9.11) - - '@corvu/drawer@0.2.4(solid-js@1.9.11)': - dependencies: - '@corvu/dialog': 0.2.4(solid-js@1.9.11) - '@corvu/utils': 0.4.2(solid-js@1.9.11) - '@solid-primitives/memo': 1.4.5(solid-js@1.9.11) - solid-js: 1.9.11 - solid-transition-size: 0.1.4(solid-js@1.9.11) - - '@corvu/otp-field@0.1.4(solid-js@1.9.11)': - dependencies: - '@corvu/utils': 0.4.2(solid-js@1.9.11) - solid-js: 1.9.11 - - '@corvu/popover@0.2.0(solid-js@1.9.11)': - dependencies: - '@corvu/dialog': 0.2.4(solid-js@1.9.11) - '@corvu/utils': 0.3.2(solid-js@1.9.11) - '@floating-ui/dom': 1.7.6 - solid-js: 1.9.11 - - '@corvu/resizable@0.2.5(solid-js@1.9.11)': - dependencies: - '@corvu/utils': 0.4.2(solid-js@1.9.11) - solid-js: 1.9.11 - - '@corvu/tooltip@0.2.2(solid-js@1.9.11)': - dependencies: - '@corvu/utils': 0.4.2(solid-js@1.9.11) - '@floating-ui/dom': 1.7.6 - solid-dismissible: 0.1.1(solid-js@1.9.11) - solid-js: 1.9.11 - solid-presence: 0.2.0(solid-js@1.9.11) - - '@corvu/utils@0.3.2(solid-js@1.9.11)': - dependencies: - '@floating-ui/dom': 1.7.6 - solid-js: 1.9.11 - '@corvu/utils@0.4.2(solid-js@1.9.11)': dependencies: '@floating-ui/dom': 1.7.6 @@ -2451,12 +2293,6 @@ snapshots: '@solid-primitives/utils': 6.4.0(solid-js@1.9.11) solid-js: 1.9.11 - '@solid-primitives/memo@1.4.5(solid-js@1.9.11)': - dependencies: - '@solid-primitives/scheduled': 1.5.3(solid-js@1.9.11) - '@solid-primitives/utils': 6.4.0(solid-js@1.9.11) - solid-js: 1.9.11 - '@solid-primitives/props@3.2.3(solid-js@1.9.11)': dependencies: '@solid-primitives/utils': 6.4.0(solid-js@1.9.11) @@ -2480,10 +2316,6 @@ snapshots: '@solid-primitives/utils': 6.4.0(solid-js@1.9.11) solid-js: 1.9.11 - '@solid-primitives/scheduled@1.5.3(solid-js@1.9.11)': - dependencies: - solid-js: 1.9.11 - '@solid-primitives/static-store@0.1.3(solid-js@1.9.11)': dependencies: '@solid-primitives/utils': 6.4.0(solid-js@1.9.11) @@ -2740,19 +2572,6 @@ snapshots: cookie@1.1.1: {} - corvu@0.7.2(solid-js@1.9.11): - dependencies: - '@corvu/accordion': 0.2.5(solid-js@1.9.11) - '@corvu/calendar': 0.1.2(solid-js@1.9.11) - '@corvu/dialog': 0.2.4(solid-js@1.9.11) - '@corvu/disclosure': 0.2.2(solid-js@1.9.11) - '@corvu/drawer': 0.2.4(solid-js@1.9.11) - '@corvu/otp-field': 0.1.4(solid-js@1.9.11) - '@corvu/popover': 0.2.0(solid-js@1.9.11) - '@corvu/resizable': 0.2.5(solid-js@1.9.11) - '@corvu/tooltip': 0.2.2(solid-js@1.9.11) - solid-js: 1.9.11 - csstype@3.2.3: {} daisyui@5.5.19: {} @@ -3048,37 +2867,17 @@ snapshots: siginfo@2.0.0: {} - solid-dismissible@0.1.1(solid-js@1.9.11): - dependencies: - '@corvu/utils': 0.4.2(solid-js@1.9.11) - solid-js: 1.9.11 - - solid-focus-trap@0.1.9(solid-js@1.9.11): - dependencies: - '@corvu/utils': 0.4.2(solid-js@1.9.11) - solid-js: 1.9.11 - solid-js@1.9.11: dependencies: csstype: 3.2.3 seroval: 1.5.1 seroval-plugins: 1.5.1(seroval@1.5.1) - solid-list@0.3.0(solid-js@1.9.11): - dependencies: - '@corvu/utils': 0.4.2(solid-js@1.9.11) - solid-js: 1.9.11 - solid-presence@0.1.8(solid-js@1.9.11): dependencies: '@corvu/utils': 0.4.2(solid-js@1.9.11) solid-js: 1.9.11 - solid-presence@0.2.0(solid-js@1.9.11): - dependencies: - '@corvu/utils': 0.4.2(solid-js@1.9.11) - solid-js: 1.9.11 - solid-prevent-scroll@0.1.10(solid-js@1.9.11): dependencies: '@corvu/utils': 0.4.2(solid-js@1.9.11) @@ -3093,11 +2892,6 @@ snapshots: transitivePeerDependencies: - supports-color - solid-transition-size@0.1.4(solid-js@1.9.11): - dependencies: - '@corvu/utils': 0.3.2(solid-js@1.9.11) - solid-js: 1.9.11 - source-map-js@1.2.1: {} stackback@0.0.2: {} diff --git a/src/app/components/shared/NotificationDrawer.tsx b/src/app/components/shared/NotificationDrawer.tsx index 58d08e00..01ee0b48 100644 --- a/src/app/components/shared/NotificationDrawer.tsx +++ b/src/app/components/shared/NotificationDrawer.tsx @@ -1,5 +1,5 @@ import { createMemo, For, Show } from "solid-js"; -import Drawer from "corvu/drawer"; +import { Dialog } from "@kobalte/core/dialog"; import { getNotifications, markAllAsRead, @@ -25,15 +25,16 @@ export default function NotificationDrawer(props: NotificationDrawerProps) { } return ( - !open && props.onClose()} side="right"> - - - + !open && props.onClose()} modal> + + + + Recent system notifications and errors {/* Header */}
- + Notifications - + - @@ -59,7 +60,7 @@ export default function NotificationDrawer(props: NotificationDrawerProps) { clip-rule="evenodd" /> - +
{/* Notification list */} @@ -129,8 +130,8 @@ export default function NotificationDrawer(props: NotificationDrawerProps) { -
-
-
+ + + ); } diff --git a/src/app/index.css b/src/app/index.css index 926851e2..fd89cab7 100644 --- a/src/app/index.css +++ b/src/app/index.css @@ -92,21 +92,20 @@ animation: toast-slide-out 0.3s ease-in forwards; } -@utility animate-drawer-in { - animation: drawer-slide-in 0.3s ease-out forwards; -} - -@utility animate-drawer-out { +/* Kobalte Dialog drawer animations — uses data-attribute selectors instead of @utility + because Kobalte sets [data-expanded]/[data-closed] based on open/closed state */ +.drawer-content[data-closed] { animation: drawer-slide-out 0.3s ease-in forwards; } - -@utility animate-overlay-in { - animation: overlay-fade-in 0.3s ease-out forwards; +.drawer-content[data-expanded] { + animation: drawer-slide-in 0.3s ease-out forwards; } - -@utility animate-overlay-out { +.drawer-overlay[data-closed] { animation: overlay-fade-out 0.3s ease-in forwards; } +.drawer-overlay[data-expanded] { + animation: overlay-fade-in 0.3s ease-out forwards; +} /* ── Kobalte → daisyUI bridges ───────────────────────────────────────────── */ @@ -129,8 +128,8 @@ @media (prefers-reduced-motion: reduce) { .animate-slow-pulse, .animate-toast-in, .animate-toast-out, - .animate-drawer-in, .animate-drawer-out, - .animate-overlay-in, .animate-overlay-out, + .drawer-content[data-expanded], .drawer-content[data-closed], + .drawer-overlay[data-expanded], .drawer-overlay[data-closed], .animate-shimmer, .animate-flash, .animate-reorder-highlight, .loading { animation: none; diff --git a/tests/components/layout/Header.test.tsx b/tests/components/layout/Header.test.tsx index 79f6e3e6..d3bfb788 100644 --- a/tests/components/layout/Header.test.tsx +++ b/tests/components/layout/Header.test.tsx @@ -145,7 +145,7 @@ describe("Header", () => { const bellBtn = screen.getByLabelText("Notifications"); await user.click(bellBtn); expect(bellBtn.getAttribute("aria-expanded")).toBe("true"); - // Close via drawer close button (corvu sets pointer-events:none on body while open) + // Close via drawer close button (Kobalte Dialog modal sets pointer-events:none on body while open) const closeBtn = screen.getByLabelText("Close notifications"); fireEvent.click(closeBtn); expect(bellBtn.getAttribute("aria-expanded")).toBe("false"); diff --git a/tests/components/shared/NotificationDrawer.test.tsx b/tests/components/shared/NotificationDrawer.test.tsx index 5f6c5af0..94f70dd2 100644 --- a/tests/components/shared/NotificationDrawer.test.tsx +++ b/tests/components/shared/NotificationDrawer.test.tsx @@ -1,6 +1,5 @@ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; import { render, screen, fireEvent } from "@solidjs/testing-library"; -import userEvent from "@testing-library/user-event"; import { createSignal } from "solid-js"; import { pushNotification, @@ -54,6 +53,7 @@ describe("NotificationDrawer", () => { vi.advanceTimersByTime(0); expect(screen.getByText(/Something failed/)).toBeDefined(); expect(screen.getByText(/api/)).toBeDefined(); + expect(screen.queryByText("(will retry)")).toBeNull(); }); it("shows newest notification first in the list", () => { @@ -108,16 +108,16 @@ describe("NotificationDrawer", () => { expect(isMuted("search")).toBe(true); }); - it("calls onClose when overlay backdrop is clicked", async () => { - const user = userEvent.setup({ delay: null }); + it("calls onClose when overlay backdrop is clicked", () => { + // Kobalte dismisses via document-level capture-phase pointerdown (createInteractOutside), + // not an overlay click handler. data-testid targets the overlay because Dialog.Overlay + // has no ARIA role to query by. const onClose = vi.fn(); render(() => ); vi.advanceTimersByTime(0); - // corvu drawer overlay - const overlay = document.body.querySelector("[data-corvu-drawer-overlay]") as HTMLElement; - expect(overlay).not.toBeNull(); - await user.click(overlay); - expect(onClose).toHaveBeenCalled(); + const overlay = screen.getByTestId("notification-overlay"); + fireEvent.pointerDown(overlay); + expect(onClose).toHaveBeenCalledTimes(1); }); it("calls onClose when X button is clicked", () => { @@ -125,7 +125,7 @@ describe("NotificationDrawer", () => { render(() => ); vi.advanceTimersByTime(0); fireEvent.click(screen.getByLabelText("Close notifications")); - expect(onClose).toHaveBeenCalled(); + expect(onClose).toHaveBeenCalledTimes(1); }); it("shows empty state text when no notifications", () => { @@ -163,6 +163,35 @@ describe("NotificationDrawer", () => { render(() => ); vi.advanceTimersByTime(0); fireEvent.keyDown(document, { key: "Escape" }); - expect(onClose).toHaveBeenCalled(); + expect(onClose).toHaveBeenCalledTimes(1); + }); + + it("shows retryable indicator when notification is retryable", () => { + pushNotification("api", "Transient failure", "error", true); + renderDrawer(true); + vi.advanceTimersByTime(0); + expect(screen.queryByText("(will retry)")).not.toBeNull(); + }); + + it("marks dialog as closed when open transitions from true to false", () => { + // solid-presence waits for animationend to unmount; happy-dom has no CSS engine + // so the element stays in DOM with data-closed instead of being removed + const { setIsOpen } = renderDrawer(true); + vi.advanceTimersByTime(0); + const dialog = screen.getByRole("dialog"); + expect(dialog.hasAttribute("data-closed")).toBe(false); + setIsOpen(false); + vi.advanceTimersByTime(0); + expect(dialog.hasAttribute("data-closed")).toBe(true); + }); + + it("has accessible description for screen readers", () => { + renderDrawer(true); + vi.advanceTimersByTime(0); + const dialog = screen.getByRole("dialog"); + const descId = dialog.getAttribute("aria-describedby"); + expect(descId).toBeTruthy(); + const descEl = document.getElementById(descId!); + expect(descEl?.textContent).toContain("Recent system notifications and errors"); }); }); diff --git a/tsconfig.json b/tsconfig.json index 1a1a216d..3dc63ab1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,8 @@ "noUnusedLocals": true, "noUnusedParameters": true, "types": ["vite/client"], - "lib": ["ESNext", "DOM", "DOM.Iterable"] + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "skipLibCheck": true }, "include": ["src/**/*", "tests/**/*"], "exclude": ["node_modules", "dist"]