@noya-app/noya-designsystem 0.0.7 → 0.0.8
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 +9 -9
- package/CHANGELOG.md +7 -0
- package/dist/index.d.mts +72 -3
- package/dist/index.d.ts +72 -3
- package/dist/index.js +675 -10789
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +301 -10417
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -2
- package/src/components/ArrayController.tsx +168 -0
- package/src/components/InspectorPrimitives.tsx +139 -0
- package/src/hooks/__tests__/mergeEventHandlers.test.ts +10 -9
- package/src/index.tsx +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noya-app/noya-designsystem",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.mjs",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -12,9 +12,11 @@
|
|
|
12
12
|
"@dnd-kit/core": "3.1.1",
|
|
13
13
|
"@dnd-kit/modifiers": "3.0.0",
|
|
14
14
|
"@dnd-kit/sortable": "4.0.0",
|
|
15
|
-
"@noya-app/noya-colorpicker": "0.0.
|
|
15
|
+
"@noya-app/noya-colorpicker": "0.0.4",
|
|
16
16
|
"@noya-app/noya-utils": "0.0.7",
|
|
17
17
|
"@noya-app/noya-geometry": "0.0.7",
|
|
18
|
+
"@noya-app/noya-icons": "0.0.1",
|
|
19
|
+
"@noya-app/noya-keymap": "0.0.3",
|
|
18
20
|
"@radix-ui/primitive": "^1.0.0",
|
|
19
21
|
"@radix-ui/react-avatar": "^1.0.1",
|
|
20
22
|
"@radix-ui/react-compose-refs": "^1.0.0",
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { range } from "@noya-app/noya-utils";
|
|
2
|
+
import React, { ReactNode, memo, useCallback, useMemo } from "react";
|
|
3
|
+
import styled, { useTheme } from "styled-components";
|
|
4
|
+
import withSeparatorElements from "../utils/withSeparatorElements";
|
|
5
|
+
import { Button } from "./Button";
|
|
6
|
+
import { IconButton } from "./IconButton";
|
|
7
|
+
import * as InspectorPrimitives from "./InspectorPrimitives";
|
|
8
|
+
import { ListView } from "./ListView";
|
|
9
|
+
import { RelativeDropPosition, Sortable } from "./Sortable";
|
|
10
|
+
import { Spacer } from "./Spacer";
|
|
11
|
+
import { Stack } from "./Stack";
|
|
12
|
+
|
|
13
|
+
const ElementRow = styled.div({
|
|
14
|
+
flex: "0 0 auto",
|
|
15
|
+
display: "flex",
|
|
16
|
+
flexDirection: "row",
|
|
17
|
+
alignItems: "center",
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const ItemContainer = styled.div({
|
|
21
|
+
position: "relative",
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export interface ArrayControllerProps<Item> {
|
|
25
|
+
id: string;
|
|
26
|
+
items: Item[];
|
|
27
|
+
title: ReactNode;
|
|
28
|
+
sortable?: boolean;
|
|
29
|
+
reversed?: boolean;
|
|
30
|
+
expanded?: boolean;
|
|
31
|
+
getKey?: (item: Item) => string;
|
|
32
|
+
onMoveItem?: (sourceIndex: number, destinationIndex: number) => void;
|
|
33
|
+
onClickPlus?: () => void;
|
|
34
|
+
onClickTrash?: () => void;
|
|
35
|
+
onClickExpand?: () => void;
|
|
36
|
+
renderItem: (props: { item: Item; index: number }) => ReactNode;
|
|
37
|
+
renderExpandedContent?: () => ReactNode;
|
|
38
|
+
padding?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const ArrayController = memo(function ArrayController<Item>({
|
|
42
|
+
id,
|
|
43
|
+
items,
|
|
44
|
+
title,
|
|
45
|
+
sortable = false,
|
|
46
|
+
reversed = true,
|
|
47
|
+
expanded = false,
|
|
48
|
+
getKey,
|
|
49
|
+
onMoveItem,
|
|
50
|
+
onClickPlus,
|
|
51
|
+
onClickTrash,
|
|
52
|
+
onClickExpand,
|
|
53
|
+
renderItem,
|
|
54
|
+
renderExpandedContent,
|
|
55
|
+
padding = 10,
|
|
56
|
+
}: ArrayControllerProps<Item>) {
|
|
57
|
+
const iconColor = useTheme().colors.icon;
|
|
58
|
+
const primaryLightColor = useTheme().colors.primaryLight;
|
|
59
|
+
|
|
60
|
+
const keys = useMemo(
|
|
61
|
+
() => items.map((item, index) => getKey?.(item) ?? index.toString()),
|
|
62
|
+
[getKey, items]
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const indexes = reversed
|
|
66
|
+
? range(0, items.length).reverse()
|
|
67
|
+
: range(0, items.length);
|
|
68
|
+
|
|
69
|
+
const handleMoveItem = useCallback(
|
|
70
|
+
(
|
|
71
|
+
sourceIndex: number,
|
|
72
|
+
destinationIndex: number,
|
|
73
|
+
position: RelativeDropPosition
|
|
74
|
+
) => {
|
|
75
|
+
if (reversed) {
|
|
76
|
+
if (position === "above") {
|
|
77
|
+
position = "below";
|
|
78
|
+
} else if (position === "below") {
|
|
79
|
+
position = "above";
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
onMoveItem?.(
|
|
84
|
+
sourceIndex,
|
|
85
|
+
position === "below" ? destinationIndex + 1 : destinationIndex
|
|
86
|
+
);
|
|
87
|
+
},
|
|
88
|
+
[onMoveItem, reversed]
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const renderRow = (index: number) => {
|
|
92
|
+
return (
|
|
93
|
+
<ElementRow key={keys[index]}>
|
|
94
|
+
{renderItem({ item: items[index], index: index })}
|
|
95
|
+
</ElementRow>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<InspectorPrimitives.Section id={id} padding={padding} gap="2px">
|
|
101
|
+
<InspectorPrimitives.SectionHeader>
|
|
102
|
+
<Button variant="none" onClick={onClickPlus}>
|
|
103
|
+
<InspectorPrimitives.Title>{title}</InspectorPrimitives.Title>
|
|
104
|
+
</Button>
|
|
105
|
+
<Spacer.Horizontal />
|
|
106
|
+
{withSeparatorElements(
|
|
107
|
+
[
|
|
108
|
+
onClickTrash && (
|
|
109
|
+
<IconButton
|
|
110
|
+
id={`${id}-trash`}
|
|
111
|
+
iconName="TrashIcon"
|
|
112
|
+
color={iconColor}
|
|
113
|
+
onClick={onClickTrash}
|
|
114
|
+
/>
|
|
115
|
+
),
|
|
116
|
+
onClickExpand && (
|
|
117
|
+
<IconButton
|
|
118
|
+
id={`${id}-gear`}
|
|
119
|
+
iconName="GearIcon"
|
|
120
|
+
color={expanded ? primaryLightColor : iconColor}
|
|
121
|
+
onClick={onClickExpand}
|
|
122
|
+
/>
|
|
123
|
+
),
|
|
124
|
+
onClickPlus && (
|
|
125
|
+
<IconButton
|
|
126
|
+
id={`${id}-add`}
|
|
127
|
+
iconName="PlusIcon"
|
|
128
|
+
color={iconColor}
|
|
129
|
+
onClick={onClickPlus}
|
|
130
|
+
/>
|
|
131
|
+
),
|
|
132
|
+
],
|
|
133
|
+
<Spacer.Horizontal size={8} />
|
|
134
|
+
)}
|
|
135
|
+
</InspectorPrimitives.SectionHeader>
|
|
136
|
+
<Stack.V gap="4px">
|
|
137
|
+
{sortable ? (
|
|
138
|
+
<Sortable.Root
|
|
139
|
+
keys={keys}
|
|
140
|
+
renderOverlay={renderRow}
|
|
141
|
+
onMoveItem={handleMoveItem}
|
|
142
|
+
>
|
|
143
|
+
{indexes.map((index) => (
|
|
144
|
+
<Sortable.Item<HTMLDivElement> id={keys[index]} key={keys[index]}>
|
|
145
|
+
{({ relativeDropPosition, ...sortableProps }) => (
|
|
146
|
+
<ItemContainer {...sortableProps}>
|
|
147
|
+
{renderRow(index)}
|
|
148
|
+
{relativeDropPosition && (
|
|
149
|
+
<ListView.DragIndicator
|
|
150
|
+
gap={0}
|
|
151
|
+
colorScheme="primary"
|
|
152
|
+
relativeDropPosition={relativeDropPosition}
|
|
153
|
+
offsetLeft={0}
|
|
154
|
+
/>
|
|
155
|
+
)}
|
|
156
|
+
</ItemContainer>
|
|
157
|
+
)}
|
|
158
|
+
</Sortable.Item>
|
|
159
|
+
))}
|
|
160
|
+
</Sortable.Root>
|
|
161
|
+
) : (
|
|
162
|
+
indexes.map(renderRow)
|
|
163
|
+
)}
|
|
164
|
+
</Stack.V>
|
|
165
|
+
{expanded && renderExpandedContent?.()}
|
|
166
|
+
</InspectorPrimitives.Section>
|
|
167
|
+
);
|
|
168
|
+
});
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import React, { CSSProperties, memo, ReactNode } from "react";
|
|
2
|
+
import styled, { useTheme } from "styled-components";
|
|
3
|
+
import { Spacer } from "./Spacer";
|
|
4
|
+
|
|
5
|
+
export const Section = styled.div<{
|
|
6
|
+
padding?: CSSProperties["padding"];
|
|
7
|
+
gap?: CSSProperties["gap"];
|
|
8
|
+
}>(({ theme, padding = "10px", gap }) => ({
|
|
9
|
+
flex: "0 0 auto",
|
|
10
|
+
display: "flex",
|
|
11
|
+
flexDirection: "column",
|
|
12
|
+
padding,
|
|
13
|
+
gap,
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
export const SectionHeader = styled.div(({ theme }) => ({
|
|
17
|
+
display: "flex",
|
|
18
|
+
alignItems: "center",
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
export const Title = styled.div<{
|
|
22
|
+
textStyle?: "small" | "heading5" | "heading4" | "heading3";
|
|
23
|
+
}>(({ theme, textStyle }) => ({
|
|
24
|
+
display: "flex",
|
|
25
|
+
flexDirection: "row",
|
|
26
|
+
userSelect: "none",
|
|
27
|
+
...(textStyle
|
|
28
|
+
? {
|
|
29
|
+
...theme.textStyles[textStyle],
|
|
30
|
+
color: theme.colors.text,
|
|
31
|
+
}
|
|
32
|
+
: {
|
|
33
|
+
...theme.textStyles.label,
|
|
34
|
+
color: theme.colors.textSubtle,
|
|
35
|
+
fontWeight: "bold",
|
|
36
|
+
}),
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
export const Row = styled.div<{ gap?: CSSProperties["gap"] }>(
|
|
40
|
+
({ theme, gap }) => ({
|
|
41
|
+
flex: "1",
|
|
42
|
+
display: "flex",
|
|
43
|
+
flexDirection: "row",
|
|
44
|
+
alignItems: "center",
|
|
45
|
+
gap,
|
|
46
|
+
})
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
export const Column = styled.div(({ theme }) => ({
|
|
50
|
+
flex: "1",
|
|
51
|
+
display: "flex",
|
|
52
|
+
flexDirection: "column",
|
|
53
|
+
gap: "2px",
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
export const Checkbox = styled.input(({ theme }) => ({
|
|
57
|
+
margin: 0,
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
export const Text = styled.span(({ theme }) => ({
|
|
61
|
+
...theme.textStyles.small,
|
|
62
|
+
}));
|
|
63
|
+
|
|
64
|
+
export const VerticalSeparator = () => (
|
|
65
|
+
<Spacer.Vertical size={useTheme().sizes.inspector.verticalSeparator} />
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
export const HorizontalSeparator = () => (
|
|
69
|
+
<Spacer.Horizontal size={useTheme().sizes.inspector.horizontalSeparator} />
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const SliderRowLabel = styled.span(({ theme }) => ({
|
|
73
|
+
...theme.textStyles.small,
|
|
74
|
+
color: theme.colors.textMuted,
|
|
75
|
+
marginBottom: "-6px",
|
|
76
|
+
}));
|
|
77
|
+
|
|
78
|
+
export const RowLabel = styled.span<{
|
|
79
|
+
textStyle?: "small" | "heading5" | "heading4" | "heading3";
|
|
80
|
+
}>(({ theme, textStyle }) => ({
|
|
81
|
+
// marginBottom: '6px',
|
|
82
|
+
display: "flex",
|
|
83
|
+
...(textStyle
|
|
84
|
+
? {
|
|
85
|
+
...theme.textStyles[textStyle],
|
|
86
|
+
}
|
|
87
|
+
: {
|
|
88
|
+
...theme.textStyles.label,
|
|
89
|
+
color: theme.colors.textSubtle,
|
|
90
|
+
fontWeight: "bold",
|
|
91
|
+
}),
|
|
92
|
+
lineHeight: "19px", // Button height
|
|
93
|
+
}));
|
|
94
|
+
|
|
95
|
+
export const LabeledRow = memo(function LabeledRow({
|
|
96
|
+
id,
|
|
97
|
+
children,
|
|
98
|
+
label,
|
|
99
|
+
labelTextStyle,
|
|
100
|
+
gap,
|
|
101
|
+
right,
|
|
102
|
+
}: {
|
|
103
|
+
id?: string;
|
|
104
|
+
children: ReactNode;
|
|
105
|
+
label: string;
|
|
106
|
+
labelTextStyle?: "small" | "heading5" | "heading4" | "heading3";
|
|
107
|
+
gap?: CSSProperties["gap"];
|
|
108
|
+
right?: ReactNode;
|
|
109
|
+
}) {
|
|
110
|
+
return (
|
|
111
|
+
<Row id={id}>
|
|
112
|
+
<Column>
|
|
113
|
+
<RowLabel textStyle={labelTextStyle}>
|
|
114
|
+
{label}
|
|
115
|
+
{right && <Spacer.Horizontal />}
|
|
116
|
+
{right}
|
|
117
|
+
</RowLabel>
|
|
118
|
+
<Row gap={gap}>{children}</Row>
|
|
119
|
+
</Column>
|
|
120
|
+
</Row>
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
export const LabeledSliderRow = memo(function LabeledRow({
|
|
125
|
+
children,
|
|
126
|
+
label,
|
|
127
|
+
}: {
|
|
128
|
+
children: ReactNode;
|
|
129
|
+
label: string;
|
|
130
|
+
}) {
|
|
131
|
+
return (
|
|
132
|
+
<Row>
|
|
133
|
+
<Column>
|
|
134
|
+
<SliderRowLabel>{label}</SliderRowLabel>
|
|
135
|
+
<Row>{children}</Row>
|
|
136
|
+
</Column>
|
|
137
|
+
</Row>
|
|
138
|
+
);
|
|
139
|
+
});
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { ReactEventHandlers } from "react-use-gesture/dist/types";
|
|
3
|
+
import { mergeEventHandlers } from "../..";
|
|
3
4
|
|
|
4
5
|
const event = { defaultPrevented: false };
|
|
5
6
|
|
|
6
|
-
test(
|
|
7
|
+
test("merges handlers", () => {
|
|
7
8
|
const out: [number, any][] = [];
|
|
8
9
|
|
|
9
10
|
const a: ReactEventHandlers = { onClick: (e) => out.push([1, e]) };
|
|
@@ -15,8 +16,8 @@ test('merges handlers', () => {
|
|
|
15
16
|
|
|
16
17
|
result.onClick?.(event as any);
|
|
17
18
|
|
|
18
|
-
expect(Object.keys(result)).toEqual([
|
|
19
|
-
expect(typeof result.onClick).toEqual(
|
|
19
|
+
expect(Object.keys(result)).toEqual(["onClick"]);
|
|
20
|
+
expect(typeof result.onClick).toEqual("function");
|
|
20
21
|
expect(out).toEqual([
|
|
21
22
|
[1, event],
|
|
22
23
|
[2, event],
|
|
@@ -26,7 +27,7 @@ test('merges handlers', () => {
|
|
|
26
27
|
|
|
27
28
|
const noop = () => {};
|
|
28
29
|
|
|
29
|
-
test(
|
|
30
|
+
test("merges multiple handlers", () => {
|
|
30
31
|
const a: ReactEventHandlers = {
|
|
31
32
|
onMouseDown: noop,
|
|
32
33
|
onMouseMove: noop,
|
|
@@ -40,8 +41,8 @@ test('merges multiple handlers', () => {
|
|
|
40
41
|
const result = mergeEventHandlers(a, b);
|
|
41
42
|
|
|
42
43
|
expect(Object.keys(result)).toEqual([
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
"onMouseDown",
|
|
45
|
+
"onMouseMove",
|
|
46
|
+
"onMouseUp",
|
|
46
47
|
]);
|
|
47
48
|
});
|
package/src/index.tsx
CHANGED
|
@@ -76,3 +76,7 @@ export * from "./utils/fuzzyScorer";
|
|
|
76
76
|
export * from "./utils/sketchColor";
|
|
77
77
|
export * from "./utils/sketchPattern";
|
|
78
78
|
export { default as withSeparatorElements } from "./utils/withSeparatorElements";
|
|
79
|
+
|
|
80
|
+
// Avoid dependency cycles
|
|
81
|
+
export * as InspectorPrimitives from "./components/InspectorPrimitives";
|
|
82
|
+
export * from "./components/ArrayController";
|