@noya-app/noya-designsystem 0.1.70 → 0.1.72
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 +19 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +82 -7
- package/dist/index.d.ts +82 -7
- package/dist/index.js +1090 -774
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1068 -753
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -8
- package/src/components/BaseToolbar.tsx +1 -1
- package/src/components/Breadcrumbs.tsx +1 -0
- package/src/components/Collection.tsx +1 -2
- package/src/components/Combobox.tsx +3 -0
- package/src/components/ComboboxMenu.tsx +1 -1
- package/src/components/InputField.tsx +20 -4
- package/src/components/ListMenu.tsx +1 -1
- package/src/components/Message.tsx +3 -2
- package/src/components/ScrollArea2.tsx +22 -2
- package/src/components/UserPicker.tsx +128 -0
- package/src/components/Virtualized.tsx +439 -0
- package/src/components/__tests__/Virtualized.math.test.ts +153 -0
- package/src/components/__tests__/Virtualized.test.tsx +85 -0
- package/src/components/listView/ListViewContexts.tsx +1 -6
- package/src/components/listView/ListViewRoot.tsx +18 -152
- package/src/index.tsx +6 -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.72",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.mjs",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
"@noya-app/noya-colorpicker": "0.1.30",
|
|
27
27
|
"@noya-app/noya-utils": "0.1.9",
|
|
28
28
|
"@noya-app/noya-geometry": "0.1.16",
|
|
29
|
-
"@noya-app/noya-icons": "0.1.
|
|
29
|
+
"@noya-app/noya-icons": "0.1.16",
|
|
30
30
|
"@noya-app/noya-keymap": "0.1.4",
|
|
31
|
-
"@noya-app/noya-tailwind-config": "0.1.
|
|
31
|
+
"@noya-app/noya-tailwind-config": "0.1.9",
|
|
32
32
|
"radix-ui": "1.4.2",
|
|
33
33
|
"@radix-ui/react-compose-refs": "^1.0.0",
|
|
34
34
|
"@radix-ui/primitive": "^1.0.1",
|
|
@@ -38,17 +38,13 @@
|
|
|
38
38
|
"immer": "9.0.5",
|
|
39
39
|
"kiwi.js": "^1.1.3",
|
|
40
40
|
"react-resizable-panels": "3.0.3",
|
|
41
|
-
"
|
|
42
|
-
"vscode-fuzzy-scorer": "^0.0.4",
|
|
43
|
-
"react-virtualized": "9.22.6"
|
|
41
|
+
"vscode-fuzzy-scorer": "^0.0.4"
|
|
44
42
|
},
|
|
45
43
|
"devDependencies": {
|
|
46
44
|
"@tailwindcss/container-queries": "^0.1.1",
|
|
47
45
|
"@tailwindcss/typography": "^0.5.19",
|
|
48
46
|
"@types/react": "*",
|
|
49
47
|
"@types/react-dom": "*",
|
|
50
|
-
"@types/react-virtualized": "^9.21.30",
|
|
51
|
-
"@types/react-window": "^1.8.5",
|
|
52
48
|
"tailwindcss": "^3.4.17",
|
|
53
49
|
"postcss": "^8.5.3",
|
|
54
50
|
"autoprefixer": "^10.4.21",
|
|
@@ -85,7 +85,7 @@ export function BaseToolbar({
|
|
|
85
85
|
{children && (
|
|
86
86
|
<div
|
|
87
87
|
className={
|
|
88
|
-
"n-flex n-items-center n-justify-center n-text-text-muted n-pointer-events-none @
|
|
88
|
+
"n-flex n-items-center n-justify-center n-text-text-muted n-pointer-events-none @xl/toolbar:!n-absolute n-inset-0"
|
|
89
89
|
}
|
|
90
90
|
>
|
|
91
91
|
<div className="n-flex n-items-center n-justify-center n-pointer-events-auto">
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { Size } from "@noya-app/noya-geometry";
|
|
4
3
|
import { forwardRefGeneric } from "@noya-app/react-utils";
|
|
5
4
|
import React, { CSSProperties } from "react";
|
|
6
5
|
import { Grid } from "./Grid";
|
|
@@ -34,7 +33,7 @@ export type CollectionRenderActionProps<T> = {
|
|
|
34
33
|
export interface CollectionProps<T, M extends string = string> {
|
|
35
34
|
className?: string;
|
|
36
35
|
items: T[];
|
|
37
|
-
virtualized?:
|
|
36
|
+
virtualized?: number;
|
|
38
37
|
getId: (item: T) => string;
|
|
39
38
|
getName: (item: T) => string;
|
|
40
39
|
getHref?: (item: T) => string;
|
|
@@ -47,6 +47,7 @@ export type BaseComboboxProps<T extends string> = {
|
|
|
47
47
|
readOnly?: boolean;
|
|
48
48
|
tabBehavior?: "autocomplete" | "select";
|
|
49
49
|
openMenuBehavior?: "showAllItems" | "showFilteredItems";
|
|
50
|
+
iconPosition?: "left" | "right";
|
|
50
51
|
};
|
|
51
52
|
|
|
52
53
|
export type StringModeOnlyProps = {
|
|
@@ -103,6 +104,7 @@ export const Combobox = memoGeneric(
|
|
|
103
104
|
openMenuBehavior = "showFilteredItems",
|
|
104
105
|
onFocus,
|
|
105
106
|
onDeleteWhenEmpty,
|
|
107
|
+
iconPosition = "right",
|
|
106
108
|
...rest
|
|
107
109
|
}: ComboboxProps<T>,
|
|
108
110
|
forwardedRef: ForwardedRef<ComboboxRef<T>>
|
|
@@ -500,6 +502,7 @@ export const Combobox = memoGeneric(
|
|
|
500
502
|
onSelectItem={handleUpdateSelection}
|
|
501
503
|
onHoverIndex={handleIndexChange}
|
|
502
504
|
listSize={adjustedListSize}
|
|
505
|
+
iconPosition={iconPosition}
|
|
503
506
|
style={{
|
|
504
507
|
flex: `0 0 ${computedHeight}px`,
|
|
505
508
|
}}
|
|
@@ -62,7 +62,7 @@ export const ComboboxMenu = memoGeneric(
|
|
|
62
62
|
return item.value;
|
|
63
63
|
}}
|
|
64
64
|
data={items}
|
|
65
|
-
virtualized={listSize}
|
|
65
|
+
virtualized={listSize.height}
|
|
66
66
|
{...(itemHeight ? { itemHeight } : {})}
|
|
67
67
|
{...(getRowHeightForItem
|
|
68
68
|
? { getItemHeightForItem: getRowHeightForItem as any }
|
|
@@ -350,6 +350,10 @@ const InputFieldTypeahead = (props: {
|
|
|
350
350
|
* NumberInput
|
|
351
351
|
* ------------------------------------------------------------------------- */
|
|
352
352
|
|
|
353
|
+
type NumberChangeOptions = {
|
|
354
|
+
isEmpty: boolean;
|
|
355
|
+
};
|
|
356
|
+
|
|
353
357
|
type InputFieldNumberInputProps = Omit<
|
|
354
358
|
TextInputProps,
|
|
355
359
|
"value" | "onChange" | "onKeyDown" | "onSubmit" | "readOnly"
|
|
@@ -361,9 +365,10 @@ type InputFieldNumberInputProps = Omit<
|
|
|
361
365
|
onBlur?: FocusEventHandler;
|
|
362
366
|
readOnly?: boolean;
|
|
363
367
|
className?: string;
|
|
368
|
+
triggersOnEmpty?: boolean;
|
|
364
369
|
} & (
|
|
365
370
|
| {
|
|
366
|
-
onChange: (value: number) => void;
|
|
371
|
+
onChange: (value: number, options: NumberChangeOptions) => void;
|
|
367
372
|
}
|
|
368
373
|
| {
|
|
369
374
|
onSubmit: (value: number) => void;
|
|
@@ -375,7 +380,15 @@ function parseNumber(value: string) {
|
|
|
375
380
|
}
|
|
376
381
|
|
|
377
382
|
function InputFieldNumberInput(props: InputFieldNumberInputProps) {
|
|
378
|
-
const {
|
|
383
|
+
const {
|
|
384
|
+
value,
|
|
385
|
+
placeholder,
|
|
386
|
+
onNudge,
|
|
387
|
+
onBlur,
|
|
388
|
+
readOnly,
|
|
389
|
+
triggersOnEmpty,
|
|
390
|
+
...rest
|
|
391
|
+
} = props;
|
|
379
392
|
const onSubmit = "onSubmit" in props ? props.onSubmit : undefined;
|
|
380
393
|
const onChange = "onChange" in props ? props.onChange : undefined;
|
|
381
394
|
|
|
@@ -414,6 +427,9 @@ function InputFieldNumberInput(props: InputFieldNumberInputProps) {
|
|
|
414
427
|
(value: string) => {
|
|
415
428
|
if (value === "" || value === "-" || value === ".") {
|
|
416
429
|
setInternalValue(value);
|
|
430
|
+
if (triggersOnEmpty) {
|
|
431
|
+
onChange?.(0, { isEmpty: true });
|
|
432
|
+
}
|
|
417
433
|
return;
|
|
418
434
|
}
|
|
419
435
|
|
|
@@ -427,10 +443,10 @@ function InputFieldNumberInput(props: InputFieldNumberInputProps) {
|
|
|
427
443
|
const newValue = parseNumber(value);
|
|
428
444
|
|
|
429
445
|
if (!isNaN(newValue)) {
|
|
430
|
-
onChange?.(newValue);
|
|
446
|
+
onChange?.(newValue, { isEmpty: false });
|
|
431
447
|
}
|
|
432
448
|
},
|
|
433
|
-
[onChange]
|
|
449
|
+
[onChange, triggersOnEmpty]
|
|
434
450
|
);
|
|
435
451
|
|
|
436
452
|
const handleBlur = useCallback(
|
|
@@ -23,7 +23,8 @@ export const Message = memo(
|
|
|
23
23
|
ref: React.ForwardedRef<HTMLDivElement>
|
|
24
24
|
) {
|
|
25
25
|
const randomId = uuid();
|
|
26
|
-
const
|
|
26
|
+
const resolvedUserId = user?.userId ?? user?.connectionId ?? randomId;
|
|
27
|
+
const userMessageBackgroundColor = colorFromString(resolvedUserId);
|
|
27
28
|
const styles = useMemo(
|
|
28
29
|
() =>
|
|
29
30
|
role === "user"
|
|
@@ -50,7 +51,7 @@ export const Message = memo(
|
|
|
50
51
|
</Avatar>
|
|
51
52
|
) : (
|
|
52
53
|
<Avatar
|
|
53
|
-
userId={
|
|
54
|
+
userId={resolvedUserId}
|
|
54
55
|
name={user?.name ?? "Anonymous"}
|
|
55
56
|
image={user?.image}
|
|
56
57
|
size={avatarSize}
|
|
@@ -17,21 +17,41 @@ const SCROLLBAR_THUMB_VERTICAL =
|
|
|
17
17
|
const SCROLLBAR_THUMB_HORIZONTAL =
|
|
18
18
|
"scroll-component n-h-full n-rounded n-bg-scrollbar";
|
|
19
19
|
|
|
20
|
+
type ScrollAreaViewportProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
21
|
+
ref?: React.Ref<HTMLDivElement>;
|
|
22
|
+
};
|
|
23
|
+
|
|
20
24
|
export function ScrollArea2({
|
|
21
25
|
children,
|
|
22
26
|
className,
|
|
23
27
|
viewportClassName,
|
|
24
28
|
orientation = "vertical",
|
|
29
|
+
style,
|
|
30
|
+
viewportProps,
|
|
25
31
|
}: {
|
|
26
32
|
children: React.ReactNode;
|
|
27
33
|
className?: string;
|
|
28
34
|
viewportClassName?: string;
|
|
29
35
|
orientation?: "vertical" | "horizontal" | "both";
|
|
36
|
+
style?: React.CSSProperties;
|
|
37
|
+
viewportProps?: ScrollAreaViewportProps;
|
|
30
38
|
}) {
|
|
39
|
+
const {
|
|
40
|
+
className: viewportPropsClassName,
|
|
41
|
+
ref: viewportRef,
|
|
42
|
+
...viewportRest
|
|
43
|
+
} = viewportProps ?? {};
|
|
44
|
+
|
|
31
45
|
return (
|
|
32
|
-
<ScrollArea.Root className={cx("n-relative", className)}>
|
|
46
|
+
<ScrollArea.Root className={cx("n-relative", className)} style={style}>
|
|
33
47
|
<ScrollArea.Viewport
|
|
34
|
-
|
|
48
|
+
ref={viewportRef}
|
|
49
|
+
className={cx(
|
|
50
|
+
"n-h-full n-overscroll-contain",
|
|
51
|
+
viewportClassName,
|
|
52
|
+
viewportPropsClassName
|
|
53
|
+
)}
|
|
54
|
+
{...viewportRest}
|
|
35
55
|
>
|
|
36
56
|
{children}
|
|
37
57
|
</ScrollArea.Viewport>
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
OptionModeProps,
|
|
5
|
+
SelectableMenuItem,
|
|
6
|
+
} from "@noya-app/noya-designsystem";
|
|
7
|
+
import { Avatar, Combobox } from "@noya-app/noya-designsystem";
|
|
8
|
+
import React, { useCallback, useMemo, useState } from "react";
|
|
9
|
+
|
|
10
|
+
type ComboboxOptionProps = Omit<
|
|
11
|
+
OptionModeProps<string>,
|
|
12
|
+
"mode" | "items" | "value" | "onSelectItem" | "onChange" | "onHoverItem"
|
|
13
|
+
>;
|
|
14
|
+
|
|
15
|
+
export type UserPickerUser = {
|
|
16
|
+
id: string;
|
|
17
|
+
name?: string | null;
|
|
18
|
+
email?: string | null;
|
|
19
|
+
image?: string | null;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type UserPickerProps<TUser extends UserPickerUser = UserPickerUser> =
|
|
23
|
+
ComboboxOptionProps & {
|
|
24
|
+
users: readonly TUser[];
|
|
25
|
+
value?: string;
|
|
26
|
+
defaultValue?: string;
|
|
27
|
+
onChangeUserId?: (userId: string | undefined) => void;
|
|
28
|
+
onSelectUser?: (user: TUser | undefined) => void;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function UserPicker<TUser extends UserPickerUser>({
|
|
32
|
+
users,
|
|
33
|
+
value,
|
|
34
|
+
defaultValue,
|
|
35
|
+
onChangeUserId,
|
|
36
|
+
onSelectUser,
|
|
37
|
+
placeholder = "Search users...",
|
|
38
|
+
...rest
|
|
39
|
+
}: UserPickerProps<TUser>) {
|
|
40
|
+
const sortedUsers = useMemo(() => {
|
|
41
|
+
return [...users].sort((a, b) =>
|
|
42
|
+
getUserDisplayName(a).localeCompare(getUserDisplayName(b), undefined, {
|
|
43
|
+
sensitivity: "base",
|
|
44
|
+
})
|
|
45
|
+
);
|
|
46
|
+
}, [users]);
|
|
47
|
+
|
|
48
|
+
const usersById = useMemo(() => {
|
|
49
|
+
return new Map(sortedUsers.map((user) => [user.id, user] as const));
|
|
50
|
+
}, [sortedUsers]);
|
|
51
|
+
|
|
52
|
+
const items = useMemo(() => {
|
|
53
|
+
return sortedUsers.map(createUserMenuItem);
|
|
54
|
+
}, [sortedUsers]);
|
|
55
|
+
|
|
56
|
+
const isControlled = value !== undefined;
|
|
57
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
|
|
58
|
+
const selectedUserId = isControlled ? value : uncontrolledValue;
|
|
59
|
+
|
|
60
|
+
const selectedItem = useMemo(() => {
|
|
61
|
+
if (!selectedUserId) return undefined;
|
|
62
|
+
return items.find((item) => item.value === selectedUserId);
|
|
63
|
+
}, [items, selectedUserId]);
|
|
64
|
+
|
|
65
|
+
const updateSelectedUserId = useCallback(
|
|
66
|
+
(userId?: string) => {
|
|
67
|
+
if (!isControlled) {
|
|
68
|
+
setUncontrolledValue(userId);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
onChangeUserId?.(userId);
|
|
72
|
+
},
|
|
73
|
+
[isControlled, onChangeUserId]
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const handleSelectItem = useCallback(
|
|
77
|
+
(item: SelectableMenuItem<string>) => {
|
|
78
|
+
updateSelectedUserId(item.value);
|
|
79
|
+
onSelectUser?.(usersById.get(item.value));
|
|
80
|
+
},
|
|
81
|
+
[updateSelectedUserId, onSelectUser, usersById]
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<Combobox
|
|
86
|
+
{...rest}
|
|
87
|
+
placeholder={placeholder}
|
|
88
|
+
mode="option"
|
|
89
|
+
items={items}
|
|
90
|
+
value={selectedItem}
|
|
91
|
+
onSelectItem={handleSelectItem}
|
|
92
|
+
iconPosition="left"
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function getUserDisplayName(user: UserPickerUser) {
|
|
98
|
+
const name = user.name?.trim();
|
|
99
|
+
const email = user.email?.trim();
|
|
100
|
+
|
|
101
|
+
return name || email || "Anonymous user";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function createUserMenuItem(user: UserPickerUser): SelectableMenuItem<string> {
|
|
105
|
+
const title = getUserDisplayName(user);
|
|
106
|
+
const overflow = 3;
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
value: user.id,
|
|
110
|
+
title,
|
|
111
|
+
icon: (
|
|
112
|
+
<div className="n-relative n-w-[15px] n-h-[15px]">
|
|
113
|
+
<Avatar
|
|
114
|
+
size={15 + overflow * 2}
|
|
115
|
+
name={title}
|
|
116
|
+
userId={user.id}
|
|
117
|
+
image={user.image ?? undefined}
|
|
118
|
+
className="n-pointer-events-none"
|
|
119
|
+
variant="bare"
|
|
120
|
+
style={{
|
|
121
|
+
position: "absolute",
|
|
122
|
+
inset: -overflow,
|
|
123
|
+
}}
|
|
124
|
+
/>
|
|
125
|
+
</div>
|
|
126
|
+
),
|
|
127
|
+
};
|
|
128
|
+
}
|