@noya-app/noya-designsystem 0.1.92 → 0.1.94

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noya-app/noya-designsystem",
3
- "version": "0.1.92",
3
+ "version": "0.1.94",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",
@@ -25,7 +25,7 @@
25
25
  "lint": "eslint . --max-warnings 0"
26
26
  },
27
27
  "dependencies": {
28
- "@base-ui/react": "1.0.0",
28
+ "@base-ui/react": "1.7.0",
29
29
  "@dnd-kit/core": "6.3.1",
30
30
  "@dnd-kit/sortable": "10.0.0",
31
31
  "@floating-ui/react": "^0.27.0",
@@ -236,12 +236,16 @@ function MenuViewport<T extends string>({
236
236
  }
237
237
  return null;
238
238
 
239
- default:
239
+ default: {
240
+ const accessibilityProps = getCheckedMenuItemAccessibilityProps(
241
+ item.checked
242
+ );
240
243
  return (
241
244
  <div key={item.value} className="n-px-1">
242
245
  <Menu.Item
243
246
  disabled={item.disabled}
244
247
  className={itemStyle}
248
+ {...accessibilityProps}
245
249
  onClick={() => {
246
250
  if (onSelect && item.value) {
247
251
  // Delay onSelect to allow menu to close and focus to settle
@@ -262,12 +266,21 @@ function MenuViewport<T extends string>({
262
266
  </Menu.Item>
263
267
  </div>
264
268
  );
269
+ }
265
270
  }
266
271
  })}
267
272
  </>
268
273
  );
269
274
  }
270
275
 
276
+ export function getCheckedMenuItemAccessibilityProps(
277
+ checked: boolean | undefined
278
+ ) {
279
+ return checked === undefined
280
+ ? {}
281
+ : ({ role: "menuitemcheckbox", "aria-checked": checked } as const);
282
+ }
283
+
271
284
  const DropdownMenuRoot = forwardRefGeneric(function DropdownMenuRoot<
272
285
  T extends string,
273
286
  >(props: DropdownMenuProps<T>, forwardedRef: ForwardedRef<HTMLElement>) {
@@ -39,7 +39,7 @@ function ToastContent({ toast }: { toast: ToastObject }) {
39
39
  return (
40
40
  <ToastPrimitive.Root
41
41
  toast={toast}
42
- className="n-relative n-flex n-items-start n-gap-2 n-rounded n-text-sm n-bg-popover-background n-shadow-popover n-text-text-muted n-px-3 n-py-2.5 n-pr-10"
42
+ className="n-pointer-events-auto n-relative n-flex n-items-start n-gap-2 n-rounded n-text-sm n-bg-popover-background n-shadow-popover n-text-text-muted n-px-3 n-py-2.5 n-pr-10"
43
43
  >
44
44
  <div className="n-flex n-flex-col n-gap-0.5 n-min-w-0">
45
45
  {toast.data?.title && (
@@ -87,7 +87,7 @@ function ToastProviderInner({ children }: { children: React.ReactNode }) {
87
87
  return (
88
88
  <ToastContext.Provider value={{ showToast }}>
89
89
  {children}
90
- <ToastPrimitive.Viewport className="n-fixed n-bottom-0 n-right-0 n-flex n-flex-col n-p-5 n-gap-2.5 n-min-w-[200px] n-max-w-[100vw] n-m-0 n-list-none n-z-[2147483647] n-outline-none">
90
+ <ToastPrimitive.Viewport className="n-pointer-events-none n-fixed n-bottom-0 n-right-0 n-flex n-flex-col n-p-5 n-gap-2.5 n-min-w-[200px] n-max-w-[100vw] n-m-0 n-list-none n-z-[2147483647] n-outline-none">
91
91
  {toastManager.toasts.map((toast) => (
92
92
  <ToastContent key={toast.id} toast={toast} />
93
93
  ))}
@@ -0,0 +1,19 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { getCheckedMenuItemAccessibilityProps } from "../DropdownMenu";
3
+
4
+ describe("DropdownMenu checked items", () => {
5
+ test("uses checkbox menu semantics when checked state is provided", () => {
6
+ expect(getCheckedMenuItemAccessibilityProps(false)).toEqual({
7
+ role: "menuitemcheckbox",
8
+ "aria-checked": false,
9
+ });
10
+ expect(getCheckedMenuItemAccessibilityProps(true)).toEqual({
11
+ role: "menuitemcheckbox",
12
+ "aria-checked": true,
13
+ });
14
+ });
15
+
16
+ test("leaves ordinary menu items unchanged", () => {
17
+ expect(getCheckedMenuItemAccessibilityProps(undefined)).toEqual({});
18
+ });
19
+ });
@@ -0,0 +1,138 @@
1
+ import { afterEach, describe, expect, mock, test } from "bun:test";
2
+ import React from "react";
3
+
4
+ const actualIcons = await import("@radix-ui/react-icons");
5
+
6
+ mock.module("@radix-ui/react-icons", () => ({
7
+ ...actualIcons,
8
+ Cross1Icon: (props: React.SVGProps<SVGSVGElement>) => (
9
+ <svg {...props} data-testid="cross-icon" />
10
+ ),
11
+ }));
12
+
13
+ const { act, cleanup, fireEvent, render, waitFor } = await import(
14
+ "@testing-library/react"
15
+ );
16
+ const { ToastProvider, useToast } = await import("../Toast");
17
+
18
+ afterEach(cleanup);
19
+
20
+ function ToastFixture({ onAction }: { onAction?: () => void }) {
21
+ const { showToast } = useToast();
22
+
23
+ return (
24
+ <button
25
+ onClick={() =>
26
+ showToast({
27
+ title: "Saved",
28
+ content: "Your changes were saved.",
29
+ action: onAction ? (
30
+ <button onClick={onAction}>Undo</button>
31
+ ) : undefined,
32
+ timeout: 60_000,
33
+ })
34
+ }
35
+ >
36
+ Show toast
37
+ </button>
38
+ );
39
+ }
40
+
41
+ function renderToast(onAction?: () => void) {
42
+ const view = render(
43
+ <ToastProvider>
44
+ <ToastFixture onAction={onAction} />
45
+ </ToastProvider>
46
+ );
47
+
48
+ fireEvent.click(view.getByRole("button", { name: "Show toast" }));
49
+
50
+ return view;
51
+ }
52
+
53
+ describe("ToastProvider pointer events", () => {
54
+ test("keeps the viewport pointer-transparent when no toast exists", () => {
55
+ const view = render(
56
+ <ToastProvider>
57
+ <button>Underlying control</button>
58
+ </ToastProvider>
59
+ );
60
+ const viewport = view.getByRole("region", { name: "Notifications" });
61
+
62
+ expect(viewport.classList.contains("n-pointer-events-none")).toBe(true);
63
+ expect(view.queryByRole("dialog")).toBeNull();
64
+ });
65
+
66
+ test("makes toast roots interactive inside the transparent viewport", () => {
67
+ const view = renderToast();
68
+ const viewport = view.getByRole("region", { name: "Notifications" });
69
+ const toast = view.getByRole("dialog");
70
+
71
+ expect(viewport.classList.contains("n-pointer-events-none")).toBe(true);
72
+ expect(toast.classList.contains("n-pointer-events-auto")).toBe(true);
73
+ });
74
+ });
75
+
76
+ describe("ToastProvider interactions", () => {
77
+ test("preserves action and close button interactions", async () => {
78
+ const onAction = mock(() => {});
79
+ const view = renderToast(onAction);
80
+
81
+ fireEvent.click(view.getByRole("button", { name: "Undo" }));
82
+ expect(onAction).toHaveBeenCalledTimes(1);
83
+
84
+ fireEvent.click(view.getByLabelText("Close"));
85
+ await waitFor(() => {
86
+ expect(view.queryByText("Your changes were saved.")).toBeNull();
87
+ });
88
+ });
89
+
90
+ test("preserves keyboard dismissal", async () => {
91
+ const view = renderToast();
92
+ const toast = view.getByRole("dialog");
93
+
94
+ act(() => toast.focus());
95
+ fireEvent.keyDown(toast, { key: "Escape" });
96
+
97
+ await waitFor(() => {
98
+ expect(view.queryByText("Your changes were saved.")).toBeNull();
99
+ });
100
+ });
101
+
102
+ test("preserves swipe dismissal", async () => {
103
+ const view = renderToast();
104
+ const toast = view.getByRole("dialog");
105
+
106
+ fireEvent.pointerDown(toast, {
107
+ button: 0,
108
+ clientX: 0,
109
+ clientY: 0,
110
+ pointerId: 1,
111
+ pointerType: "touch",
112
+ });
113
+ fireEvent.pointerMove(toast, {
114
+ clientX: 1,
115
+ clientY: 0,
116
+ pointerId: 1,
117
+ pointerType: "touch",
118
+ });
119
+ fireEvent.pointerMove(toast, {
120
+ clientX: 60,
121
+ clientY: 0,
122
+ movementX: 59,
123
+ movementY: 0,
124
+ pointerId: 1,
125
+ pointerType: "touch",
126
+ });
127
+ fireEvent.pointerUp(toast, {
128
+ clientX: 60,
129
+ clientY: 0,
130
+ pointerId: 1,
131
+ pointerType: "touch",
132
+ });
133
+
134
+ await waitFor(() => {
135
+ expect(view.queryByText("Your changes were saved.")).toBeNull();
136
+ });
137
+ });
138
+ });
package/src/index.css CHANGED
@@ -291,7 +291,8 @@
291
291
 
292
292
  /* Direct child pre blocks */
293
293
  .n-prose pre {
294
- background-color: var(--n-indigo-25);
294
+ color: var(--n-text);
295
+ background-color: var(--n-code-background);
295
296
  border-width: 1px;
296
297
  border-style: solid;
297
298
  border-color: var(--n-divider);