@noya-app/noya-designsystem 0.1.61 → 0.1.63

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.
Files changed (43) hide show
  1. package/.eslintrc.js +1 -5
  2. package/.turbo/turbo-build.log +10 -10
  3. package/.turbo/turbo-lint.log +0 -1
  4. package/CHANGELOG.md +16 -0
  5. package/dist/index.css +1 -1
  6. package/dist/index.d.mts +147 -69
  7. package/dist/index.d.ts +147 -69
  8. package/dist/index.js +1347 -1063
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.mjs +756 -473
  11. package/dist/index.mjs.map +1 -1
  12. package/package.json +4 -4
  13. package/src/components/Button.tsx +28 -20
  14. package/src/components/DimensionInput.tsx +1 -1
  15. package/src/components/DraggableMenuButton.tsx +20 -4
  16. package/src/components/DropdownMenu.tsx +31 -5
  17. package/src/components/IconButton.tsx +2 -2
  18. package/src/components/InputField.tsx +5 -5
  19. package/src/components/Label.tsx +0 -2
  20. package/src/components/LabeledField.tsx +36 -34
  21. package/src/components/Popover.tsx +12 -3
  22. package/src/components/SegmentedControl.tsx +1 -1
  23. package/src/components/SelectMenu.tsx +3 -3
  24. package/src/components/SelectionToolbar.tsx +28 -7
  25. package/src/components/Slider.tsx +1 -1
  26. package/src/components/Spacer.tsx +0 -1
  27. package/src/components/Tabs.tsx +46 -0
  28. package/src/components/Toolbar.tsx +106 -11
  29. package/src/components/Tooltip.tsx +8 -8
  30. package/src/components/file-explorer/FileExplorerLayout.tsx +12 -6
  31. package/src/components/internal/Menu.tsx +19 -2
  32. package/src/components/internal/MenuViewport.tsx +4 -3
  33. package/src/components/workspace/DrawerWorkspaceLayout.tsx +7 -3
  34. package/src/components/workspace/PanelWorkspaceLayout.tsx +103 -3
  35. package/src/components/workspace/WorkspaceLayout.tsx +96 -15
  36. package/src/components/workspace/renderPanelChildren.tsx +12 -0
  37. package/src/components/workspace/types.ts +19 -3
  38. package/src/hooks/useLabel.ts +10 -10
  39. package/src/hooks/usePreservePanelSize.tsx +1 -1
  40. package/src/index.css +10 -6
  41. package/src/index.tsx +2 -0
  42. package/src/utils/editableBlockStyles.ts +10 -0
  43. package/src/hooks/useWindowSize.tsx +0 -26
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noya-app/noya-designsystem",
3
- "version": "0.1.61",
3
+ "version": "0.1.63",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",
@@ -22,12 +22,12 @@
22
22
  "dependencies": {
23
23
  "@dnd-kit/core": "6.3.1",
24
24
  "@dnd-kit/sortable": "10.0.0",
25
- "@noya-app/noya-colorpicker": "0.1.25",
25
+ "@noya-app/noya-colorpicker": "0.1.26",
26
26
  "@noya-app/noya-utils": "0.1.5",
27
27
  "@noya-app/noya-geometry": "0.1.12",
28
- "@noya-app/noya-icons": "0.1.10",
28
+ "@noya-app/noya-icons": "0.1.11",
29
29
  "@noya-app/noya-keymap": "0.1.3",
30
- "@noya-app/noya-tailwind-config": "0.1.4",
30
+ "@noya-app/noya-tailwind-config": "0.1.5",
31
31
  "@radix-ui/primitive": "^1.0.0",
32
32
  "@radix-ui/react-avatar": "^1.0.1",
33
33
  "@radix-ui/react-compose-refs": "^1.0.0",
@@ -12,25 +12,22 @@ import { Spacer } from "./Spacer";
12
12
  import { Tooltip } from "./Tooltip";
13
13
  import type { MenuItemIcon } from "./internal/Menu";
14
14
 
15
- type ButtonVariant =
16
- | "normal"
17
- | "primary"
18
- | "secondary"
19
- | "secondaryBright"
20
- | "thin"
21
- | "floating"
22
- | "none";
15
+ type ButtonVariant = "normal" | "thin" | "floating" | "none";
23
16
 
24
17
  type ButtonSize = "small" | "normal" | "large" | "floating";
25
18
 
26
- const variantStyles: Record<ButtonVariant, string> = {
27
- normal:
28
- "bg-button-background text-button-text hover:bg-button-background-hover active:bg-button-background-active min-w-[31px]",
29
- primary:
30
- "bg-primary text-white hover:bg-primary-light active:bg-primary min-w-[31px]",
19
+ type ButtonColorScheme = "primary" | "secondary";
20
+
21
+ const colorSchemeStyles: Record<ButtonColorScheme | "default", string> = {
22
+ default:
23
+ "bg-button-background text-button-text hover:bg-button-background-hover active:bg-button-background-active",
24
+ primary: "bg-primary text-white hover:bg-primary-light active:bg-primary",
31
25
  secondary:
32
- "bg-secondary text-white hover:bg-secondary-light active:bg-secondary min-w-[31px]",
33
- secondaryBright: "bg-[#0ab557] text-white hover:opacity-80 active:opacity-90",
26
+ "bg-secondary text-white hover:bg-secondary-light active:bg-secondary",
27
+ };
28
+
29
+ const variantStyles: Record<ButtonVariant, string> = {
30
+ normal: "",
34
31
  floating:
35
32
  "bg-floating-button text-text shadow-[0_1px_2px_rgba(0,0,0,0.1)] hover:opacity-80 active:opacity-90",
36
33
  thin: "bg-transparent text-text hover:bg-input-background-light",
@@ -46,16 +43,21 @@ const sizeStyles: Record<ButtonSize | "thin" | "none", string> = {
46
43
  none: "py-0 px-0",
47
44
  };
48
45
 
49
- export interface ButtonRootProps {
46
+ export interface ButtonProps
47
+ extends React.DetailedHTMLProps<
48
+ React.ButtonHTMLAttributes<HTMLButtonElement>,
49
+ HTMLButtonElement
50
+ > {
50
51
  id?: string;
51
52
  className?: string;
52
53
  style?: CSSProperties;
53
54
  tabIndex?: number;
54
55
  icon?: MenuItemIcon;
55
56
  iconRight?: MenuItemIcon;
56
- children: ReactNode;
57
+ children?: ReactNode;
57
58
  active?: boolean;
58
59
  disabled?: boolean;
60
+ colorScheme?: ButtonColorScheme;
59
61
  variant?: ButtonVariant;
60
62
  size?: ButtonSize;
61
63
  tooltip?: ReactNode;
@@ -81,6 +83,7 @@ export const Button = forwardRef(function Button(
81
83
  tooltip,
82
84
  active = false,
83
85
  disabled = false,
86
+ colorScheme: colorSchemeProp,
84
87
  variant = "normal",
85
88
  size = "normal",
86
89
  contentStyle,
@@ -88,15 +91,17 @@ export const Button = forwardRef(function Button(
88
91
  children,
89
92
  as: Component = "button",
90
93
  ...rest
91
- }: ButtonRootProps,
94
+ }: ButtonProps,
92
95
  forwardedRef: ForwardedRef<HTMLButtonElement>
93
96
  ) {
97
+ const colorScheme = colorSchemeProp ?? "default";
98
+
94
99
  const baseClassName = cx(
95
100
  "no-underline leading-[1] relative border-0 outline-none select-none text-left rounded flex items-center justify-center [&>*]:pointer-events-none [-webkit-app-region:no-drag] focus:ring-2 focus:ring-primary focus:shadow-[0_0_0_1px_var(--n-popover-background)_inset] transition-all",
96
101
  disabled ? "opacity-25" : "",
97
102
  !className?.includes("flex") ? "flex-none" : "",
98
103
  active
99
- ? "bg-button-background-active text-button-text-active hover:bg-button-background-active-hover min-w-[31px]"
104
+ ? "bg-button-background-active text-button-text-active hover:bg-button-background-active-hover"
100
105
  : variantStyles[variant],
101
106
  sizeStyles[
102
107
  variant === "thin"
@@ -106,7 +111,10 @@ export const Button = forwardRef(function Button(
106
111
  : variant === "none"
107
112
  ? "none"
108
113
  : size
109
- ]
114
+ ],
115
+ variant === "normal" && (children ? "min-w-[31px]" : "min-w-[27px]"),
116
+ colorSchemeStyles[colorScheme],
117
+ !children && (icon || iconRight) && !(icon && iconRight) && "w-[27px]"
110
118
  );
111
119
 
112
120
  const buttonClassName = useMemo(() => {
@@ -51,7 +51,7 @@ export const DimensionInput = React.memo(function DimensionInput({
51
51
  );
52
52
 
53
53
  return (
54
- <LabeledField label={label} labelPosition="inset">
54
+ <LabeledField label={label} labelType="inset">
55
55
  <InputField.Root id={id} width={size}>
56
56
  <InputField.NumberInput
57
57
  value={value === undefined ? value : round(value, 2)}
@@ -1,5 +1,8 @@
1
1
  import { DragHandleDots2Icon } from "@noya-app/noya-icons";
2
- import { memoGeneric } from "@noya-app/react-utils";
2
+ import {
3
+ memoGeneric,
4
+ useControlledOrUncontrolled,
5
+ } from "@noya-app/react-utils";
3
6
  import React, { useCallback, useState } from "react";
4
7
  import { cssVars } from "../theme";
5
8
  import { cx } from "../utils/classNames";
@@ -12,21 +15,33 @@ import { MenuItem } from "./internal/Menu";
12
15
  export const DraggableMenuButton = memoGeneric(function DraggableMenuButton<
13
16
  T extends string,
14
17
  >({
18
+ open: openProp,
19
+ onOpenChange: onOpenChangeProp,
15
20
  items,
16
21
  onSelect,
17
22
  onClick,
18
23
  isVisible = true,
19
24
  className,
20
25
  style,
26
+ ...rest
21
27
  }: {
28
+ open?: boolean;
29
+ onOpenChange?: (open: boolean) => void;
22
30
  items?: MenuItem<T>[];
23
31
  onSelect?: (value: T) => void;
24
32
  onClick?: () => void;
25
33
  isVisible?: boolean;
26
34
  className?: string;
27
35
  style?: React.CSSProperties;
28
- }) {
29
- const [open, setOpen] = useState(false);
36
+ } & Omit<
37
+ React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>,
38
+ "onSelect"
39
+ >) {
40
+ const [open, setOpen] = useControlledOrUncontrolled({
41
+ value: openProp,
42
+ onChange: onOpenChangeProp,
43
+ defaultValue: false,
44
+ });
30
45
  const [downPosition, setDownPosition] = useState<{
31
46
  x: number;
32
47
  y: number;
@@ -64,7 +79,7 @@ export const DraggableMenuButton = memoGeneric(function DraggableMenuButton<
64
79
 
65
80
  setDownPosition(null);
66
81
  },
67
- [downPosition, open]
82
+ [downPosition, open, setOpen]
68
83
  );
69
84
 
70
85
  const handleOpenChange = useCallback(
@@ -76,6 +91,7 @@ export const DraggableMenuButton = memoGeneric(function DraggableMenuButton<
76
91
 
77
92
  return (
78
93
  <div
94
+ {...rest}
79
95
  className={cx(
80
96
  "cursor-pointer rounded flex items-center justify-center py-[2px] hover:bg-input-background-light active:bg-active-background ",
81
97
  className
@@ -1,9 +1,14 @@
1
1
  import { useKeyboardShortcuts } from "@noya-app/noya-keymap";
2
- import { forwardRefGeneric, memoGeneric } from "@noya-app/react-utils";
2
+ import {
3
+ forwardRefGeneric,
4
+ memoGeneric,
5
+ useControlledOrUncontrolled,
6
+ } from "@noya-app/react-utils";
3
7
  import * as RadixDropdownMenu from "@radix-ui/react-dropdown-menu";
4
8
  import React, {
5
9
  ComponentProps,
6
10
  ForwardedRef,
11
+ ReactNode,
7
12
  SyntheticEvent,
8
13
  useMemo,
9
14
  } from "react";
@@ -11,6 +16,7 @@ import {
11
16
  portalScopeProps,
12
17
  usePortalScopeId,
13
18
  } from "../contexts/PortalScopeContext";
19
+ import Button, { ButtonProps } from "./Button";
14
20
  import { MenuProps } from "./ContextMenu";
15
21
  import { getKeyboardShortcutsForMenuItems, styles } from "./internal/Menu";
16
22
  import { MenuViewport } from "./internal/MenuViewport";
@@ -19,7 +25,13 @@ import { MenuViewport } from "./internal/MenuViewport";
19
25
  * Root
20
26
  * ------------------------------------------------------------------------- */
21
27
 
22
- export type DropdownRootProps<T extends string> = MenuProps<T> & {
28
+ export type DropdownRootProps<T extends string> = Omit<
29
+ MenuProps<T>,
30
+ "children"
31
+ > & {
32
+ children?: ReactNode | ((props: { open: boolean }) => ReactNode);
33
+ title?: ReactNode;
34
+ icon?: ButtonProps["icon"];
23
35
  open?: boolean;
24
36
  onCloseAutoFocus?: React.EventHandler<SyntheticEvent<unknown>>;
25
37
  emptyState?: React.ReactNode;
@@ -50,7 +62,7 @@ const DropdownMenuRoot = forwardRefGeneric(function DropdownMenuRoot<
50
62
  onSelect,
51
63
  shouldBindKeyboardShortcuts,
52
64
  onOpenChange,
53
- open,
65
+ open: openProp,
54
66
  onCloseAutoFocus,
55
67
  side,
56
68
  sideOffset = 4,
@@ -59,8 +71,16 @@ const DropdownMenuRoot = forwardRefGeneric(function DropdownMenuRoot<
59
71
  collisionPadding = 8,
60
72
  emptyState,
61
73
  contentStyle,
74
+ title,
75
+ icon,
62
76
  } = props;
63
77
 
78
+ const [open, setOpen] = useControlledOrUncontrolled({
79
+ value: openProp,
80
+ onChange: onOpenChange,
81
+ defaultValue: false,
82
+ });
83
+
64
84
  const portalScopeId = usePortalScopeId();
65
85
 
66
86
  const keymap = useMemo(
@@ -83,12 +103,18 @@ const DropdownMenuRoot = forwardRefGeneric(function DropdownMenuRoot<
83
103
  );
84
104
 
85
105
  return (
86
- <RadixDropdownMenu.Root onOpenChange={onOpenChange} open={open}>
106
+ <RadixDropdownMenu.Root onOpenChange={setOpen} open={open}>
87
107
  <RadixDropdownMenu.Trigger
88
108
  ref={forwardedRef as ForwardedRef<any>}
89
109
  asChild
90
110
  >
91
- {children}
111
+ {typeof children === "function"
112
+ ? children({ open })
113
+ : children || (
114
+ <Button icon={icon} active={open} iconRight="DropdownChevronIcon">
115
+ {title}
116
+ </Button>
117
+ )}
92
118
  </RadixDropdownMenu.Trigger>
93
119
  <RadixDropdownMenu.Portal>
94
120
  <RadixDropdownMenu.Content
@@ -1,9 +1,9 @@
1
1
  import * as React from "react";
2
2
  import { cssVars } from "../theme";
3
- import { Button, ButtonRootProps } from "./Button";
3
+ import { Button, ButtonProps } from "./Button";
4
4
  import { IconName, Icons } from "./Icons";
5
5
 
6
- type Props = Omit<ButtonRootProps, "children" | "variant" | "flex" | "size"> & {
6
+ type Props = Omit<ButtonProps, "children" | "variant" | "flex" | "size"> & {
7
7
  iconName: IconName;
8
8
  className?: string;
9
9
  style?: React.CSSProperties;
@@ -14,11 +14,11 @@ import React, {
14
14
  useMemo,
15
15
  useState,
16
16
  } from "react";
17
- import { useLabel, useLabelPosition } from "../hooks/useLabel";
17
+ import { useLabel, useLabelType } from "../hooks/useLabel";
18
18
  import { cx, mergeConflictingClassNames } from "../utils/classNames";
19
19
  import handleNudge from "../utils/handleNudge";
20
20
  import { getInsetEndStyles, InputSize, startBaseStyles } from "../utils/inputs";
21
- import { Button, ButtonRootProps } from "./Button";
21
+ import { Button, ButtonProps } from "./Button";
22
22
  import { DropdownMenu } from "./DropdownMenu";
23
23
  import { MenuItem } from "./internal/Menu";
24
24
  import TextInput, { TextInputProps } from "./internal/TextInput";
@@ -101,7 +101,7 @@ const InputFieldDropdownMenu = memoGeneric(function InputFieldDropdownMenu<
101
101
  const InputFieldButton = memo(
102
102
  forwardRef(function InputFieldButton(
103
103
  props: React.ButtonHTMLAttributes<HTMLButtonElement> &
104
- ButtonRootProps & {
104
+ ButtonProps & {
105
105
  variant?: "floating" | "normal";
106
106
  },
107
107
  forwardedRef: ForwardedRef<HTMLButtonElement>
@@ -537,7 +537,7 @@ function InputFieldRoot({
537
537
  const measuredInputSize = useSize(inputRef, "width");
538
538
  const measuredStartSize = useSize(startRef, "width");
539
539
  const measuredEndSize = useSize(endRef, "width");
540
- const labelPosition = useLabelPosition();
540
+ const labelType = useLabelType();
541
541
  const { label } = useLabel({ fieldId: id });
542
542
 
543
543
  const endStyles = getInsetEndStyles(size);
@@ -592,7 +592,7 @@ function InputFieldRoot({
592
592
  >
593
593
  {labelProp
594
594
  ? insetLabel
595
- : label && labelPosition === "inset" && insetLabel}
595
+ : label && labelType === "inset" && insetLabel}
596
596
  {end}
597
597
  </span>
598
598
  )}
@@ -1,8 +1,6 @@
1
1
  import React, { LabelHTMLAttributes, forwardRef, memo } from "react";
2
2
  import { mergeConflictingClassNames } from "../utils/classNames";
3
3
 
4
- export type LabelPosition = "inset" | "start" | "end" | "above";
5
-
6
4
  export interface LabelProps extends LabelHTMLAttributes<HTMLLabelElement> {}
7
5
 
8
6
  const labelStyles = "flex items-center select-none z-label relative";
@@ -2,29 +2,30 @@ import React, { memo, useMemo } from "react";
2
2
  import { useIndent } from "../hooks/useIndent";
3
3
  import {
4
4
  LabelContext,
5
- LabelPositionContext,
6
- LabelPositionContextValue,
7
- useLabelPosition,
5
+ LabelType,
6
+ LabelTypeContext,
7
+ LabelTypeContextValue,
8
+ useLabelType,
8
9
  useLabelWidth,
9
10
  } from "../hooks/useLabel";
10
11
  import { cx } from "../utils/classNames";
11
- import { Label, LabelPosition } from "./Label";
12
+ import { Label } from "./Label";
12
13
 
13
- export const labeledFieldStyles = (labelPosition?: LabelPosition) =>
14
+ export const labeledFieldStyles = (labelType?: LabelType) =>
14
15
  cx(
15
16
  "flex flex-row flex-1 flex-wrap items-center gap-x-1.5",
16
- labelPosition === "end" && "flex-row-reverse justify-end",
17
- labelPosition === "above" && "flex-col items-stretch justify-start"
17
+ labelType === "end" && "flex-row-reverse justify-end",
18
+ labelType === "above" && "flex-col items-stretch justify-start"
18
19
  );
19
20
 
20
- const labelPositionInset: LabelPositionContextValue = {
21
- position: "inset",
21
+ const labelTypeInset: LabelTypeContextValue = {
22
+ type: "inset",
22
23
  };
23
24
 
24
25
  export const LabeledField = memo(function LabeledField({
25
26
  children,
26
27
  label,
27
- labelPosition: labelPositionProp,
28
+ labelType: labelTypeProp,
28
29
  fieldId,
29
30
  minWidth = 100,
30
31
  className,
@@ -34,7 +35,7 @@ export const LabeledField = memo(function LabeledField({
34
35
  }: {
35
36
  children: React.ReactNode;
36
37
  label: React.ReactNode;
37
- labelPosition?: LabelPosition;
38
+ labelType?: LabelType;
38
39
  labelStyle?: React.CSSProperties;
39
40
  /** Used for both the `id` of the field and the `htmlFor` of the label, unless `id` and `htmlFor` are explicitly provided */
40
41
  fieldId?: string;
@@ -47,13 +48,12 @@ export const LabeledField = memo(function LabeledField({
47
48
  const labelWidthFromContext = useLabelWidth();
48
49
  const labelWidth = labelWidthFromContext ?? minWidth;
49
50
 
50
- const labelPositionFromContext = useLabelPosition();
51
- const labelPosition =
52
- labelPositionProp ?? labelPositionFromContext ?? "start";
51
+ const labelTypeFromContext = useLabelType();
52
+ const labelType = labelTypeProp ?? labelTypeFromContext;
53
53
 
54
54
  const labeledFieldClasses = useMemo(
55
- () => labeledFieldStyles(labelPosition),
56
- [labelPosition]
55
+ () => labeledFieldStyles(labelType),
56
+ [labelType]
57
57
  );
58
58
 
59
59
  const labelContextValue = useMemo(
@@ -89,29 +89,31 @@ export const LabeledField = memo(function LabeledField({
89
89
  return (
90
90
  <LabelContext.Provider value={labelContextValue}>
91
91
  {/* let components individually handle inset labels using info from context*/}
92
- {labelPosition === "inset" ? (
93
- <LabelPositionContext.Provider value={labelPositionInset}>
92
+ {labelType === "inset" ? (
93
+ <LabelTypeContext.Provider value={labelTypeInset}>
94
94
  {children}
95
- </LabelPositionContext.Provider>
95
+ </LabelTypeContext.Provider>
96
96
  ) : (
97
97
  <div className={cx("flex relative flex-1", className)} style={style}>
98
98
  {Boolean(indentLevel) && <div style={indentStyle} />}
99
99
  <div className={labeledFieldClasses}>
100
- <Label
101
- htmlFor={fieldId}
102
- // we do this so presence avatars are aligned to the right when position is above
103
- // and when start/end position with textarea it aligns label to the top of the field
104
- className={
105
- labelPosition === "start" || labelPosition === "end"
106
- ? "self-start"
107
- : undefined
108
- }
109
- style={labelStyle}
110
- {...props}
111
- >
112
- {label}
113
- </Label>
114
- {labelPosition === "start" ? (
100
+ {labelType !== "none" && (
101
+ <Label
102
+ htmlFor={fieldId}
103
+ // we do this so presence avatars are aligned to the right when position is above
104
+ // and when start/end position with textarea it aligns label to the top of the field
105
+ className={
106
+ labelType === "start" || labelType === "end"
107
+ ? "self-start"
108
+ : undefined
109
+ }
110
+ style={labelStyle}
111
+ {...props}
112
+ >
113
+ {label}
114
+ </Label>
115
+ )}
116
+ {labelType === "start" ? (
115
117
  <div className="flex-auto flex" style={fieldStyle}>
116
118
  {children}
117
119
  </div>
@@ -1,6 +1,12 @@
1
1
  import { useControlledOrUncontrolled } from "@noya-app/react-utils";
2
2
  import * as PopoverPrimitive from "@radix-ui/react-popover";
3
- import React, { ComponentProps, useEffect, useId, useRef } from "react";
3
+ import React, {
4
+ ComponentProps,
5
+ useCallback,
6
+ useEffect,
7
+ useId,
8
+ useRef,
9
+ } from "react";
4
10
  import { useOpenPortalsControls } from "../contexts/OpenPortalsContext";
5
11
  import {
6
12
  portalScopeProps,
@@ -26,7 +32,9 @@ interface Props
26
32
  | "onFocusOutside"
27
33
  | "side"
28
34
  > {
29
- children: React.ReactNode;
35
+ children:
36
+ | React.ReactNode
37
+ | ((props: { close: () => void }) => React.ReactNode);
30
38
  trigger: React.ReactNode;
31
39
  variant?: PopoverVariant;
32
40
  closable?: boolean;
@@ -72,6 +80,7 @@ export function Popover({
72
80
  onChange: onOpenChange,
73
81
  defaultValue: false,
74
82
  });
83
+ const close = useCallback(() => setIsOpen(false), [setIsOpen]);
75
84
 
76
85
  const { addOpenPortal, removeOpenPortal } = useOpenPortalsControls();
77
86
 
@@ -127,7 +136,7 @@ export function Popover({
127
136
  // event.stopPropagation();
128
137
  // }}
129
138
  >
130
- {children}
139
+ {typeof children === "function" ? children({ close }) : children}
131
140
  {showArrow && (
132
141
  <PopoverPrimitive.Arrow className="fill-popover-background" />
133
142
  )}
@@ -103,7 +103,7 @@ const SegmentedControlItem = forwardRef(function SegmentedControlItem<
103
103
  className={cx(
104
104
  "inline-flex whitespace-pre flex-nowrap gap-1.5 items-center justify-center align-middle text-center",
105
105
  variant === "tabs" &&
106
- "group-focus:ring-2 group-focus:ring-primary-pastel group-focus:ring-offset-2"
106
+ "group-focus-visible:ring-2 group-focus-visible:ring-primary-pastel group-focus-visible:ring-offset-2"
107
107
  )}
108
108
  >
109
109
  {icon && renderIcon(icon)}
@@ -12,7 +12,7 @@ import React, {
12
12
  useMemo,
13
13
  useState,
14
14
  } from "react";
15
- import { useLabel, useLabelPosition } from "../hooks/useLabel";
15
+ import { useLabel, useLabelType } from "../hooks/useLabel";
16
16
  import { cx } from "../utils/classNames";
17
17
 
18
18
  import {
@@ -92,7 +92,7 @@ const SelectMenuTrigger = memoGeneric(function SelectMenuTrigger({
92
92
  label: props.label,
93
93
  fieldId: props.id,
94
94
  });
95
- const labelPosition = useLabelPosition();
95
+ const labelType = useLabelType();
96
96
 
97
97
  return (
98
98
  <Select.SelectTrigger asChild onFocus={onFocus} onBlur={onBlur}>
@@ -107,7 +107,7 @@ const SelectMenuTrigger = memoGeneric(function SelectMenuTrigger({
107
107
  disabled={disabled}
108
108
  >
109
109
  {icon && <span className="pr-1.5">{renderIcon(icon)}</span>}
110
- {label && labelPosition === "inset" && (
110
+ {label && labelType === "inset" && (
111
111
  <div className={insetEndStyles}>
112
112
  <Label className="pr-4 text-text-disabled" htmlFor={id}>
113
113
  {label}
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  cssVarNames,
3
+ cx,
3
4
  portalScopeProps,
4
5
  usePortalScopeId,
5
6
  } from "@noya-app/noya-designsystem";
@@ -7,7 +8,7 @@ import { Rect } from "@noya-app/noya-geometry";
7
8
  import { useSize } from "@noya-app/react-utils";
8
9
  import * as PopperPrimitive from "@radix-ui/react-popper";
9
10
  import type { MeasurableElement } from "@radix-ui/utils";
10
- import React, { memo, useMemo } from "react";
11
+ import React, { ComponentProps, memo, useMemo } from "react";
11
12
  import { createPortal } from "react-dom";
12
13
 
13
14
  // Create a Measurable from a Rect
@@ -25,10 +26,20 @@ const createVirtualElement = (rect: Rect): MeasurableElement => ({
25
26
  }),
26
27
  });
27
28
 
28
- type SelectionToolbarContainerProps = {
29
+ type PopperPrimitiveContentProps = ComponentProps<
30
+ typeof PopperPrimitive.Content
31
+ >;
32
+
33
+ type SelectionToolbarContainerProps = Pick<
34
+ PopperPrimitiveContentProps,
35
+ "side" | "sideOffset" | "align"
36
+ > & {
29
37
  rect: Rect;
30
38
  children: React.ReactNode;
31
39
  portalContainer?: HTMLElement | null;
40
+ containerClassName?: string;
41
+ containerStyle?: React.CSSProperties;
42
+ transitionDuration?: number;
32
43
  };
33
44
 
34
45
  export const SelectionToolbarContainer = memo(
@@ -36,6 +47,12 @@ export const SelectionToolbarContainer = memo(
36
47
  rect,
37
48
  children,
38
49
  portalContainer,
50
+ side = "top",
51
+ sideOffset = 10,
52
+ align = "center",
53
+ containerClassName,
54
+ containerStyle,
55
+ transitionDuration = 0.2,
39
56
  }: SelectionToolbarContainerProps) {
40
57
  const portalScopeId = usePortalScopeId();
41
58
  const containerRef = React.useRef<HTMLDivElement>(null);
@@ -52,23 +69,27 @@ export const SelectionToolbarContainer = memo(
52
69
  const popperContent = (
53
70
  <PopperPrimitive.Content
54
71
  {...portalScopeProps(portalScopeId)}
55
- side="top"
56
- sideOffset={10}
57
- align="center"
72
+ side={side}
73
+ sideOffset={sideOffset}
74
+ align={align}
58
75
  avoidCollisions
59
76
  collisionPadding={10}
60
77
  contentEditable={false}
61
78
  style={{
62
79
  zIndex: 1000,
63
80
  opacity: size ? 1 : 0,
64
- transition: "opacity 0.2s ease-in-out",
81
+ transition: `opacity ${transitionDuration}s ease-in-out`,
65
82
  [cssVarNames.colors.inputBackground]: "transparent",
66
83
  [cssVarNames.colors.buttonBackground]: "transparent",
67
84
  }}
68
85
  >
69
86
  <div
70
87
  ref={containerRef}
71
- className="flex gap-1 bg-popover-background rounded shadow-popover p-1"
88
+ className={cx(
89
+ "flex gap-1 bg-popover-background rounded shadow-popover p-1",
90
+ containerClassName
91
+ )}
92
+ style={containerStyle}
72
93
  onClick={(e) => e.stopPropagation()}
73
94
  onMouseDown={(e) => e.stopPropagation()}
74
95
  onPointerDown={(e) => e.stopPropagation()}
@@ -78,7 +78,7 @@ export const Slider = memo(function Slider({
78
78
  >
79
79
  <div className="relative flex-grow h-[3px] overflow-hidden">
80
80
  <div
81
- className="absolute h-full rounded-l bg-primary"
81
+ className="absolute h-full rounded-l bg-slider-active-background"
82
82
  style={{
83
83
  left: 0,
84
84
  width: `calc(${ratio * 100}% - ${ratio * 8}px - 2px)`,
@@ -1,4 +1,3 @@
1
- /* eslint-disable react/jsx-pascal-case */
2
1
  import type { ForwardedRef } from "react";
3
2
  import * as React from "react";
4
3