@noya-app/noya-designsystem 0.1.86 → 0.1.88

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.
@@ -0,0 +1,56 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+ import { cleanup, render } from "@testing-library/react";
3
+ import React from "react";
4
+ import { cssVarNames } from "../../theme";
5
+ import { AppToolbar } from "../AppToolbar";
6
+ import { Toolbar } from "../Toolbar";
7
+
8
+ afterEach(cleanup);
9
+
10
+ describe("Toolbar", () => {
11
+ test("renders the floating toolbar surface with transparent controls", () => {
12
+ const { getByRole } = render(
13
+ <Toolbar aria-label="Canvas tools">Controls</Toolbar>
14
+ );
15
+ const toolbar = getByRole("toolbar", { name: "Canvas tools" });
16
+
17
+ expect(toolbar.classList.contains("n-bg-popover-background")).toBe(true);
18
+ expect(toolbar.classList.contains("n-shadow-popover")).toBe(true);
19
+ expect(toolbar.classList.contains("n-gap-toolbar-separator")).toBe(true);
20
+ expect(toolbar.classList.contains("n-p-2")).toBe(true);
21
+ expect(
22
+ toolbar.style.getPropertyValue(cssVarNames.colors.buttonBackground)
23
+ ).toBe("transparent");
24
+ expect(
25
+ toolbar.style.getPropertyValue(cssVarNames.colors.inputBackground)
26
+ ).toBe("transparent");
27
+ });
28
+
29
+ test("supports compact spacing and consumer style overrides", () => {
30
+ const { getByRole } = render(
31
+ <Toolbar
32
+ aria-label="Selection tools"
33
+ size="small"
34
+ style={{ backgroundColor: "transparent" }}
35
+ />
36
+ );
37
+ const toolbar = getByRole("toolbar", { name: "Selection tools" });
38
+
39
+ expect(toolbar.classList.contains("n-gap-1")).toBe(true);
40
+ expect(toolbar.classList.contains("n-p-1")).toBe(true);
41
+ expect(toolbar.style.backgroundColor).toBe("transparent");
42
+ });
43
+ });
44
+
45
+ describe("AppToolbar", () => {
46
+ test("renders menu-driven application actions", () => {
47
+ const { getByRole } = render(
48
+ <AppToolbar
49
+ leftMenuItems={[{ title: "Undo", value: "undo" }]}
50
+ shouldBindKeyboardShortcuts={false}
51
+ />
52
+ );
53
+
54
+ expect(getByRole("button", { name: "Undo" })).toBeTruthy();
55
+ });
56
+ });
@@ -12,6 +12,7 @@ import {
12
12
  DragStartEvent,
13
13
  Over,
14
14
  PointerSensor,
15
+ PointerSensorProps,
15
16
  pointerWithin,
16
17
  useSensor,
17
18
  useSensors,
@@ -54,6 +55,41 @@ export type SharedDragProviderProps = {
54
55
  itemHitSlop?: Insets;
55
56
  };
56
57
 
58
+ export class ContinuousPointerSensor extends PointerSensor {
59
+ constructor(props: PointerSensorProps) {
60
+ const ownerDocument =
61
+ props.event.target instanceof Node
62
+ ? (props.event.target.ownerDocument ?? document)
63
+ : document;
64
+ let latestCoordinates: Point | null = null;
65
+
66
+ const trackPointer = (event: PointerEvent) => {
67
+ latestCoordinates = { x: event.clientX, y: event.clientY };
68
+ };
69
+ const cleanup = () => {
70
+ ownerDocument.removeEventListener("pointermove", trackPointer, true);
71
+ ownerDocument.removeEventListener("pointerup", cleanup, true);
72
+ ownerDocument.removeEventListener("pointercancel", cleanup, true);
73
+ };
74
+
75
+ ownerDocument.addEventListener("pointermove", trackPointer, true);
76
+ ownerDocument.addEventListener("pointerup", cleanup, true);
77
+ ownerDocument.addEventListener("pointercancel", cleanup, true);
78
+
79
+ super({
80
+ ...props,
81
+ onStart(coordinates) {
82
+ cleanup();
83
+ props.onStart(coordinates);
84
+
85
+ if (latestCoordinates) {
86
+ props.onMove(latestCoordinates);
87
+ }
88
+ },
89
+ });
90
+ }
91
+ }
92
+
57
93
  export function SharedDragProvider({
58
94
  children,
59
95
  acceptsDrop,
@@ -65,7 +101,7 @@ export function SharedDragProvider({
65
101
  itemHitSlop,
66
102
  }: SharedDragProviderProps) {
67
103
  const sensors = useSensors(
68
- useSensor(PointerSensor, {
104
+ useSensor(ContinuousPointerSensor, {
69
105
  activationConstraint: {
70
106
  distance: 4,
71
107
  },
@@ -6,7 +6,7 @@ import { cssVarNames } from "../../theme";
6
6
  import { Button } from "../Button";
7
7
  import { Divider } from "../Divider";
8
8
  import { isSelectableMenuItem, MenuItem } from "../internal/Menu";
9
- import { ToolbarMenu } from "../Toolbar";
9
+ import { ToolbarMenu } from "../AppToolbar";
10
10
  import { EDITOR_PANEL_GROUP_ID } from "./constants";
11
11
  import { LayoutProps, PanelLayoutState } from "./types";
12
12
 
@@ -209,24 +209,14 @@ export const DrawerWorkspaceLayout = memoGeneric(function DrawerWorkspaceLayout<
209
209
  {isDrawerOpen && (
210
210
  <div className="n-absolute n-inset-0 n-z-20 n-flex n-flex-col">
211
211
  {/* Drawer content */}
212
- <div className="n-flex-none n-max-h-[50vh] n-overflow-auto n-bg-sidebar-background n-border-b n-border-divider-strong n-shadow-lg">
212
+ <div className="n-relative n-flex-1 n-min-h-0 n-overflow-auto n-bg-sidebar-background n-border-b n-border-divider-strong n-shadow-lg">
213
213
  {activeDrawerPanel}
214
214
  </div>
215
- {/* Backdrop - click to close */}
216
- <div
217
- className="n-flex-1 n-bg-black/30"
218
- onClick={() =>
219
- setLayoutState({
220
- leftSidebarCollapsed: true,
221
- rightSidebarCollapsed: true,
222
- })
223
- }
224
- />
225
215
  </div>
226
216
  )}
227
217
 
228
218
  {/* Center content */}
229
- <div className="n-flex-1 n-flex n-relative n-min-h-0">
219
+ <div className="n-z-0 n-flex-1 n-flex n-relative n-min-h-0">
230
220
  {centerPanel}
231
221
  </div>
232
222
  </div>
@@ -2,7 +2,7 @@ import { memoGeneric } from "@noya-app/react-utils";
2
2
  import React, { ComponentProps, useMemo } from "react";
3
3
  import { cssVarNames } from "../../theme";
4
4
  import { MenuItem } from "../internal/Menu";
5
- import { ToolbarMenu } from "../Toolbar";
5
+ import { ToolbarMenu } from "../AppToolbar";
6
6
  import { Tooltip } from "../Tooltip";
7
7
 
8
8
  type VerticalTabMenuProps<TabValue extends string> = {
@@ -31,4 +31,68 @@ describe("DrawerWorkspaceLayout", () => {
31
31
 
32
32
  expect(getByText("Sidebar content")).toBeTruthy();
33
33
  });
34
+
35
+ test("contains absolutely positioned panel content in a full-height drawer", () => {
36
+ const leftSidebarRef = React.createRef<SidebarRef>();
37
+ const rightSidebarRef = React.createRef<SidebarRef>();
38
+ const { getByRole, getByText, queryByRole } = render(
39
+ <DrawerWorkspaceLayout
40
+ leftPanel={<div className="n-absolute n-inset-0">Sidebar content</div>}
41
+ leftTabItems={[{ title: "Navigation", value: "navigation" }]}
42
+ leftTabValue="navigation"
43
+ rightPanel={null}
44
+ centerPanel={<div>Center content</div>}
45
+ leftSidebarOptions={{}}
46
+ rightSidebarOptions={{}}
47
+ leftSidebarRef={leftSidebarRef}
48
+ rightSidebarRef={rightSidebarRef}
49
+ compactDrawerMenu
50
+ />
51
+ );
52
+
53
+ fireEvent.click(getByRole("button", { name: "Navigation" }));
54
+
55
+ const drawerContent = getByText("Sidebar content").parentElement;
56
+ expect(drawerContent).toBeTruthy();
57
+ expect(drawerContent?.classList.contains("n-relative")).toBe(true);
58
+ expect(drawerContent?.classList.contains("n-flex-1")).toBe(true);
59
+ expect(drawerContent?.classList.contains("n-min-h-0")).toBe(true);
60
+ expect(drawerContent?.classList.contains("n-h-[50vh]")).toBe(false);
61
+ expect(queryByRole("button", { name: "Close drawer" })).toBeNull();
62
+ });
63
+
64
+ test("keeps touch input on drawer content instead of an overlapping backdrop", () => {
65
+ const leftSidebarRef = React.createRef<SidebarRef>();
66
+ const rightSidebarRef = React.createRef<SidebarRef>();
67
+ let touchCount = 0;
68
+ const { getByRole, getByText, queryByRole } = render(
69
+ <DrawerWorkspaceLayout
70
+ leftPanel={<button onClick={() => touchCount++}>Drawer action</button>}
71
+ leftTabItems={[{ title: "Navigation", value: "navigation" }]}
72
+ leftTabValue="navigation"
73
+ rightPanel={null}
74
+ centerPanel={<div>Center content</div>}
75
+ leftSidebarOptions={{}}
76
+ rightSidebarOptions={{}}
77
+ leftSidebarRef={leftSidebarRef}
78
+ rightSidebarRef={rightSidebarRef}
79
+ compactDrawerMenu
80
+ />
81
+ );
82
+
83
+ fireEvent.click(getByRole("button", { name: "Navigation" }));
84
+ const action = getByRole("button", { name: "Drawer action" });
85
+ const drawer = getByText("Drawer action").parentElement?.parentElement;
86
+ const center = getByText("Center content").parentElement;
87
+
88
+ fireEvent.pointerDown(action, { pointerType: "touch" });
89
+ fireEvent.pointerUp(action, { pointerType: "touch" });
90
+ fireEvent.click(action);
91
+
92
+ expect(drawer?.classList.contains("n-z-20")).toBe(true);
93
+ expect(center?.classList.contains("n-z-0")).toBe(true);
94
+ expect(queryByRole("button", { name: "Close drawer" })).toBeNull();
95
+ expect(touchCount).toBe(1);
96
+ expect(leftSidebarRef.current?.isExpanded()).toBe(true);
97
+ });
34
98
  });
package/src/index.css CHANGED
@@ -185,6 +185,12 @@
185
185
  }
186
186
  }
187
187
 
188
+ @media (max-width: 639px) {
189
+ :root {
190
+ --n-dialog-padding: 16px;
191
+ }
192
+ }
193
+
188
194
  [data-theme="dark"] {
189
195
  --n-background: #111111;
190
196
  --n-input-background: rgb(45 43 54);
package/src/index.tsx CHANGED
@@ -10,6 +10,7 @@ export * from "./components/Banner";
10
10
  export * from "./components/blocks/ImageBlockComponent";
11
11
  export * from "./components/Breadcrumbs";
12
12
  export * from "./components/Button";
13
+ export * from "./components/Card";
13
14
  export * from "./components/Checkbox";
14
15
  export * from "./components/Chip";
15
16
  export * from "./components/Collection";
@@ -21,6 +22,7 @@ export * from "./components/CommandPalette";
21
22
  export * from "./components/connected-users-menu/ConnectedUsersMenuLayout";
22
23
  export * from "./components/ContextMenu";
23
24
  export * from "./components/Dialog";
25
+ export * from "./components/DialogNavigator";
24
26
  export * from "./components/Divider";
25
27
  export * from "./components/DraggableMenuButton";
26
28
  export * from "./components/Drawer";
@@ -75,6 +77,7 @@ export * from "./components/sorting/Sortable";
75
77
  export * from "./components/sorting/sorting";
76
78
  export * from "./components/Spacer";
77
79
  export * from "./components/StackNavigator";
80
+ export * from "./components/StatCard";
78
81
  export * from "./components/Stepper";
79
82
  export * from "./components/Switch";
80
83
  export * from "./components/Tabs";
@@ -128,6 +131,7 @@ export * from "./components/DimensionInput";
128
131
  export * as InspectorPrimitives from "./components/InspectorPrimitives";
129
132
 
130
133
  export * from "./components/BaseToolbar";
134
+ export * from "./components/AppToolbar";
131
135
  export * from "./components/OverlayToolbar";
132
136
  export * from "./components/Toolbar";
133
137
  export * from "./components/ToolbarDrawer";