@noya-app/noya-designsystem 0.1.29 → 0.1.31
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/.turbo/turbo-lint.log +13 -0
- package/CHANGELOG.md +16 -0
- package/dist/index.d.mts +172 -200
- package/dist/index.d.ts +172 -200
- package/dist/index.js +304 -467
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +301 -468
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -3
- package/src/components/Button.tsx +5 -3
- package/src/components/GridView.tsx +37 -35
- package/src/components/InputField.tsx +118 -88
- package/src/components/ListView.tsx +1 -0
- package/src/components/RadioGroup.tsx +2 -0
- package/src/components/SelectMenu.tsx +69 -12
- package/src/components/Stack.tsx +10 -1
- package/src/components/Switch.tsx +1 -0
- package/src/components/WorkspaceLayout.tsx +29 -6
- package/src/index.tsx +1 -2
- package/tailwind.config.ts +283 -0
- package/tailwind.d.ts +11 -0
- package/tsconfig.json +4 -1
- package/src/components/ArrayController.tsx +0 -168
- package/src/components/Select.tsx +0 -183
|
@@ -1,168 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
import { memoize } from "@noya-app/noya-utils";
|
|
2
|
-
import React, {
|
|
3
|
-
createContext,
|
|
4
|
-
CSSProperties,
|
|
5
|
-
memo,
|
|
6
|
-
ReactNode,
|
|
7
|
-
useCallback,
|
|
8
|
-
useContext,
|
|
9
|
-
useEffect,
|
|
10
|
-
useMemo,
|
|
11
|
-
useRef,
|
|
12
|
-
} from "react";
|
|
13
|
-
import styled from "styled-components";
|
|
14
|
-
|
|
15
|
-
type SelectContextValue = {
|
|
16
|
-
addListener: (value: string, listener: () => void) => void;
|
|
17
|
-
removeListener: (value: string) => void;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const SelectContext = createContext<SelectContextValue | undefined>(undefined);
|
|
21
|
-
|
|
22
|
-
interface SelectOptionProps<T extends string> {
|
|
23
|
-
value: T;
|
|
24
|
-
title?: string;
|
|
25
|
-
onSelect?: () => void;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export const SelectOption = memo(function SelectOption<T extends string>({
|
|
29
|
-
value,
|
|
30
|
-
title,
|
|
31
|
-
onSelect,
|
|
32
|
-
}: SelectOptionProps<T>) {
|
|
33
|
-
const { addListener, removeListener } = useContext(SelectContext)!;
|
|
34
|
-
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
if (!onSelect) return;
|
|
37
|
-
|
|
38
|
-
addListener(value, onSelect);
|
|
39
|
-
|
|
40
|
-
return () => removeListener(value);
|
|
41
|
-
}, [addListener, onSelect, removeListener, value]);
|
|
42
|
-
|
|
43
|
-
return <option value={value}>{title ?? value}</option>;
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
const createChevronSVGString = memoize((color: string) =>
|
|
47
|
-
`
|
|
48
|
-
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 15 15' fill='${color}'>
|
|
49
|
-
<path d='M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z'></path>
|
|
50
|
-
</svg>
|
|
51
|
-
`.replace(/\n/g, "")
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
const SelectContainer = styled.div<{ $flex: CSSProperties["flex"] }>(
|
|
55
|
-
({ $flex }) => ({
|
|
56
|
-
flex: $flex ?? "1 1 0px",
|
|
57
|
-
display: "flex",
|
|
58
|
-
flexDirection: "row",
|
|
59
|
-
position: "relative",
|
|
60
|
-
})
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
const SelectElement = styled.select<{ $flex: CSSProperties["flex"] }>(
|
|
64
|
-
({ theme, $flex }) => ({
|
|
65
|
-
appearance: "none",
|
|
66
|
-
...theme.textStyles.small,
|
|
67
|
-
color: theme.colors.text,
|
|
68
|
-
lineHeight: "19px",
|
|
69
|
-
width: "0px", // Reset intrinsic width
|
|
70
|
-
flex: $flex ?? "1 1 0px",
|
|
71
|
-
position: "relative",
|
|
72
|
-
border: "0",
|
|
73
|
-
outline: "none",
|
|
74
|
-
minWidth: "0",
|
|
75
|
-
textAlign: "left",
|
|
76
|
-
alignSelf: "stretch",
|
|
77
|
-
borderRadius: "4px",
|
|
78
|
-
paddingTop: "4px",
|
|
79
|
-
paddingBottom: "4px",
|
|
80
|
-
paddingLeft: "6px",
|
|
81
|
-
paddingRight: "23px",
|
|
82
|
-
background: [
|
|
83
|
-
`calc(100% - 6px) / 15px url("data:image/svg+xml;utf8,${createChevronSVGString(
|
|
84
|
-
theme.colors.icon
|
|
85
|
-
)}") no-repeat`,
|
|
86
|
-
theme.colors.inputBackground,
|
|
87
|
-
].join(","),
|
|
88
|
-
"&:focus": {
|
|
89
|
-
boxShadow: `0 0 0 2px ${theme.colors.primary}`,
|
|
90
|
-
},
|
|
91
|
-
})
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
const SelectLabel = styled.label(({ theme }) => ({
|
|
95
|
-
...theme.textStyles.label,
|
|
96
|
-
color: theme.colors.textDisabled,
|
|
97
|
-
lineHeight: "19px",
|
|
98
|
-
position: "absolute",
|
|
99
|
-
inset: "0",
|
|
100
|
-
fontWeight: "bold",
|
|
101
|
-
fontSize: "60%",
|
|
102
|
-
pointerEvents: "none",
|
|
103
|
-
display: "flex",
|
|
104
|
-
alignItems: "center",
|
|
105
|
-
justifyContent: "end",
|
|
106
|
-
paddingRight: "24px",
|
|
107
|
-
}));
|
|
108
|
-
|
|
109
|
-
type ChildrenProps<T> =
|
|
110
|
-
| {
|
|
111
|
-
children: ReactNode;
|
|
112
|
-
}
|
|
113
|
-
| {
|
|
114
|
-
options: readonly T[];
|
|
115
|
-
getTitle?: (option: T, index: number) => string;
|
|
116
|
-
onChange: (value: T) => void;
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
type Props<T extends string> = ChildrenProps<T> & {
|
|
120
|
-
id: string;
|
|
121
|
-
flex?: CSSProperties["flex"];
|
|
122
|
-
value: T;
|
|
123
|
-
label?: ReactNode;
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
export const Select = memo(function Select<T extends string>({
|
|
127
|
-
id,
|
|
128
|
-
flex,
|
|
129
|
-
value,
|
|
130
|
-
label,
|
|
131
|
-
...rest
|
|
132
|
-
}: Props<T>) {
|
|
133
|
-
const options = "options" in rest ? rest.options : undefined;
|
|
134
|
-
const getTitle = "options" in rest ? rest.getTitle : undefined;
|
|
135
|
-
const onChange = "options" in rest ? rest.onChange : undefined;
|
|
136
|
-
const children = "options" in rest ? undefined : rest.children;
|
|
137
|
-
|
|
138
|
-
const optionElements = useMemo(
|
|
139
|
-
() =>
|
|
140
|
-
options
|
|
141
|
-
? options.map((option, index) => (
|
|
142
|
-
<SelectOption
|
|
143
|
-
key={option}
|
|
144
|
-
value={option}
|
|
145
|
-
title={getTitle?.(option, index)}
|
|
146
|
-
onSelect={() => onChange?.(option)}
|
|
147
|
-
/>
|
|
148
|
-
))
|
|
149
|
-
: children,
|
|
150
|
-
[children, getTitle, onChange, options]
|
|
151
|
-
);
|
|
152
|
-
|
|
153
|
-
const listeners = useRef<Map<string, () => void>>(new Map());
|
|
154
|
-
|
|
155
|
-
const contextValue: SelectContextValue = useMemo(
|
|
156
|
-
() => ({
|
|
157
|
-
addListener: (value, listener) => listeners.current.set(value, listener),
|
|
158
|
-
removeListener: (value) => listeners.current.delete(value),
|
|
159
|
-
}),
|
|
160
|
-
[]
|
|
161
|
-
);
|
|
162
|
-
|
|
163
|
-
return (
|
|
164
|
-
<SelectContext.Provider value={contextValue}>
|
|
165
|
-
<SelectContainer $flex={flex}>
|
|
166
|
-
<SelectElement
|
|
167
|
-
id={id}
|
|
168
|
-
$flex={flex}
|
|
169
|
-
value={value}
|
|
170
|
-
onChange={useCallback(
|
|
171
|
-
(event: React.ChangeEvent<HTMLSelectElement>) =>
|
|
172
|
-
listeners.current.get(event.target.value)?.(),
|
|
173
|
-
[listeners]
|
|
174
|
-
)}
|
|
175
|
-
onPointerDown={(event) => event.stopPropagation()}
|
|
176
|
-
>
|
|
177
|
-
{optionElements}
|
|
178
|
-
</SelectElement>
|
|
179
|
-
</SelectContainer>
|
|
180
|
-
{label && <SelectLabel htmlFor={id}>{label}</SelectLabel>}
|
|
181
|
-
</SelectContext.Provider>
|
|
182
|
-
);
|
|
183
|
-
});
|