@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.
- package/.eslintrc.js +1 -5
- package/.turbo/turbo-build.log +10 -10
- package/.turbo/turbo-lint.log +0 -1
- package/CHANGELOG.md +16 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +147 -69
- package/dist/index.d.ts +147 -69
- package/dist/index.js +1347 -1063
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +756 -473
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/components/Button.tsx +28 -20
- package/src/components/DimensionInput.tsx +1 -1
- package/src/components/DraggableMenuButton.tsx +20 -4
- package/src/components/DropdownMenu.tsx +31 -5
- package/src/components/IconButton.tsx +2 -2
- package/src/components/InputField.tsx +5 -5
- package/src/components/Label.tsx +0 -2
- package/src/components/LabeledField.tsx +36 -34
- package/src/components/Popover.tsx +12 -3
- package/src/components/SegmentedControl.tsx +1 -1
- package/src/components/SelectMenu.tsx +3 -3
- package/src/components/SelectionToolbar.tsx +28 -7
- package/src/components/Slider.tsx +1 -1
- package/src/components/Spacer.tsx +0 -1
- package/src/components/Tabs.tsx +46 -0
- package/src/components/Toolbar.tsx +106 -11
- package/src/components/Tooltip.tsx +8 -8
- package/src/components/file-explorer/FileExplorerLayout.tsx +12 -6
- package/src/components/internal/Menu.tsx +19 -2
- package/src/components/internal/MenuViewport.tsx +4 -3
- package/src/components/workspace/DrawerWorkspaceLayout.tsx +7 -3
- package/src/components/workspace/PanelWorkspaceLayout.tsx +103 -3
- package/src/components/workspace/WorkspaceLayout.tsx +96 -15
- package/src/components/workspace/renderPanelChildren.tsx +12 -0
- package/src/components/workspace/types.ts +19 -3
- package/src/hooks/useLabel.ts +10 -10
- package/src/hooks/usePreservePanelSize.tsx +1 -1
- package/src/index.css +10 -6
- package/src/index.tsx +2 -0
- package/src/utils/editableBlockStyles.ts +10 -0
- 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.
|
|
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
|
+
"@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.
|
|
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.
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"bg-
|
|
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
|
|
33
|
-
|
|
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
|
|
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
|
|
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
|
-
}:
|
|
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
|
|
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}
|
|
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 {
|
|
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
|
-
|
|
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 {
|
|
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> =
|
|
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={
|
|
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,
|
|
3
|
+
import { Button, ButtonProps } from "./Button";
|
|
4
4
|
import { IconName, Icons } from "./Icons";
|
|
5
5
|
|
|
6
|
-
type Props = Omit<
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
|
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 &&
|
|
595
|
+
: label && labelType === "inset" && insetLabel}
|
|
596
596
|
{end}
|
|
597
597
|
</span>
|
|
598
598
|
)}
|
package/src/components/Label.tsx
CHANGED
|
@@ -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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
|
12
|
+
import { Label } from "./Label";
|
|
12
13
|
|
|
13
|
-
export const labeledFieldStyles = (
|
|
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
|
-
|
|
17
|
-
|
|
17
|
+
labelType === "end" && "flex-row-reverse justify-end",
|
|
18
|
+
labelType === "above" && "flex-col items-stretch justify-start"
|
|
18
19
|
);
|
|
19
20
|
|
|
20
|
-
const
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
51
|
-
const
|
|
52
|
-
labelPositionProp ?? labelPositionFromContext ?? "start";
|
|
51
|
+
const labelTypeFromContext = useLabelType();
|
|
52
|
+
const labelType = labelTypeProp ?? labelTypeFromContext;
|
|
53
53
|
|
|
54
54
|
const labeledFieldClasses = useMemo(
|
|
55
|
-
() => labeledFieldStyles(
|
|
56
|
-
[
|
|
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
|
-
{
|
|
93
|
-
<
|
|
92
|
+
{labelType === "inset" ? (
|
|
93
|
+
<LabelTypeContext.Provider value={labelTypeInset}>
|
|
94
94
|
{children}
|
|
95
|
-
</
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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, {
|
|
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:
|
|
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,
|
|
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
|
|
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 &&
|
|
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
|
|
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=
|
|
56
|
-
sideOffset={
|
|
57
|
-
align=
|
|
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:
|
|
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=
|
|
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-
|
|
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)`,
|