@noya-app/noya-designsystem 0.1.76 → 0.1.77

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.76",
3
+ "version": "0.1.77",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",
@@ -23,10 +23,10 @@
23
23
  "@base-ui-components/react": "1.0.0-beta.4",
24
24
  "@dnd-kit/core": "6.3.1",
25
25
  "@dnd-kit/sortable": "10.0.0",
26
- "@noya-app/noya-colorpicker": "0.1.32",
26
+ "@noya-app/noya-colorpicker": "0.1.33",
27
27
  "@noya-app/noya-utils": "0.1.9",
28
- "@noya-app/noya-geometry": "0.1.16",
29
- "@noya-app/noya-icons": "0.1.16",
28
+ "@noya-app/noya-geometry": "0.1.17",
29
+ "@noya-app/noya-icons": "0.1.17",
30
30
  "@noya-app/noya-keymap": "0.1.4",
31
31
  "@noya-app/noya-tailwind-config": "0.1.9",
32
32
  "radix-ui": "1.4.2",
@@ -13,12 +13,15 @@ export interface BaseToolbarProps {
13
13
  className?: string;
14
14
  innerClassName?: string;
15
15
  enableAbsoluteBreakpoint?: boolean;
16
+ /** Whether to enable container query classes. @default true */
17
+ enableContainerQuery?: boolean;
16
18
  }
17
19
 
18
- const toolbarStyle = {
20
+ /** Shared toolbar style that sets button and input backgrounds to transparent */
21
+ export const toolbarTransparentStyle = {
19
22
  [cssVarNames.colors.buttonBackground]: "transparent",
20
23
  [cssVarNames.colors.inputBackground]: "transparent",
21
- };
24
+ } as const;
22
25
 
