@noya-app/noya-designsystem 0.1.87 → 0.1.89
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 +14 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +181 -120
- package/dist/index.d.ts +181 -120
- package/dist/index.js +2147 -1915
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1936 -1711
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/SharedDragProvider.test.ts +39 -0
- package/src/components/AppToolbar.tsx +464 -0
- package/src/components/Card.tsx +20 -0
- package/src/components/Dialog.tsx +12 -5
- package/src/components/DialogNavigator.tsx +154 -0
- package/src/components/InputField.tsx +9 -7
- package/src/components/Navigator.tsx +2 -0
- package/src/components/SegmentedControl.tsx +32 -4
- package/src/components/SelectionToolbar.tsx +5 -10
- package/src/components/StatCard.tsx +82 -0
- package/src/components/Toolbar.tsx +32 -459
- package/src/components/__tests__/Card.test.tsx +71 -0
- package/src/components/__tests__/DialogNavigator.test.tsx +88 -0
- package/src/components/__tests__/InputField.test.tsx +227 -3
- package/src/components/__tests__/SegmentedControl.test.ts +46 -0
- package/src/components/__tests__/Toolbar.test.tsx +56 -0
- package/src/components/connected-users-menu/ConnectedUsersMenuLayout.tsx +0 -16
- package/src/components/sorting/SharedDragProvider.tsx +37 -1
- package/src/components/workspace/DrawerWorkspaceLayout.tsx +2 -2
- package/src/components/workspace/VerticalTabMenu.tsx +1 -1
- package/src/components/workspace/__tests__/DrawerWorkspaceLayout.test.tsx +2 -0
- package/src/index.css +6 -0
- package/src/index.tsx +4 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { ReactNode } from "react";
|
|
4
|
+
import { cx } from "../utils/classNames";
|
|
5
|
+
import { BaseToolbar } from "./BaseToolbar";
|
|
6
|
+
import { Button } from "./Button";
|
|
7
|
+
import { Dialog, DialogProps } from "./Dialog";
|
|
8
|
+
import { IconButton } from "./IconButton";
|
|
9
|
+
import {
|
|
10
|
+
Navigator,
|
|
11
|
+
NavigatorParams,
|
|
12
|
+
NavigatorProps,
|
|
13
|
+
NavigatorRoute,
|
|
14
|
+
NavigatorRoutesConfig,
|
|
15
|
+
NavigatorScreenProps,
|
|
16
|
+
useNavigator,
|
|
17
|
+
useNavigatorRoute,
|
|
18
|
+
} from "./Navigator";
|
|
19
|
+
import { StackNavigatorHeaderProps } from "./StackNavigator";
|
|
20
|
+
import { Body } from "./Text";
|
|
21
|
+
|
|
22
|
+
export type DialogNavigatorProps<
|
|
23
|
+
Routes extends Record<string, NavigatorRoute<any>>,
|
|
24
|
+
Name extends keyof Routes,
|
|
25
|
+
> = Omit<
|
|
26
|
+
NavigatorProps<Routes, Name>,
|
|
27
|
+
"children" | "className" | "style" | "renderHeader" | "showHeader"
|
|
28
|
+
> &
|
|
29
|
+
Pick<DialogProps, "closeOnInteractOutside" | "open"> & {
|
|
30
|
+
children: ReactNode;
|
|
31
|
+
title: ReactNode;
|
|
32
|
+
onOpenChange: (open: boolean) => void;
|
|
33
|
+
className?: string;
|
|
34
|
+
style?: React.CSSProperties;
|
|
35
|
+
navigatorClassName?: string;
|
|
36
|
+
navigatorStyle?: React.CSSProperties;
|
|
37
|
+
showCloseButton?: boolean;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export function DialogNavigator<
|
|
41
|
+
Routes extends Record<string, NavigatorRoute<any>>,
|
|
42
|
+
Name extends keyof Routes,
|
|
43
|
+
>({
|
|
44
|
+
children,
|
|
45
|
+
title,
|
|
46
|
+
open,
|
|
47
|
+
onOpenChange,
|
|
48
|
+
closeOnInteractOutside,
|
|
49
|
+
className,
|
|
50
|
+
style,
|
|
51
|
+
navigatorClassName,
|
|
52
|
+
navigatorStyle,
|
|
53
|
+
showCloseButton = true,
|
|
54
|
+
...navigatorProps
|
|
55
|
+
}: DialogNavigatorProps<Routes, Name>) {
|
|
56
|
+
return (
|
|
57
|
+
<Dialog
|
|
58
|
+
title={title}
|
|
59
|
+
titleVisibility="hidden"
|
|
60
|
+
open={open}
|
|
61
|
+
onOpenChange={onOpenChange}
|
|
62
|
+
closeOnInteractOutside={closeOnInteractOutside}
|
|
63
|
+
showCloseButton={false}
|
|
64
|
+
className={cx("n-p-0 n-overflow-hidden", className)}
|
|
65
|
+
style={style}
|
|
66
|
+
>
|
|
67
|
+
<Navigator
|
|
68
|
+
{...navigatorProps}
|
|
69
|
+
className={navigatorClassName}
|
|
70
|
+
style={navigatorStyle}
|
|
71
|
+
renderHeader={(headerProps) => (
|
|
72
|
+
<DialogNavigatorHeader
|
|
73
|
+
{...headerProps}
|
|
74
|
+
showCloseButton={showCloseButton}
|
|
75
|
+
onClose={() => onOpenChange(false)}
|
|
76
|
+
/>
|
|
77
|
+
)}
|
|
78
|
+
>
|
|
79
|
+
{children}
|
|
80
|
+
</Navigator>
|
|
81
|
+
</Dialog>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function DialogNavigatorHeader({
|
|
86
|
+
showDivider,
|
|
87
|
+
showBackButton,
|
|
88
|
+
onBack,
|
|
89
|
+
right,
|
|
90
|
+
title,
|
|
91
|
+
showCloseButton,
|
|
92
|
+
onClose,
|
|
93
|
+
}: StackNavigatorHeaderProps & {
|
|
94
|
+
showCloseButton: boolean;
|
|
95
|
+
onClose: () => void;
|
|
96
|
+
}) {
|
|
97
|
+
return (
|
|
98
|
+
<BaseToolbar
|
|
99
|
+
innerClassName="n-px-1"
|
|
100
|
+
showDivider={showDivider}
|
|
101
|
+
enableAbsoluteBreakpoint={false}
|
|
102
|
+
enableContainerQuery={false}
|
|
103
|
+
left={
|
|
104
|
+
showBackButton && (
|
|
105
|
+
<Button
|
|
106
|
+
className="-n-mx-2"
|
|
107
|
+
icon="ArrowLeftIcon"
|
|
108
|
+
onClick={onBack}
|
|
109
|
+
aria-label="Back"
|
|
110
|
+
/>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
right={
|
|
114
|
+
(right || showCloseButton) && (
|
|
115
|
+
<div className="n-flex n-items-center n-gap-1">
|
|
116
|
+
{right}
|
|
117
|
+
{showCloseButton && (
|
|
118
|
+
<IconButton
|
|
119
|
+
iconName="Cross1Icon"
|
|
120
|
+
aria-label="Close"
|
|
121
|
+
onClick={onClose}
|
|
122
|
+
/>
|
|
123
|
+
)}
|
|
124
|
+
</div>
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
>
|
|
128
|
+
{typeof title === "string" || typeof title === "number" ? (
|
|
129
|
+
<Body>{title}</Body>
|
|
130
|
+
) : (
|
|
131
|
+
title
|
|
132
|
+
)}
|
|
133
|
+
</BaseToolbar>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function createDialogNavigator<
|
|
138
|
+
RoutesConfig extends Record<string, NavigatorParams | null>,
|
|
139
|
+
>() {
|
|
140
|
+
return Object.assign(
|
|
141
|
+
DialogNavigator as <Name extends keyof RoutesConfig>(
|
|
142
|
+
props: DialogNavigatorProps<NavigatorRoutesConfig<RoutesConfig>, Name>
|
|
143
|
+
) => ReactNode,
|
|
144
|
+
{
|
|
145
|
+
Screen: Navigator.Screen as <Name extends keyof RoutesConfig>(
|
|
146
|
+
props: NavigatorScreenProps<NavigatorRoutesConfig<RoutesConfig>, Name>
|
|
147
|
+
) => ReactNode,
|
|
148
|
+
useNavigator: useNavigator<NavigatorRoutesConfig<RoutesConfig>>,
|
|
149
|
+
useNavigatorRoute: useNavigatorRoute as <Name extends keyof RoutesConfig>(
|
|
150
|
+
name: Name
|
|
151
|
+
) => NavigatorRoute<NavigatorRoutesConfig<RoutesConfig>[Name]["params"]>,
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
}
|
|
@@ -434,23 +434,25 @@ function InputFieldNumberInput(props: InputFieldNumberInputProps) {
|
|
|
434
434
|
);
|
|
435
435
|
|
|
436
436
|
const handleChange = useCallback(
|
|
437
|
-
(
|
|
438
|
-
if (
|
|
439
|
-
setInternalValue(
|
|
437
|
+
(inputValue: string) => {
|
|
438
|
+
if (inputValue === "" || inputValue === "-" || inputValue === ".") {
|
|
439
|
+
setInternalValue(inputValue);
|
|
440
440
|
if (triggersOnEmpty) {
|
|
441
441
|
onChange?.(0, { isEmpty: true });
|
|
442
442
|
}
|
|
443
443
|
return;
|
|
444
444
|
}
|
|
445
445
|
|
|
446
|
-
if (
|
|
447
|
-
setInternalValue(
|
|
446
|
+
if (inputValue.endsWith(".")) {
|
|
447
|
+
setInternalValue(inputValue);
|
|
448
448
|
return;
|
|
449
449
|
}
|
|
450
450
|
|
|
451
|
-
|
|
451
|
+
// Keep the text being edited stable if the parent rejects or normalizes
|
|
452
|
+
// an intermediate numeric value. The accepted prop value takes over on blur.
|
|
453
|
+
setInternalValue(inputValue);
|
|
452
454
|
|
|
453
|
-
const newValue = parseNumber(
|
|
455
|
+
const newValue = parseNumber(inputValue);
|
|
454
456
|
|
|
455
457
|
if (!isNaN(newValue)) {
|
|
456
458
|
onChange?.(newValue, { isEmpty: false });
|
|
@@ -71,6 +71,7 @@ export type NavigatorHeaderProps<
|
|
|
71
71
|
title: React.ReactNode;
|
|
72
72
|
right: React.ReactNode;
|
|
73
73
|
showBackButton: boolean;
|
|
74
|
+
showDivider: boolean;
|
|
74
75
|
onBack?: () => void;
|
|
75
76
|
};
|
|
76
77
|
|
|
@@ -406,6 +407,7 @@ function NavigatorImpl<
|
|
|
406
407
|
title,
|
|
407
408
|
right,
|
|
408
409
|
showBackButton: headerProps.showBackButton,
|
|
410
|
+
showDivider: headerProps.showDivider,
|
|
409
411
|
onBack: headerProps.onBack,
|
|
410
412
|
}),
|
|
411
413
|
renderContent: () => {
|
|
@@ -87,6 +87,30 @@ export type SegmentedControlItemProps<T extends string> = {
|
|
|
87
87
|
"value" | "disabled" | "icon" | "role" | "title"
|
|
88
88
|
>;
|
|
89
89
|
|
|
90
|
+
export function snapIndicatorEdgesToDevicePixels({
|
|
91
|
+
containerLeft,
|
|
92
|
+
itemLeft,
|
|
93
|
+
itemRight,
|
|
94
|
+
devicePixelRatio,
|
|
95
|
+
}: {
|
|
96
|
+
containerLeft: number;
|
|
97
|
+
itemLeft: number;
|
|
98
|
+
itemRight: number;
|
|
99
|
+
devicePixelRatio: number;
|
|
100
|
+
}) {
|
|
101
|
+
const scale =
|
|
102
|
+
Number.isFinite(devicePixelRatio) && devicePixelRatio > 0
|
|
103
|
+
? devicePixelRatio
|
|
104
|
+
: 1;
|
|
105
|
+
const snappedLeft = Math.round(itemLeft * scale) / scale;
|
|
106
|
+
const snappedRight = Math.round(itemRight * scale) / scale;
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
left: snappedLeft - containerLeft,
|
|
110
|
+
width: Math.max(0, snappedRight - snappedLeft),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
90
114
|
const SegmentedControlItem = forwardRef(function SegmentedControlItem<
|
|
91
115
|
T extends string,
|
|
92
116
|
>(
|
|
@@ -256,10 +280,14 @@ function useIndicatorPosition({
|
|
|
256
280
|
const containerRect = container.getBoundingClientRect();
|
|
257
281
|
const itemRect = selectedItem.getBoundingClientRect();
|
|
258
282
|
|
|
259
|
-
setIndicatorStyle(
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
283
|
+
setIndicatorStyle(
|
|
284
|
+
snapIndicatorEdgesToDevicePixels({
|
|
285
|
+
containerLeft: containerRect.left,
|
|
286
|
+
itemLeft: itemRect.left,
|
|
287
|
+
itemRight: itemRect.right,
|
|
288
|
+
devicePixelRatio: window.devicePixelRatio,
|
|
289
|
+
})
|
|
290
|
+
);
|
|
263
291
|
|
|
264
292
|
hasInitialized.current = true;
|
|
265
293
|
}, [containerRef, itemRefs, selectedIndex, variant, itemCount, isCompact]);
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
cssVarNames,
|
|
5
|
-
cx,
|
|
6
4
|
portalScopeProps,
|
|
7
5
|
usePortalScopeId,
|
|
8
6
|
} from "@noya-app/noya-designsystem";
|
|
@@ -18,6 +16,7 @@ import {
|
|
|
18
16
|
type VirtualElement,
|
|
19
17
|
} from "@floating-ui/react";
|
|
20
18
|
import React, { memo, useCallback, useEffect, useMemo, useState } from "react";
|
|
19
|
+
import { Toolbar } from "./Toolbar";
|
|
21
20
|
|
|
22
21
|
type Side = "top" | "right" | "bottom" | "left";
|
|
23
22
|
type Align = "start" | "center" | "end";
|
|
@@ -106,17 +105,13 @@ export const SelectionToolbarContainer = memo(
|
|
|
106
105
|
...floatingStyles,
|
|
107
106
|
opacity: size ? 1 : 0,
|
|
108
107
|
transition: `opacity ${transitionDuration}s ease-in-out`,
|
|
109
|
-
[cssVarNames.colors.inputBackground]: "transparent",
|
|
110
|
-
[cssVarNames.colors.buttonBackground]: "transparent",
|
|
111
108
|
}}
|
|
112
109
|
className="n-z-menu"
|
|
113
110
|
>
|
|
114
|
-
<
|
|
111
|
+
<Toolbar
|
|
115
112
|
ref={containerRef}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
containerClassName
|
|
119
|
-
)}
|
|
113
|
+
size="small"
|
|
114
|
+
className={containerClassName}
|
|
120
115
|
style={containerStyle}
|
|
121
116
|
onClick={(e) => e.stopPropagation()}
|
|
122
117
|
onMouseDown={(e) => e.stopPropagation()}
|
|
@@ -124,7 +119,7 @@ export const SelectionToolbarContainer = memo(
|
|
|
124
119
|
onWheel={(e) => e.stopPropagation()}
|
|
125
120
|
>
|
|
126
121
|
{children}
|
|
127
|
-
</
|
|
122
|
+
</Toolbar>
|
|
128
123
|
</div>
|
|
129
124
|
</FloatingPortal>
|
|
130
125
|
);
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { ArrowDownIcon, ArrowUpIcon } from "@noya-app/noya-icons";
|
|
2
|
+
import React, { forwardRef } from "react";
|
|
3
|
+
import { cx } from "../utils/classNames";
|
|
4
|
+
import { Card, CardProps } from "./Card";
|
|
5
|
+
import { Chip, ChipProps } from "./Chip";
|
|
6
|
+
import { IconProp, renderIcon } from "./Icons";
|
|
7
|
+
import { Heading1, Small } from "./Text";
|
|
8
|
+
|
|
9
|
+
export type StatCardColorScheme = NonNullable<ChipProps["colorScheme"]>;
|
|
10
|
+
export type StatCardTrend = "up" | "down";
|
|
11
|
+
|
|
12
|
+
export type StatCardProps = Omit<CardProps, "children"> & {
|
|
13
|
+
label: React.ReactNode;
|
|
14
|
+
value: React.ReactNode;
|
|
15
|
+
icon?: IconProp;
|
|
16
|
+
change?: React.ReactNode;
|
|
17
|
+
trend?: StatCardTrend;
|
|
18
|
+
colorScheme?: StatCardColorScheme;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const ICON_COLOR_STYLES: Record<StatCardColorScheme, string> = {
|
|
22
|
+
primary: "n-bg-primary",
|
|
23
|
+
secondary: "n-bg-secondary",
|
|
24
|
+
info: "n-bg-banner-info-text",
|
|
25
|
+
error: "n-bg-banner-error-text",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const StatCard = forwardRef<HTMLDivElement, StatCardProps>(
|
|
29
|
+
function StatCard(
|
|
30
|
+
{
|
|
31
|
+
label,
|
|
32
|
+
value,
|
|
33
|
+
icon,
|
|
34
|
+
change,
|
|
35
|
+
trend,
|
|
36
|
+
colorScheme = "primary",
|
|
37
|
+
className,
|
|
38
|
+
...props
|
|
39
|
+
},
|
|
40
|
+
forwardedRef
|
|
41
|
+
) {
|
|
42
|
+
const TrendIcon = trend === "down" ? ArrowDownIcon : ArrowUpIcon;
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Card
|
|
46
|
+
ref={forwardedRef}
|
|
47
|
+
className={cx("n-flex n-items-center n-gap-4", className)}
|
|
48
|
+
{...props}
|
|
49
|
+
>
|
|
50
|
+
{icon && (
|
|
51
|
+
<div
|
|
52
|
+
aria-hidden="true"
|
|
53
|
+
className={cx(
|
|
54
|
+
"n-flex n-h-16 n-w-16 n-flex-none n-items-center n-justify-center n-rounded-md n-text-white [&>svg]:n-h-8 [&>svg]:n-w-8",
|
|
55
|
+
ICON_COLOR_STYLES[colorScheme]
|
|
56
|
+
)}
|
|
57
|
+
>
|
|
58
|
+
{renderIcon(icon)}
|
|
59
|
+
</div>
|
|
60
|
+
)}
|
|
61
|
+
<div className="n-flex n-min-w-0 n-flex-1 n-self-stretch n-flex-col n-justify-center n-gap-1">
|
|
62
|
+
<div className="n-flex n-items-center n-gap-4">
|
|
63
|
+
<Small className="n-min-w-0 n-flex-1 n-opacity-70">{label}</Small>
|
|
64
|
+
{change !== undefined && (
|
|
65
|
+
<Chip
|
|
66
|
+
className="n-flex-none n-gap-1"
|
|
67
|
+
colorScheme={colorScheme}
|
|
68
|
+
size="large"
|
|
69
|
+
>
|
|
70
|
+
{trend && <TrendIcon aria-hidden="true" />}
|
|
71
|
+
{change}
|
|
72
|
+
</Chip>
|
|
73
|
+
)}
|
|
74
|
+
</div>
|
|
75
|
+
<Heading1 as="div" className="!n-font-bold n-leading-none">
|
|
76
|
+
{value}
|
|
77
|
+
</Heading1>
|
|
78
|
+
</div>
|
|
79
|
+
</Card>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
);
|