@noya-app/noya-designsystem 0.1.53 → 0.1.55
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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +16 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +35 -11
- package/dist/index.d.ts +35 -11
- package/dist/index.js +443 -315
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1061 -937
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/components/Button.tsx +13 -0
- package/src/components/ColorSwatch.tsx +5 -2
- package/src/components/Dialog.tsx +3 -5
- package/src/components/Grid.tsx +3 -1
- package/src/components/GridView.tsx +1 -0
- package/src/components/InputField.tsx +10 -3
- package/src/components/Popover.tsx +68 -4
- package/src/components/SegmentedControl.tsx +47 -53
- package/src/components/SelectionToolbar.tsx +35 -22
- package/src/components/Slider.tsx +1 -1
- package/src/components/Toolbar.tsx +62 -48
- package/src/components/connected-users-menu/ConnectedUsersMenuLayout.tsx +3 -26
- package/src/components/internal/Menu.tsx +1 -1
- package/src/components/internal/SelectItem.tsx +24 -3
- package/src/contexts/DesignSystemConfiguration.tsx +8 -5
- package/src/contexts/OpenPortalsContext.tsx +67 -0
- package/src/index.css +2 -2
- package/src/index.tsx +1 -0
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.55",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.mjs",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -22,10 +22,10 @@
|
|
|
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.21",
|
|
26
26
|
"@noya-app/noya-utils": "0.1.5",
|
|
27
27
|
"@noya-app/noya-geometry": "0.1.11",
|
|
28
|
-
"@noya-app/noya-icons": "0.1.
|
|
28
|
+
"@noya-app/noya-icons": "0.1.9",
|
|
29
29
|
"@noya-app/noya-keymap": "0.1.3",
|
|
30
30
|
"@noya-app/noya-tailwind-config": "0.1.3",
|
|
31
31
|
"@radix-ui/primitive": "^1.0.0",
|
|
@@ -7,7 +7,10 @@ import React, {
|
|
|
7
7
|
useMemo,
|
|
8
8
|
} from "react";
|
|
9
9
|
import { cx, mergeConflictingClassNames } from "../utils/classNames";
|
|
10
|
+
import { renderIcon } from "./Icons";
|
|
11
|
+
import { Spacer } from "./Spacer";
|
|
10
12
|
import { Tooltip } from "./Tooltip";
|
|
13
|
+
import type { MenuItemIcon } from "./internal/Menu";
|
|
11
14
|
|
|
12
15
|
type ButtonVariant =
|
|
13
16
|
| "normal"
|
|
@@ -48,6 +51,8 @@ export interface ButtonRootProps {
|
|
|
48
51
|
className?: string;
|
|
49
52
|
style?: CSSProperties;
|
|
50
53
|
tabIndex?: number;
|
|
54
|
+
icon?: MenuItemIcon;
|
|
55
|
+
iconRight?: MenuItemIcon;
|
|
51
56
|
children: ReactNode;
|
|
52
57
|
active?: boolean;
|
|
53
58
|
disabled?: boolean;
|
|
@@ -71,6 +76,8 @@ export const Button = forwardRef(function Button(
|
|
|
71
76
|
className,
|
|
72
77
|
style,
|
|
73
78
|
tabIndex,
|
|
79
|
+
icon,
|
|
80
|
+
iconRight,
|
|
74
81
|
tooltip,
|
|
75
82
|
active = false,
|
|
76
83
|
disabled = false,
|
|
@@ -128,7 +135,13 @@ export const Button = forwardRef(function Button(
|
|
|
128
135
|
className="min-h-[19px] flex items-center flex-1 justify-center leading-[15px]"
|
|
129
136
|
style={contentStyle}
|
|
130
137
|
>
|
|
138
|
+
{icon && renderIcon(icon)}
|
|
139
|
+
{icon && children && <Spacer.Horizontal inline size={6} />}
|
|
131
140
|
{children}
|
|
141
|
+
{iconRight && (icon || children) && (
|
|
142
|
+
<Spacer.Horizontal inline size={6} />
|
|
143
|
+
)}
|
|
144
|
+
{iconRight && renderIcon(iconRight)}
|
|
132
145
|
</span>
|
|
133
146
|
</Component>
|
|
134
147
|
);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { forwardRef } from "react";
|
|
2
|
+
import { cssVars } from "../theme";
|
|
2
3
|
|
|
3
4
|
export type ColorSwatchSize = "xsmall" | "small" | "medium" | "large";
|
|
4
5
|
|
|
@@ -31,10 +32,12 @@ export const ColorSwatch = forwardRef(function ColorSwatch(
|
|
|
31
32
|
height: sizePx,
|
|
32
33
|
borderRadius: "4px",
|
|
33
34
|
outline: checked
|
|
34
|
-
?
|
|
35
|
+
? `2px solid ${cssVars.colors.primary}`
|
|
35
36
|
: "1px solid rgba(0,0,0,0.1)",
|
|
36
37
|
outlineOffset: -1,
|
|
37
|
-
boxShadow: checked
|
|
38
|
+
boxShadow: checked
|
|
39
|
+
? `inset 0 0 0 2px ${cssVars.colors.background}`
|
|
40
|
+
: undefined,
|
|
38
41
|
...style,
|
|
39
42
|
}}
|
|
40
43
|
/>
|
|
@@ -30,9 +30,8 @@ const StyledContent = forwardRef<
|
|
|
30
30
|
<DialogPrimitive.Content
|
|
31
31
|
ref={ref}
|
|
32
32
|
className={cx(
|
|
33
|
-
`
|
|
34
|
-
|
|
35
|
-
w-[90vw] max-w-[450px] max-h-[85vh] p-dialog-padding rounded-[2px]
|
|
33
|
+
`fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2
|
|
34
|
+
w-[90vw] max-w-[550px] max-h-[85vh] p-dialog-padding rounded-[2px]
|
|
36
35
|
font-sans text-heading5 font-normal leading-[19px] text-text-muted
|
|
37
36
|
bg-popover-background pointer-events-all z-[1000] focus:outline-none
|
|
38
37
|
shadow-dialog-shadow
|
|
@@ -54,7 +53,7 @@ const StyledTitle = forwardRef<
|
|
|
54
53
|
<DialogPrimitive.Title
|
|
55
54
|
ref={ref}
|
|
56
55
|
className={cx(
|
|
57
|
-
`m-0 font-sans text-
|
|
56
|
+
`m-0 font-sans text-heading3 font-medium leading-[1.4] text-text `,
|
|
58
57
|
className
|
|
59
58
|
)}
|
|
60
59
|
{...props}
|
|
@@ -152,7 +151,6 @@ export const Dialog = forwardRef(function Dialog(
|
|
|
152
151
|
)}
|
|
153
152
|
{description && (
|
|
154
153
|
<>
|
|
155
|
-
<Spacer.Vertical size={10} />
|
|
156
154
|
<StyledDescription>{description}</StyledDescription>
|
|
157
155
|
<Spacer.Vertical size={20} />
|
|
158
156
|
</>
|
package/src/components/Grid.tsx
CHANGED
|
@@ -239,7 +239,7 @@ const InputElement = forwardRef(
|
|
|
239
239
|
: size === "large"
|
|
240
240
|
? "py-2.5 text-heading5"
|
|
241
241
|
: size === "medium"
|
|
242
|
-
? "py-1
|
|
242
|
+
? "py-1 text-heading5"
|
|
243
243
|
: $variant === "bare" && "py-1 -m-1",
|
|
244
244
|
className
|
|
245
245
|
);
|
|
@@ -488,7 +488,9 @@ interface InputFieldRootProps {
|
|
|
488
488
|
id?: string;
|
|
489
489
|
children?: ReactNode;
|
|
490
490
|
start?: ReactNode;
|
|
491
|
+
startClassName?: string;
|
|
491
492
|
end?: ReactNode;
|
|
493
|
+
endClassName?: string;
|
|
492
494
|
label?: ReactNode;
|
|
493
495
|
width?: number;
|
|
494
496
|
startWidth?: number;
|
|
@@ -520,6 +522,8 @@ function InputFieldRoot({
|
|
|
520
522
|
className,
|
|
521
523
|
start,
|
|
522
524
|
end,
|
|
525
|
+
startClassName,
|
|
526
|
+
endClassName,
|
|
523
527
|
label: labelProp,
|
|
524
528
|
}: InputFieldRootProps) {
|
|
525
529
|
const randomId = useId();
|
|
@@ -577,12 +581,15 @@ function InputFieldRoot({
|
|
|
577
581
|
<RootContainer $width={width} style={style} className={className}>
|
|
578
582
|
{children}
|
|
579
583
|
{start && (
|
|
580
|
-
<span ref={startRef} className={startBaseStyles}>
|
|
584
|
+
<span ref={startRef} className={cx(startBaseStyles, startClassName)}>
|
|
581
585
|
{start}
|
|
582
586
|
</span>
|
|
583
587
|
)}
|
|
584
588
|
{(end || label) && (
|
|
585
|
-
<span
|
|
589
|
+
<span
|
|
590
|
+
ref={endRef}
|
|
591
|
+
className={cx(endStyles, label && end && "gap-1.5", endClassName)}
|
|
592
|
+
>
|
|
586
593
|
{labelProp
|
|
587
594
|
? insetLabel
|
|
588
595
|
: label && labelPosition === "inset" && insetLabel}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { useControlledOrUncontrolled } from "@noya-app/react-utils";
|
|
1
2
|
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
2
|
-
import React, { ComponentProps } from "react";
|
|
3
|
+
import React, { ComponentProps, useEffect, useId, useRef } from "react";
|
|
4
|
+
import { useOpenPortalsControls } from "../contexts/OpenPortalsContext";
|
|
3
5
|
import {
|
|
4
6
|
portalScopeProps,
|
|
5
7
|
usePortalScopeId,
|
|
@@ -34,6 +36,7 @@ interface Props
|
|
|
34
36
|
onClickClose?: () => void;
|
|
35
37
|
showArrow?: boolean;
|
|
36
38
|
className?: string;
|
|
39
|
+
portalContainer?: HTMLElement | null;
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
export const popoverStyle = {
|
|
@@ -59,15 +62,36 @@ export function Popover({
|
|
|
59
62
|
onFocusOutside,
|
|
60
63
|
onClickClose,
|
|
61
64
|
className,
|
|
65
|
+
portalContainer,
|
|
62
66
|
}: Props) {
|
|
67
|
+
const defaultId = useId();
|
|
63
68
|
const portalScopeId = usePortalScopeId();
|
|
69
|
+
const contentRef = useRef<HTMLDivElement>(null);
|
|
70
|
+
const [isOpen, setIsOpen] = useControlledOrUncontrolled({
|
|
71
|
+
value: open,
|
|
72
|
+
onChange: onOpenChange,
|
|
73
|
+
defaultValue: false,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const { addOpenPortal, removeOpenPortal } = useOpenPortalsControls();
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (!isOpen) return;
|
|
80
|
+
|
|
81
|
+
addOpenPortal(portalScopeId || defaultId);
|
|
82
|
+
|
|
83
|
+
return () => {
|
|
84
|
+
removeOpenPortal(portalScopeId || defaultId);
|
|
85
|
+
};
|
|
86
|
+
}, [addOpenPortal, isOpen, portalScopeId, removeOpenPortal, defaultId]);
|
|
64
87
|
|
|
65
88
|
return (
|
|
66
|
-
<PopoverPrimitive.Root open={
|
|
89
|
+
<PopoverPrimitive.Root open={isOpen} onOpenChange={setIsOpen}>
|
|
67
90
|
<PopoverPrimitive.Trigger asChild>{trigger}</PopoverPrimitive.Trigger>
|
|
68
|
-
<PopoverPrimitive.Portal>
|
|
91
|
+
<PopoverPrimitive.Portal container={portalContainer}>
|
|
69
92
|
<PopoverPrimitive.Content
|
|
70
93
|
{...portalScopeProps(portalScopeId)}
|
|
94
|
+
ref={contentRef}
|
|
71
95
|
className={cx(
|
|
72
96
|
popoverStyle.base,
|
|
73
97
|
variant === "large" && popoverStyle.large,
|
|
@@ -81,7 +105,18 @@ export function Popover({
|
|
|
81
105
|
onCloseAutoFocus={onCloseAutoFocus}
|
|
82
106
|
onInteractOutside={onInteractOutside}
|
|
83
107
|
onFocusOutside={onFocusOutside}
|
|
84
|
-
onPointerDownOutside={
|
|
108
|
+
onPointerDownOutside={(event) => {
|
|
109
|
+
if (
|
|
110
|
+
isEventInsideElement(
|
|
111
|
+
event.detail.originalEvent,
|
|
112
|
+
contentRef.current
|
|
113
|
+
)
|
|
114
|
+
) {
|
|
115
|
+
event.preventDefault();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
onPointerDownOutside?.(event);
|
|
119
|
+
}}
|
|
85
120
|
collisionPadding={8}
|
|
86
121
|
arrowPadding={2}
|
|
87
122
|
// Don't propagate pointer down events to the canvas
|
|
@@ -109,3 +144,32 @@ export function Popover({
|
|
|
109
144
|
</PopoverPrimitive.Root>
|
|
110
145
|
);
|
|
111
146
|
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Radix ignores pointer-events on the body when a popover is open.
|
|
150
|
+
* This function checks if an event is inside an element by temporarily
|
|
151
|
+
* setting pointer-events to auto and then restoring it afterward.
|
|
152
|
+
*/
|
|
153
|
+
function isEventInsideElement(
|
|
154
|
+
event: PointerEvent,
|
|
155
|
+
element: HTMLElement | null
|
|
156
|
+
) {
|
|
157
|
+
if (!element) return false;
|
|
158
|
+
|
|
159
|
+
const body = document.body;
|
|
160
|
+
const originalPointerEvents = body.style.pointerEvents;
|
|
161
|
+
body.style.pointerEvents = "auto";
|
|
162
|
+
|
|
163
|
+
// Get all elements at the point
|
|
164
|
+
const elementsAtPoint = document.elementsFromPoint(
|
|
165
|
+
event.clientX,
|
|
166
|
+
event.clientY
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
// Restore original pointer-events
|
|
170
|
+
body.style.pointerEvents = originalPointerEvents;
|
|
171
|
+
|
|
172
|
+
const target = elementsAtPoint.at(0);
|
|
173
|
+
|
|
174
|
+
return target && element.contains(target);
|
|
175
|
+
}
|
|
@@ -15,6 +15,7 @@ import { SelectableMenuItem } from "./internal/Menu";
|
|
|
15
15
|
import { Tooltip } from "./Tooltip";
|
|
16
16
|
|
|
17
17
|
type SegmentedControlColorScheme = "primary" | "secondary";
|
|
18
|
+
type SegmentedControlVariant = "default" | "tabs";
|
|
18
19
|
|
|
19
20
|
type SegmentedControlContextValue = {
|
|
20
21
|
/** @default primary */
|
|
@@ -32,10 +33,10 @@ export interface SegmentedControlProps<T extends string> {
|
|
|
32
33
|
/** @default primary */
|
|
33
34
|
colorScheme?: SegmentedControlColorScheme;
|
|
34
35
|
allowEmpty?: boolean;
|
|
35
|
-
separator?: boolean;
|
|
36
36
|
items: SegmentedControlItemProps<T>[];
|
|
37
37
|
className?: string;
|
|
38
38
|
style?: React.CSSProperties;
|
|
39
|
+
variant?: SegmentedControlVariant;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
export type SegmentedControlItemProps<T extends string> = {
|
|
@@ -43,6 +44,7 @@ export type SegmentedControlItemProps<T extends string> = {
|
|
|
43
44
|
className?: string;
|
|
44
45
|
style?: React.CSSProperties;
|
|
45
46
|
tooltip?: ReactNode;
|
|
47
|
+
variant?: SegmentedControlVariant;
|
|
46
48
|
} & Pick<
|
|
47
49
|
SelectableMenuItem<T>,
|
|
48
50
|
"value" | "disabled" | "icon" | "role" | "title"
|
|
@@ -58,6 +60,7 @@ const SegmentedControlItem = forwardRef(function SegmentedControlItem<
|
|
|
58
60
|
disabled = false,
|
|
59
61
|
icon,
|
|
60
62
|
className,
|
|
63
|
+
variant = "default",
|
|
61
64
|
...props
|
|
62
65
|
}: SegmentedControlItemProps<T>,
|
|
63
66
|
forwardedRef: React.ForwardedRef<HTMLButtonElement>
|
|
@@ -70,24 +73,42 @@ const SegmentedControlItem = forwardRef(function SegmentedControlItem<
|
|
|
70
73
|
value={(value ?? "").toString()}
|
|
71
74
|
disabled={disabled}
|
|
72
75
|
className={cx(
|
|
73
|
-
"
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
? "
|
|
79
|
-
: colorScheme === "
|
|
80
|
-
? "
|
|
81
|
-
: "
|
|
76
|
+
"group",
|
|
77
|
+
"flex items-center justify-center",
|
|
78
|
+
"font-sans text-button relative flex-1 appearance-none text-segmented-control-item focus:outline-none transition-colors",
|
|
79
|
+
variant === "tabs" ? "font-semibold" : "font-normal",
|
|
80
|
+
variant === "tabs"
|
|
81
|
+
? ""
|
|
82
|
+
: colorScheme === "secondary"
|
|
83
|
+
? "focus:ring-2 focus:ring-secondary focus:ring-offset-1"
|
|
84
|
+
: "focus:ring-2 focus:ring-primary focus:ring-offset-1",
|
|
85
|
+
variant === "tabs"
|
|
86
|
+
? "aria-checked:text-primary"
|
|
87
|
+
: colorScheme === "secondary"
|
|
88
|
+
? "aria-checked:bg-secondary aria-checked:text-white"
|
|
89
|
+
: colorScheme === "primary"
|
|
90
|
+
? "aria-checked:bg-primary aria-checked:text-white"
|
|
91
|
+
: "aria-checked:bg-background aria-checked:text-text",
|
|
82
92
|
"focus:z-interactable",
|
|
83
|
-
"
|
|
84
|
-
|
|
93
|
+
variant === "default"
|
|
94
|
+
? "px-1 rounded aria-checked:shadow-segment"
|
|
95
|
+
: "px-1.5 border-y-2 border-y-transparent aria-checked:border-b-primary",
|
|
96
|
+
variant === "default" &&
|
|
97
|
+
"border-r border-divider last:border-r-0 aria-checked:border-r-transparent [&:has(+[aria-checked=true])]:border-r-transparent",
|
|
85
98
|
className
|
|
86
99
|
)}
|
|
87
100
|
{...props}
|
|
88
101
|
>
|
|
89
|
-
|
|
90
|
-
|
|
102
|
+
<span
|
|
103
|
+
className={cx(
|
|
104
|
+
"inline-flex whitespace-pre flex-nowrap gap-1.5 items-center justify-center align-middle text-center",
|
|
105
|
+
variant === "tabs" &&
|
|
106
|
+
"group-focus:ring-2 group-focus:ring-primary-pastel group-focus:ring-offset-2"
|
|
107
|
+
)}
|
|
108
|
+
>
|
|
109
|
+
{icon && renderIcon(icon)}
|
|
110
|
+
{title}
|
|
111
|
+
</span>
|
|
91
112
|
</ToggleGroupPrimitive.Item>
|
|
92
113
|
);
|
|
93
114
|
|
|
@@ -98,15 +119,6 @@ const SegmentedControlItem = forwardRef(function SegmentedControlItem<
|
|
|
98
119
|
);
|
|
99
120
|
});
|
|
100
121
|
|
|
101
|
-
const Separator = ({ transparent }: { transparent: boolean }) => (
|
|
102
|
-
<div
|
|
103
|
-
className={cx(
|
|
104
|
-
"w-[1px] my-1 self-stretch bg-divider",
|
|
105
|
-
transparent && "bg-transparent"
|
|
106
|
-
)}
|
|
107
|
-
/>
|
|
108
|
-
);
|
|
109
|
-
|
|
110
122
|
export const SegmentedControl = memoGeneric(function SegmentedControl<
|
|
111
123
|
T extends string,
|
|
112
124
|
>({
|
|
@@ -116,7 +128,7 @@ export const SegmentedControl = memoGeneric(function SegmentedControl<
|
|
|
116
128
|
colorScheme,
|
|
117
129
|
allowEmpty,
|
|
118
130
|
items,
|
|
119
|
-
|
|
131
|
+
variant = "default",
|
|
120
132
|
className,
|
|
121
133
|
style,
|
|
122
134
|
}: SegmentedControlProps<T>) {
|
|
@@ -131,8 +143,6 @@ export const SegmentedControl = memoGeneric(function SegmentedControl<
|
|
|
131
143
|
[allowEmpty, onValueChange]
|
|
132
144
|
);
|
|
133
145
|
|
|
134
|
-
const selectedIndex = items.findIndex((item) => item?.value === value);
|
|
135
|
-
|
|
136
146
|
return (
|
|
137
147
|
<SegmentedControlContext.Provider value={contextValue}>
|
|
138
148
|
<ToggleGroupPrimitive.Root
|
|
@@ -141,36 +151,20 @@ export const SegmentedControl = memoGeneric(function SegmentedControl<
|
|
|
141
151
|
value={value}
|
|
142
152
|
onValueChange={handleValueChange}
|
|
143
153
|
className={cx(
|
|
144
|
-
`
|
|
154
|
+
`appearance-none relative outline-none`,
|
|
155
|
+
variant === "default" &&
|
|
156
|
+
"grid min-h-input-height bg-input-background rounded py-0.5 px-0.5",
|
|
157
|
+
variant === "tabs" && "flex gap-1.5 min-h-[33px]",
|
|
145
158
|
className
|
|
146
159
|
)}
|
|
147
|
-
style={
|
|
160
|
+
style={{
|
|
161
|
+
...style,
|
|
162
|
+
gridTemplateColumns: `repeat(${items.length}, 1fr)`,
|
|
163
|
+
}}
|
|
148
164
|
>
|
|
149
|
-
{
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const currentValue = item?.value;
|
|
153
|
-
|
|
154
|
-
// Show separator if:
|
|
155
|
-
// 1. Current item is not selected AND
|
|
156
|
-
// 2. Current item is not immediately before the selected item (except when selected is 0)
|
|
157
|
-
// 3. Current item is not immediately after the selected item
|
|
158
|
-
const showSeparator =
|
|
159
|
-
currentValue !== value &&
|
|
160
|
-
(selectedIndex === 0 && selectedIndex !== index
|
|
161
|
-
? true
|
|
162
|
-
: index !== selectedIndex - 1);
|
|
163
|
-
|
|
164
|
-
return (
|
|
165
|
-
<React.Fragment key={index}>
|
|
166
|
-
<SegmentedControlItem key={index} {...item} />
|
|
167
|
-
{!isLastItem && <Separator transparent={!showSeparator} />}
|
|
168
|
-
</React.Fragment>
|
|
169
|
-
);
|
|
170
|
-
})
|
|
171
|
-
: items.map((item, index) => (
|
|
172
|
-
<SegmentedControlItem key={index} {...item} />
|
|
173
|
-
))}
|
|
165
|
+
{items.map((item, index) => (
|
|
166
|
+
<SegmentedControlItem key={index} {...item} variant={variant} />
|
|
167
|
+
))}
|
|
174
168
|
</ToggleGroupPrimitive.Root>
|
|
175
169
|
</SegmentedControlContext.Provider>
|
|
176
170
|
);
|
|
@@ -7,6 +7,7 @@ import { useSize } from "@noya-app/react-utils";
|
|
|
7
7
|
import * as PopperPrimitive from "@radix-ui/react-popper";
|
|
8
8
|
import type { MeasurableElement } from "@radix-ui/utils";
|
|
9
9
|
import React, { CSSProperties, memo, useMemo } from "react";
|
|
10
|
+
import { createPortal } from "react-dom";
|
|
10
11
|
|
|
11
12
|
// Create a Measurable from a Rect
|
|
12
13
|
const createVirtualElement = (rect: Rect): MeasurableElement => ({
|
|
@@ -26,12 +27,14 @@ const createVirtualElement = (rect: Rect): MeasurableElement => ({
|
|
|
26
27
|
type SelectionToolbarContainerProps = {
|
|
27
28
|
rect: Rect;
|
|
28
29
|
children: React.ReactNode;
|
|
30
|
+
portalContainer?: HTMLElement | null;
|
|
29
31
|
};
|
|
30
32
|
|
|
31
33
|
export const SelectionToolbarContainer = memo(
|
|
32
34
|
function SelectionToolbarContainer({
|
|
33
35
|
rect,
|
|
34
36
|
children,
|
|
37
|
+
portalContainer,
|
|
35
38
|
}: SelectionToolbarContainerProps) {
|
|
36
39
|
const portalScopeId = usePortalScopeId();
|
|
37
40
|
const containerRef = React.useRef<HTMLDivElement>(null);
|
|
@@ -45,31 +48,41 @@ export const SelectionToolbarContainer = memo(
|
|
|
45
48
|
[rect]
|
|
46
49
|
);
|
|
47
50
|
|
|
51
|
+
const popperContent = (
|
|
52
|
+
<PopperPrimitive.Content
|
|
53
|
+
{...portalScopeProps(portalScopeId)}
|
|
54
|
+
side="top"
|
|
55
|
+
sideOffset={10}
|
|
56
|
+
align="center"
|
|
57
|
+
avoidCollisions
|
|
58
|
+
collisionPadding={10}
|
|
59
|
+
contentEditable={false}
|
|
60
|
+
style={{
|
|
61
|
+
zIndex: 1000,
|
|
62
|
+
opacity: size ? 1 : 0,
|
|
63
|
+
transition: "opacity 0.2s ease-in-out",
|
|
64
|
+
["--n-input-background" as keyof CSSProperties]: "transparent",
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
<div
|
|
68
|
+
ref={containerRef}
|
|
69
|
+
className="flex gap-1 bg-popover-background rounded shadow-popover p-1"
|
|
70
|
+
onClick={(e) => e.stopPropagation()}
|
|
71
|
+
onMouseDown={(e) => e.stopPropagation()}
|
|
72
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
73
|
+
onWheel={(e) => e.stopPropagation()}
|
|
74
|
+
>
|
|
75
|
+
{children}
|
|
76
|
+
</div>
|
|
77
|
+
</PopperPrimitive.Content>
|
|
78
|
+
);
|
|
79
|
+
|
|
48
80
|
return (
|
|
49
81
|
<PopperPrimitive.Root>
|
|
50
82
|
<PopperPrimitive.Anchor virtualRef={virtualRef} />
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
sideOffset={10}
|
|
55
|
-
align="center"
|
|
56
|
-
avoidCollisions
|
|
57
|
-
collisionPadding={10}
|
|
58
|
-
contentEditable={false}
|
|
59
|
-
style={{
|
|
60
|
-
zIndex: 1000,
|
|
61
|
-
opacity: size ? 1 : 0,
|
|
62
|
-
transition: "opacity 0.2s ease-in-out",
|
|
63
|
-
["--n-input-background" as keyof CSSProperties]: "transparent",
|
|
64
|
-
}}
|
|
65
|
-
>
|
|
66
|
-
<div
|
|
67
|
-
ref={containerRef}
|
|
68
|
-
className="flex gap-1 bg-popover-background rounded shadow-popover p-1"
|
|
69
|
-
>
|
|
70
|
-
{children}
|
|
71
|
-
</div>
|
|
72
|
-
</PopperPrimitive.Content>
|
|
83
|
+
{portalContainer
|
|
84
|
+
? createPortal(popperContent, portalContainer)
|
|
85
|
+
: popperContent}
|
|
73
86
|
</PopperPrimitive.Root>
|
|
74
87
|
);
|
|
75
88
|
}
|
|
@@ -117,7 +117,7 @@ export const Slider = memo(function Slider({
|
|
|
117
117
|
<div
|
|
118
118
|
style={trackFillStyle}
|
|
119
119
|
className={cx(
|
|
120
|
-
"absolute inset-0 w-full h-full rounded overflow-hidden",
|
|
120
|
+
"absolute inset-0 w-full h-full rounded overflow-hidden bg-primary-pastel",
|
|
121
121
|
colorScheme === "primary" && "bg-primary",
|
|
122
122
|
colorScheme === "secondary" && "bg-secondary"
|
|
123
123
|
)}
|