23
26
  export function BaseToolbarContainer({
24
27
  children,
@@ -34,7 +37,10 @@ export function BaseToolbarContainer({
34
37
  enableAbsoluteBreakpoint?: boolean;
35
38
  }) {
36
39
  return (
37
- <div className={cx("n-flex n-flex-col", className)} style={toolbarStyle}>
40
+ <div
41
+ className={cx("n-flex n-flex-col", className)}
42
+ style={toolbarTransparentStyle}
43
+ >
38
44
  <div
39
45
  className={cx(
40
46
  "n-flex n-items-center n-bg-sidebar-background n-flex-none n-relative",
@@ -61,10 +67,12 @@ export function BaseToolbar({
61
67
  className,
62
68
  innerClassName,
63
69
  enableAbsoluteBreakpoint = true,
70
+ enableContainerQuery = true,
64
71
  }: BaseToolbarProps) {
65
72
  const childrenContainerClassName = cx(
66
73
  "n-flex n-items-center n-justify-center n-text-text-muted n-pointer-events-none n-inset-0 n-pl-1 n-pr-1 n-min-w-0",
67
- "@xl/toolbar:!n-absolute @xl/toolbar:!n-pl-0 @xl/toolbar:!n-pr-0"
74
+ enableContainerQuery &&
75
+ "@xl/toolbar:!n-absolute @xl/toolbar:!n-pl-0 @xl/toolbar:!n-pr-0"
68
76
  );
69
77
 
70
78
  const childrenElement = children && (
@@ -93,8 +101,14 @@ export function BaseToolbar({
93
101
  </>
94
102
  )}
95
103
  <Spacer.Horizontal size={10} />
104
+ {leftElement && !enableContainerQuery && (
105
+ <>
106
+ {leftElement}
107
+ {childrenElement && <Spacer.Horizontal size={10} />}
108
+ </>
109
+ )}
96
110
  {childrenElement}
97
- {leftElement && (
111
+ {leftElement && enableContainerQuery && (
98
112
  <>
99
113
  {childrenElement && (
100
114
  <>
@@ -38,6 +38,7 @@ export type DropdownRootProps<T extends string> = Omit<
38
38
  onCloseAutoFocus?: React.EventHandler<SyntheticEvent<unknown>>;
39
39
  emptyState?: React.ReactNode;
40
40
  contentStyle?: React.CSSProperties;
41
+ modal?: boolean;
41
42
  } & Pick<
42
43
  ComponentProps<typeof RadixDropdownMenu.Content>,
43
44
  "side" | "sideOffset" | "align" | "alignOffset" | "collisionPadding"
@@ -75,6 +76,7 @@ const DropdownMenuRoot = forwardRefGeneric(function DropdownMenuRoot<
75
76
  contentStyle,
76
77
  title,
77
78
  icon,
79
+ modal = true,
78
80
  } = props;
79
81
 
80
82
  const [open, setOpen] = useControlledOrUncontrolled({
@@ -105,7 +107,7 @@ const DropdownMenuRoot = forwardRefGeneric(function DropdownMenuRoot<
105
107
  );
106
108
 
107
109
  return (
108
- <RadixDropdownMenu.Root onOpenChange={setOpen} open={open}>
110
+ <RadixDropdownMenu.Root onOpenChange={setOpen} open={open} modal={modal}>
109
111
  <RadixDropdownMenu.Trigger
110
112
  ref={forwardedRef as ForwardedRef<any>}
111
113
  asChild
@@ -0,0 +1,40 @@
1
+ import { AffineTransform } from "@noya-app/noya-geometry";
2
+ import React from "react";
3
+
4
+ interface GridBackgroundProps {
5
+ transform?: AffineTransform;
6
+ /** @default 10 */
7
+ gridSize?: number;
8
+ dotSize?: number;
9
+ }
10
+
11
+ export const GridBackground = ({
12
+ transform = AffineTransform.identity,
13
+ gridSize: gridSizeProp = 10,
14
+ dotSize = 1,
15
+ }: GridBackgroundProps) => {
16
+ const scale = transform.scaleComponents.x;
17
+ const isScaling = scale !== 1 && transform.scaleComponents.y !== 1;
18
+ const gridSize = isScaling && scale > 1 ? gridSizeProp * scale : gridSizeProp;
19
+ return (
20
+ <div className="n-absolute n-inset-0 n-pointer-events-none n-overflow-hidden">
21
+ <svg
22
+ className="n-absolute n-inset-0 n-w-full n-h-full"
23
+ style={{ opacity: 0.5 }}
24
+ >
25
+ <defs>
26
+ <pattern
27
+ id="dot-pattern"
28
+ width={gridSize}
29
+ height={gridSize}
30
+ patternUnits="userSpaceOnUse"
31
+ patternTransform={`translate(${transform.tx % gridSize} ${transform.ty % gridSize})`}
32
+ >
33
+ <rect x={0} y={0} width={dotSize} height={dotSize} fill="#9ca3af" />
34
+ </pattern>
35
+ </defs>
36
+ <rect width="100%" height="100%" fill="url(#dot-pattern)" />
37
+ </svg>
38
+ </div>
39
+ );
40
+ };
@@ -0,0 +1,97 @@
1
+ "use client";
2
+
3
+ import React, { useMemo } from "react";
4
+ import { cx } from "../utils/classNames";
5
+ import { toolbarTransparentStyle } from "./BaseToolbar";
6
+
7
+ export type OverlayLayout = "bottomCenterTopRight" | "topLeftTopRight";
8
+
9
+ export interface OverlayToolbarProps {
10
+ left?: React.ReactNode;
11
+ right?: React.ReactNode;
12
+ overlayLayout?: OverlayLayout;
13
+ className?: string;
14
+ style?: React.CSSProperties;
15
+ }
16
+
17
+ const panelClassName =
18
+ "n-pointer-events-auto n-inline-flex n-items-center n-gap-toolbar-separator n-rounded-3xl n-border n-border-divider n-bg-sidebar-background n-px-3 n-py-2.5 n-shadow-2xl";
19
+
20
+ export function OverlayToolbar({
21
+ left,
22
+ right,
23
+ overlayLayout = "bottomCenterTopRight",
24
+ className,
25
+ style,
26
+ }: OverlayToolbarProps) {
27
+ const resolvedLayout =
28
+ overlayLayout === "topLeftTopRight"
29
+ ? "topLeftTopRight"
30
+ : "bottomCenterTopRight";
31
+
32
+ // Merge transparent backgrounds with custom style
33
+ const panelStyle = useMemo(
34
+ () => ({
35
+ ...toolbarTransparentStyle,
36
+ ...style,
37
+ }),
38
+ [style]
39
+ );
40
+
41
+ const renderLeftPanel = (overlayId: string) => {
42
+ if (!left) return null;
43
+
44
+ return (
45
+ <div
46
+ className={panelClassName}
47
+ data-embedded-overlay-id={overlayId}
48
+ style={panelStyle}
49
+ >
50
+ {left}
51
+ </div>
52
+ );
53
+ };
54
+
55
+ return (
56
+ <>
57
+ {/* Left items panel */}
58
+ {resolvedLayout === "bottomCenterTopRight" ? (
59
+ <div
60
+ className={cx(
61
+ "n-pointer-events-none n-absolute n-bottom-0 n-left-0 n-right-0 n-flex n-justify-center n-px-6 n-pb-6",
62
+ className
63
+ )}
64
+ >
65
+ {renderLeftPanel("noya-toolbar-bottom-center")}
66
+ </div>
67
+ ) : (
68
+ <div
69
+ className={cx(
70
+ "n-pointer-events-none n-absolute n-top-0 n-left-0 n-px-4 sm:!n-px-6 n-pt-4 sm:!n-pt-6",
71
+ className
72
+ )}
73
+ >
74
+ {renderLeftPanel("noya-toolbar-top-left")}
75
+ </div>
76
+ )}
77
+
78
+ {/* Right items panel */}
79
+ {right && (
80
+ <div
81
+ className={cx(
82
+ "n-pointer-events-none n-absolute n-top-0 n-right-0 n-px-4 sm:!n-px-6 n-pt-4 sm:!n-pt-6",
83
+ className
84
+ )}
85
+ >
86
+ <div
87
+ className={panelClassName}
88
+ data-embedded-overlay-id="noya-toolbar-top-right"
89
+ style={panelStyle}
90
+ >
91
+ {right}
92
+ </div>
93
+ </div>
94
+ )}
95
+ </>
96
+ );
97
+ }
@@ -92,7 +92,7 @@ const SegmentedControlItem = forwardRef(function SegmentedControlItem<
92
92
  variant === "tabs"
93
93
  ? ""
94
94
  : variant === "buttons"
95
- ? "focus-visible:n-ring-2 focus-visible:n-ring-primary focus:n-shadow-[0_0_0_1px_var(--n-popover-background)_inset] n-transition-all"
95
+ ? "focus:n-ring-2 focus:n-ring-primary focus:n-shadow-[0_0_0_1px_var(--n-popover-background)_inset] n-transition-all"
96
96
  : colorScheme === "secondary"
97
97
  ? "focus:n-ring-2 focus:n-ring-secondary focus:n-ring-offset-1"
98
98
  : "focus:n-ring-2 focus:n-ring-primary focus:n-ring-offset-1",
@@ -116,7 +116,7 @@ const SegmentedControlItem = forwardRef(function SegmentedControlItem<
116
116
  variant === "default"
117
117
  ? "n-px-1 n-rounded n-border-r n-border-divider last:n-border-r-0"
118
118
  : variant === "buttons"
119
- ? "n-px-1 n-rounded-sm n-max-w-fit"
119
+ ? "n-px-1.5 n-rounded-sm n-max-w-fit"
120
120
  : "n-px-1.5 n-border-y-2 n-border-y-transparent aria-checked:n-border-b-primary",
121
121
  // icon only buttons
122
122
  variant === "buttons" && title === "" && icon && "n-min-w-input-height",
@@ -356,6 +356,7 @@ function defaultRenderHeader({
356
356
  innerClassName="n-px-1"
357
357
  showDivider={showDivider}
358
358
  enableAbsoluteBreakpoint={false}
359
+ enableContainerQuery={false}
359
360
  left={
360
361
  showBackButton && (
361
362
  <Button
@@ -49,6 +49,7 @@ export const ToolbarMenuDropdown = memoGeneric(function ToolbarMenuDropdown<
49
49
  open={open}
50
50
  onOpenChange={setOpen}
51
51
  items={item.items}
52
+ modal={false}
52
53
  onSelect={(value) => {
53
54
  if (onSelectMenuItem && value) {
54
55
  onSelectMenuItem(value);
@@ -299,6 +300,21 @@ export interface ToolbarProps<T extends string = string> {
299
300
  dividerOverflow?: number;
300
301
  }
301
302
 
303
+ export function useBindKeyboardShortcutsForMenuItems<T extends string>(
304
+ items: MenuItem<T>[],
305
+ onSelectMenuItem?: (value: T) => void
306
+ ) {
307
+ return useKeyboardShortcuts(
308
+ React.useMemo(
309
+ () =>
310
+ onSelectMenuItem
311
+ ? getKeyboardShortcutsForMenuItems(items, onSelectMenuItem)
312
+ : {},
313
+ [items, onSelectMenuItem]
314
+ )
315
+ );
316
+ }
317
+
302
318
  export function Toolbar<T extends string = string>({
303
319
  children,
304
320
  logo,
@@ -314,14 +330,9 @@ export function Toolbar<T extends string = string>({
314
330
  [leftMenuItems, rightMenuItems]
315
331
  );
316
332
 
317
- useKeyboardShortcuts(
318
- React.useMemo(
319
- () =>
320
- onSelectMenuItem && shouldBindKeyboardShortcuts
321
- ? getKeyboardShortcutsForMenuItems(allMenuItems, onSelectMenuItem)
322
- : {},
323
- [allMenuItems, onSelectMenuItem, shouldBindKeyboardShortcuts]
324
- )
333
+ useBindKeyboardShortcutsForMenuItems(
334
+ allMenuItems,
335
+ shouldBindKeyboardShortcuts ? onSelectMenuItem : undefined
325
336
  );
326
337
 
327
338
  return (
@@ -13,7 +13,7 @@ const UserPointerContainer = memo(function UserPointerContainer({
13
13
  className,
14
14
  style,
15
15
  }: {
16
- point: Point;
16
+ point?: Point;
17
17
  visible?: boolean;
18
18
  children: React.ReactNode;
19
19
  className?: string;
@@ -22,7 +22,9 @@ const UserPointerContainer = memo(function UserPointerContainer({
22
22
  return (
23
23
  <div
24
24
  style={{
25
- transform: `translate(${Math.round(point.x)}px, ${Math.round(point.y)}px)`,
25
+ ...(point && {
26
+ transform: `translate(${Math.round(point.x)}px, ${Math.round(point.y)}px)`,
27
+ }),
26
28
  transition: "transform 0.075s, opacity 0.2s",
27
29
  ...style,
28
30
  }}
@@ -117,7 +119,7 @@ export type UserPointerProps = {
117
119
  name?: string;
118
120
  /** label to display in the tooltip next to the Pointer */
119
121
  visible?: boolean;
120
- point: Point;
122
+ point?: Point;
121
123
  /** defaults to a random color based on the key, but can optionally be overridden */
122
124
  backgroundColor?: string;
123
125
  style?: React.CSSProperties;
@@ -16,6 +16,7 @@ import React, {
16
16
  } from "react";
17
17
  import { cx } from "../../utils/classNames";
18
18
  import { MenuItem } from "../internal/Menu";
19
+ import { OverlayLayout, OverlayToolbar } from "../OverlayToolbar";
19
20
  import { DrawerWorkspaceLayout } from "./DrawerWorkspaceLayout";
20
21
  import { PanelWorkspaceLayout } from "./PanelWorkspaceLayout";
21
22
  import { renderPanelChildren } from "./renderPanelChildren";
@@ -31,6 +32,8 @@ export type SideType = "auto" | "panel" | "drawer";
31
32
 
32
33
  export type DetectSizeType = "container" | "window";
33
34
 
35
+ export type ToolbarVariant = "default" | "overlay";
36
+
34
37
  export interface WorkspaceLayoutProps<
35
38
  LeftTab extends string,
36
39
  RightTab extends string,
@@ -54,6 +57,24 @@ export interface WorkspaceLayoutProps<
54
57
  right?: RenderPanel<RightTab>;
55
58
  rightOptions?: SideOptions;
56
59
  toolbar?: React.ReactNode;
60
+ /**
61
+ * Toolbar variant - "default" renders above content, "overlay" renders floating panels
62
+ * @default "default"
63
+ */
64
+ toolbarVariant?: ToolbarVariant;
65
+ /**
66
+ * Content for the left/bottom-center overlay panel (when toolbarVariant is "overlay")
67
+ */
68
+ overlayToolbarLeft?: React.ReactNode;
69
+ /**
70
+ * Content for the right overlay panel (when toolbarVariant is "overlay")
71
+ */
72
+ overlayToolbarRight?: React.ReactNode;
73
+ /**
74
+ * Layout configuration for overlay toolbar
75
+ * @default "bottomCenterTopRight"
76
+ */
77
+ overlayLayout?: OverlayLayout;
57
78
  sideType?: SideType;
58
79
  sideTypeBreakpoint?: number;
59
80
  detectSize?: DetectSizeType;
@@ -125,6 +146,10 @@ export const WorkspaceLayout = forwardRefGeneric(function WorkspaceLayout<
125
146
  className,
126
147
  style,
127
148
  toolbar,
149
+ toolbarVariant = "default",
150
+ overlayToolbarLeft,
151
+ overlayToolbarRight,
152
+ overlayLayout,
128
153
  children,
129
154
  sideType = "auto",
130
155
  sideTypeBreakpoint = 800,
@@ -422,6 +447,8 @@ export const WorkspaceLayout = forwardRefGeneric(function WorkspaceLayout<
422
447
  const readyToRender =
423
448
  detectSize === "window" || (containerSize && containerSize.width > 0);
424
449
 
450
+ const isOverlay = toolbarVariant === "overlay";
451
+
425
452
  return (
426
453
  <div
427
454
  ref={containerRef}
@@ -433,27 +460,36 @@ export const WorkspaceLayout = forwardRefGeneric(function WorkspaceLayout<
433
460
  style={style}
434
461
  data-theme={theme}
435
462
  >
436
- {toolbar}
463
+ {!isOverlay && toolbar}
437
464
  <div className="n-flex n-flex-row n-flex-1 n-relative">
438
465
  {readyToRender && (
439
- <LayoutComponent
440
- leftPanel={leftPanel}
441
- rightPanel={rightPanel}
442
- centerPanel={centerPanel}
443
- leftTabItems={leftTabItems}
444
- leftTabValue={leftTabValue}
445
- onChangeLeftTab={setLeftTabValue}
446
- rightTabItems={rightTabItems}
447
- rightTabValue={rightTabValue}
448
- onChangeRightTab={setRightTabValue}
449
- leftSidebarOptions={leftSidebarOptions}
450
- rightSidebarOptions={rightSidebarOptions}
451
- centerPanelPercentage={centerPanelPercentage}
452
- autoSavePrefix={autoSavePrefix}
453
- leftSidebarRef={leftSidebarRef}
454
- rightSidebarRef={rightSidebarRef}
455
- compactDrawerMenu={compactDrawerMenu}
456
- />
466
+ <>
467
+ <LayoutComponent
468
+ leftPanel={leftPanel}
469
+ rightPanel={rightPanel}
470
+ centerPanel={centerPanel}
471
+ leftTabItems={leftTabItems}
472
+ leftTabValue={leftTabValue}
473
+ onChangeLeftTab={setLeftTabValue}
474
+ rightTabItems={rightTabItems}
475
+ rightTabValue={rightTabValue}
476
+ onChangeRightTab={setRightTabValue}
477
+ leftSidebarOptions={leftSidebarOptions}
478
+ rightSidebarOptions={rightSidebarOptions}
479
+ centerPanelPercentage={centerPanelPercentage}
480
+ autoSavePrefix={autoSavePrefix}
481
+ leftSidebarRef={leftSidebarRef}
482
+ rightSidebarRef={rightSidebarRef}
483
+ compactDrawerMenu={compactDrawerMenu}
484
+ />
485
+ {isOverlay && (
486
+ <OverlayToolbar
487
+ left={overlayToolbarLeft}
488
+ right={overlayToolbarRight}
489
+ overlayLayout={overlayLayout}
490
+ />
491
+ )}
492
+ </>
457
493
  )}
458
494
  </div>
459
495
  </div>
package/src/index.tsx CHANGED
@@ -31,6 +31,7 @@ export * from "./components/file-explorer/FileExplorerLayout";
31
31
  export * from "./components/FileUploadIndicator";
32
32
  export * from "./components/FloatingWindow";
33
33
  export * from "./components/Grid";
34
+ export * from "./components/GridBackground";
34
35
  export * from "./components/GridView";
35
36
  export * from "./components/IconButton";
36
37
  export * from "./components/Icons";
@@ -128,5 +129,6 @@ export * from "./components/DimensionInput";
128
129
  export * as InspectorPrimitives from "./components/InspectorPrimitives";
129
130
 
130
131
  export * from "./components/BaseToolbar";
132
+ export * from "./components/OverlayToolbar";
131
133
  export * from "./components/Toolbar";
132
134
  export * from "./components/ToolbarDrawer";
@@ -1,2 +1,22 @@
1
1
  export const proseTheme =
2
2
  "n-prose n-prose-sm n-max-w-none [[data-theme='dark']_&]:n-prose-invert prose-a:n-text-blue-600";
3
+
4
+ export const whiteboardProseTheme = (isDark: boolean) =>
5
+ [
6
+ "n-prose",
7
+ "n-prose-sm",
8
+ "n-max-w-none",
9
+ isDark ? "n-prose-invert n-text-white" : "",
10
+ "prose-p:n-m-0",
11
+ "prose-ul:n-m-0",
12
+ "prose-ol:n-m-0",
13
+ "prose-li:n-m-0",
14
+ "prose-li:n-p-0",
15
+ "prose-a:n-text-inherit",
16
+ "prose-strong:n-text-inherit",
17
+ "prose-em:n-italic",
18
+ "prose-ol:n-list-inside",
19
+ "prose-ul:n-list-inside",
20
+ "prose-ol:n-pl-0",
21
+ "prose-ul:n-pl-0",
22
+ ].join(" ");
@@ -7,7 +7,12 @@ const config = {
7
7
  ...noyaConfig,
8
8
  prefix: "n-",
9
9
  plugins: [containerQueries, typography],
10
- safelist: [...noyaConfig.safelist, "dark:n-prose-invert"],
10
+ safelist: [
11
+ ...noyaConfig.safelist,
12
+ "n-prose-invert",
13
+ "dark:n-prose-invert",
14
+ "prose-p:n-m-0",
15
+ ],
11
16
  } satisfies Config;
12
17
 
13
18
  export default config;