@noya-app/noya-designsystem 0.1.86 → 0.1.88
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 +12 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +180 -117
- package/dist/index.d.ts +180 -117
- package/dist/index.js +2105 -1872
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1930 -1704
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- 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/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__/SegmentedControl.test.ts +46 -0
- package/src/components/__tests__/Toolbar.test.tsx +56 -0
- package/src/components/sorting/SharedDragProvider.tsx +37 -1
- package/src/components/workspace/DrawerWorkspaceLayout.tsx +3 -13
- package/src/components/workspace/VerticalTabMenu.tsx +1 -1
- package/src/components/workspace/__tests__/DrawerWorkspaceLayout.test.tsx +64 -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
|
+
}
|
|
@@ -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
|
+
);
|