@noya-app/noya-designsystem 0.0.2
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 +17 -0
- package/CHANGELOG.md +7 -0
- package/README.md +1 -0
- package/dist/index.d.mts +1178 -0
- package/dist/index.d.ts +1178 -0
- package/dist/index.js +15651 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +15684 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +49 -0
- package/src/__tests__/__snapshots__/fuzzyScorer.test.ts.snap +41 -0
- package/src/__tests__/fuzzyScorer.test.ts +85 -0
- package/src/components/ActivityIndicator.tsx +44 -0
- package/src/components/Avatar.tsx +64 -0
- package/src/components/Button.tsx +209 -0
- package/src/components/Chip.tsx +214 -0
- package/src/components/ContextMenu.tsx +224 -0
- package/src/components/Dialog.tsx +143 -0
- package/src/components/Divider.tsx +40 -0
- package/src/components/DropdownMenu.tsx +208 -0
- package/src/components/FillInputField.tsx +43 -0
- package/src/components/FillPreviewBackground.tsx +132 -0
- package/src/components/GradientPicker.tsx +88 -0
- package/src/components/Grid.tsx +54 -0
- package/src/components/GridView.tsx +468 -0
- package/src/components/IconButton.tsx +46 -0
- package/src/components/InputField.tsx +568 -0
- package/src/components/InputFieldWithCompletions.tsx +477 -0
- package/src/components/Label.tsx +62 -0
- package/src/components/LabeledElementView.tsx +185 -0
- package/src/components/ListView.tsx +950 -0
- package/src/components/Popover.tsx +113 -0
- package/src/components/Progress.tsx +57 -0
- package/src/components/RadioGroup.tsx +163 -0
- package/src/components/ScrollArea.tsx +70 -0
- package/src/components/Select.tsx +152 -0
- package/src/components/Slider.tsx +82 -0
- package/src/components/Sortable.tsx +296 -0
- package/src/components/Spacer.tsx +29 -0
- package/src/components/Stack.tsx +142 -0
- package/src/components/Switch.tsx +67 -0
- package/src/components/Text.tsx +164 -0
- package/src/components/Toast.tsx +86 -0
- package/src/components/Tooltip.tsx +43 -0
- package/src/components/TreeView.tsx +85 -0
- package/src/components/internal/Menu.tsx +222 -0
- package/src/components/internal/TextInput.tsx +296 -0
- package/src/components/internal/__tests__/TextInput.test.tsx +144 -0
- package/src/contexts/DesignSystemConfiguration.tsx +57 -0
- package/src/contexts/DialogContext.tsx +190 -0
- package/src/contexts/GlobalInputBlurContext.tsx +61 -0
- package/src/contexts/ImageDataContext.tsx +44 -0
- package/src/hooks/__tests__/mergeEventHandlers.test.ts +47 -0
- package/src/hooks/mergeEventHandlers.ts +55 -0
- package/src/hooks/useHover.ts +173 -0
- package/src/hooks/useObjectURL.ts +22 -0
- package/src/hooks/usePlatform.ts +13 -0
- package/src/index.tsx +77 -0
- package/src/mediaQuery.ts +13 -0
- package/src/theme/dark.ts +37 -0
- package/src/theme/index.ts +17 -0
- package/src/theme/light.ts +178 -0
- package/src/utils/breakpoints.ts +45 -0
- package/src/utils/completions.ts +21 -0
- package/src/utils/createSectionedMenu.ts +38 -0
- package/src/utils/fuzzyScorer.ts +105 -0
- package/src/utils/getGradientBackground.tsx +33 -0
- package/src/utils/handleNudge.ts +30 -0
- package/src/utils/mouseEvent.ts +7 -0
- package/src/utils/sketchColor.ts +34 -0
- package/src/utils/sketchPattern.ts +29 -0
- package/src/utils/withSeparatorElements.ts +31 -0
- package/tsconfig.json +3 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { CSSObject } from 'styled-components';
|
|
2
|
+
import { mediaQuery } from '../mediaQuery';
|
|
3
|
+
|
|
4
|
+
export const colors = {
|
|
5
|
+
logo: {
|
|
6
|
+
fill: 'rgb(175,175,175)',
|
|
7
|
+
highlight: 'rgb(175,175,175)',
|
|
8
|
+
},
|
|
9
|
+
text: 'rgb(38, 48, 83)',
|
|
10
|
+
textMuted: 'rgb(85, 85, 85)',
|
|
11
|
+
textSubtle: 'rgb(121, 121, 121)',
|
|
12
|
+
textDisabled: 'rgb(160, 160, 160)',
|
|
13
|
+
dividerSubtle: 'rgba(0, 0, 0, 0.04)',
|
|
14
|
+
divider: 'rgba(0, 0, 0, 0.07)',
|
|
15
|
+
dividerStrong: 'rgba(0, 0, 0, 0.09)',
|
|
16
|
+
primary: 'rgb(132, 63, 255)',
|
|
17
|
+
primaryLight: 'rgb(147, 86, 255)',
|
|
18
|
+
secondary: 'rgb(0, 151, 117)',
|
|
19
|
+
secondaryLight: 'rgb(0, 160, 129)',
|
|
20
|
+
secondaryBright: '#0ab557',
|
|
21
|
+
warning: 'rgb(251, 211, 0)',
|
|
22
|
+
neutralBackground: 'rgb(222,223,232)',
|
|
23
|
+
inputBackground: 'rgb(240, 240, 242)',
|
|
24
|
+
inputBackgroundLight: 'rgb(243, 243, 245)',
|
|
25
|
+
codeBackground: 'rgb(250, 250, 250)',
|
|
26
|
+
selectedBackground: 'rgb(242, 245, 250)',
|
|
27
|
+
transparentChecker: 'rgba(255,255,255,0.8)',
|
|
28
|
+
activeBackground: 'rgba(0,0,0,0.1)',
|
|
29
|
+
scrollbar: 'rgba(199,199,199,0.8)',
|
|
30
|
+
placeholderDots: 'rgba(0,0,0,0.3)',
|
|
31
|
+
listView: {
|
|
32
|
+
raisedBackground: 'rgba(0,0,0,0.03)',
|
|
33
|
+
editingBackground: '#fff',
|
|
34
|
+
},
|
|
35
|
+
canvas: {
|
|
36
|
+
background: 'rgb(249,249,249)',
|
|
37
|
+
get selectionStroke() {
|
|
38
|
+
return colors.primary;
|
|
39
|
+
},
|
|
40
|
+
dragHandleStroke: 'rgba(180,180,180,0.5)',
|
|
41
|
+
measurement: 'rgb(207,92,42)',
|
|
42
|
+
sliceOutline: 'rgb(210,210,210)',
|
|
43
|
+
grid: 'rgba(0,0,0,0.05)',
|
|
44
|
+
},
|
|
45
|
+
sidebar: {
|
|
46
|
+
background: 'rgba(252,252,252,0.85)',
|
|
47
|
+
},
|
|
48
|
+
popover: {
|
|
49
|
+
background: 'rgb(252,252,252)',
|
|
50
|
+
divider: 'transparent',
|
|
51
|
+
},
|
|
52
|
+
slider: {
|
|
53
|
+
background: 'white',
|
|
54
|
+
border: '#BBB',
|
|
55
|
+
},
|
|
56
|
+
radioGroup: {
|
|
57
|
+
background: 'white',
|
|
58
|
+
},
|
|
59
|
+
icon: 'rgb(139, 139, 139)',
|
|
60
|
+
iconSelected: 'rgb(220, 220, 220)',
|
|
61
|
+
mask: 'rgb(12,193,67)',
|
|
62
|
+
imageOverlay:
|
|
63
|
+
'linear-gradient(0deg, rgba(132, 63, 255,0.55), rgba(132, 63, 255,0.55))',
|
|
64
|
+
get dragOutline() {
|
|
65
|
+
return colors.primary;
|
|
66
|
+
},
|
|
67
|
+
selection: 'rgb(179,215,254)',
|
|
68
|
+
thumbnailBackground: 'rgb(240, 240, 242)',
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const fonts = {
|
|
72
|
+
normal:
|
|
73
|
+
"-apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
|
|
74
|
+
monospace: "Menlo, Monaco, Consolas, 'Courier New', monospace",
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// The last one, 0.85, I just eyeballed
|
|
78
|
+
const typeScale = [3.052, 2.441, 1.953, 1.563, 1.25, 1, 0.85]; // Major third
|
|
79
|
+
|
|
80
|
+
export const textStyles = {
|
|
81
|
+
title: {
|
|
82
|
+
fontFamily: fonts.normal,
|
|
83
|
+
fontSize: `${typeScale[0]}rem`,
|
|
84
|
+
fontWeight: 'bold',
|
|
85
|
+
lineHeight: '1.4',
|
|
86
|
+
[mediaQuery.small]: {
|
|
87
|
+
fontSize: '36px',
|
|
88
|
+
},
|
|
89
|
+
} as CSSObject,
|
|
90
|
+
subtitle: {
|
|
91
|
+
fontFamily: fonts.normal,
|
|
92
|
+
fontSize: `${typeScale[3]}rem`,
|
|
93
|
+
fontWeight: 500,
|
|
94
|
+
lineHeight: '1.75',
|
|
95
|
+
[mediaQuery.small]: {
|
|
96
|
+
fontSize: '18px',
|
|
97
|
+
},
|
|
98
|
+
} as CSSObject,
|
|
99
|
+
heading1: {
|
|
100
|
+
fontFamily: fonts.normal,
|
|
101
|
+
fontSize: `${typeScale[2]}rem`,
|
|
102
|
+
fontWeight: 500,
|
|
103
|
+
lineHeight: '1.75',
|
|
104
|
+
} as CSSObject,
|
|
105
|
+
heading2: {
|
|
106
|
+
fontFamily: fonts.normal,
|
|
107
|
+
fontSize: `${typeScale[3]}rem`,
|
|
108
|
+
fontWeight: 500,
|
|
109
|
+
lineHeight: '1.75',
|
|
110
|
+
} as CSSObject,
|
|
111
|
+
heading3: {
|
|
112
|
+
fontFamily: fonts.normal,
|
|
113
|
+
fontSize: `${typeScale[4]}rem`,
|
|
114
|
+
fontWeight: 500,
|
|
115
|
+
lineHeight: '1.75',
|
|
116
|
+
} as CSSObject,
|
|
117
|
+
heading4: {
|
|
118
|
+
fontFamily: fonts.normal,
|
|
119
|
+
fontSize: `${typeScale[5]}rem`,
|
|
120
|
+
fontWeight: 500,
|
|
121
|
+
lineHeight: '1.75',
|
|
122
|
+
} as CSSObject,
|
|
123
|
+
heading5: {
|
|
124
|
+
fontFamily: fonts.normal,
|
|
125
|
+
fontSize: `${typeScale[6]}rem`,
|
|
126
|
+
fontWeight: 500,
|
|
127
|
+
lineHeight: '1.75',
|
|
128
|
+
} as CSSObject,
|
|
129
|
+
body: {
|
|
130
|
+
fontFamily: fonts.normal,
|
|
131
|
+
fontSize: `${typeScale[5]}rem`,
|
|
132
|
+
fontWeight: 400,
|
|
133
|
+
lineHeight: '1.75',
|
|
134
|
+
} as CSSObject,
|
|
135
|
+
small: {
|
|
136
|
+
fontFamily: fonts.normal,
|
|
137
|
+
fontSize: `${typeScale[6]}rem`,
|
|
138
|
+
fontWeight: 400,
|
|
139
|
+
lineHeight: '1.4',
|
|
140
|
+
} as CSSObject,
|
|
141
|
+
code: {
|
|
142
|
+
fontFamily: fonts.monospace,
|
|
143
|
+
fontSize: '90%',
|
|
144
|
+
lineHeight: '1.5',
|
|
145
|
+
} as CSSObject,
|
|
146
|
+
label: {
|
|
147
|
+
fontFamily: fonts.normal,
|
|
148
|
+
fontSize: '0.62rem',
|
|
149
|
+
fontWeight: 400,
|
|
150
|
+
lineHeight: '1.4',
|
|
151
|
+
textTransform: 'uppercase',
|
|
152
|
+
letterSpacing: '0.3px',
|
|
153
|
+
} as CSSObject,
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
export const sizes = {
|
|
157
|
+
sidebarWidth: 260,
|
|
158
|
+
toolbar: {
|
|
159
|
+
height: 46,
|
|
160
|
+
itemSeparator: 8,
|
|
161
|
+
},
|
|
162
|
+
inspector: {
|
|
163
|
+
horizontalSeparator: 8,
|
|
164
|
+
verticalSeparator: 10,
|
|
165
|
+
},
|
|
166
|
+
spacing: {
|
|
167
|
+
nano: 2,
|
|
168
|
+
micro: 4,
|
|
169
|
+
small: 8,
|
|
170
|
+
medium: 16,
|
|
171
|
+
large: 32,
|
|
172
|
+
xlarge: 64,
|
|
173
|
+
xxlarge: 128,
|
|
174
|
+
},
|
|
175
|
+
dialog: {
|
|
176
|
+
padding: 16,
|
|
177
|
+
},
|
|
178
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { CSSObject } from 'styled-components';
|
|
2
|
+
|
|
3
|
+
type BreakpointKey = number | string;
|
|
4
|
+
|
|
5
|
+
export type Breakpoint<T extends object> = [BreakpointKey, T];
|
|
6
|
+
|
|
7
|
+
export type BreakpointCollection<T extends object> =
|
|
8
|
+
| Breakpoint<T>[]
|
|
9
|
+
| Record<BreakpointKey, T>;
|
|
10
|
+
|
|
11
|
+
export type StyleMap = Record<string, CSSObject>;
|
|
12
|
+
|
|
13
|
+
export function mergeBreakpoints(
|
|
14
|
+
breakpoints: BreakpointCollection<CSSObject>,
|
|
15
|
+
): StyleMap;
|
|
16
|
+
export function mergeBreakpoints<T extends object>(
|
|
17
|
+
breakpoints: BreakpointCollection<T>,
|
|
18
|
+
transform?: (value: T) => CSSObject,
|
|
19
|
+
): StyleMap;
|
|
20
|
+
export function mergeBreakpoints<T extends object>(
|
|
21
|
+
breakpoints: BreakpointCollection<T>,
|
|
22
|
+
transform?: (value: T) => CSSObject,
|
|
23
|
+
): StyleMap {
|
|
24
|
+
const breakpointMap = Array.isArray(breakpoints)
|
|
25
|
+
? breakpoints.reduce(
|
|
26
|
+
(result: Record<number, T>, item: any /* TODO Fix type */) => {
|
|
27
|
+
const [key, value] = item;
|
|
28
|
+
result[key] = result[key] ? { ...result[key], ...value } : value;
|
|
29
|
+
return result;
|
|
30
|
+
},
|
|
31
|
+
{},
|
|
32
|
+
)
|
|
33
|
+
: breakpoints;
|
|
34
|
+
|
|
35
|
+
const breakpointList = Object.entries(breakpointMap) as Breakpoint<T>[];
|
|
36
|
+
|
|
37
|
+
const transformedList = breakpointList
|
|
38
|
+
.sort((a, b) => Number(b[0]) - Number(a[0]))
|
|
39
|
+
.map(([width, value]: Breakpoint<T>) => [
|
|
40
|
+
`@media screen and (max-width: ${width}px)`,
|
|
41
|
+
transform?.(value) ?? value,
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
return Object.fromEntries(transformedList);
|
|
45
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { IScoredItem } from './fuzzyScorer';
|
|
3
|
+
|
|
4
|
+
export type CompletionItem = {
|
|
5
|
+
type?: undefined;
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
icon?: ReactNode;
|
|
9
|
+
alwaysInclude?: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type CompletionSectionHeader = {
|
|
13
|
+
type: 'sectionHeader';
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
maxVisibleItems?: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type CompletionListItem =
|
|
20
|
+
| (CompletionItem & IScoredItem)
|
|
21
|
+
| CompletionSectionHeader;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MenuItem,
|
|
3
|
+
RegularMenuItem,
|
|
4
|
+
SEPARATOR_ITEM,
|
|
5
|
+
} from '../components/internal/Menu';
|
|
6
|
+
|
|
7
|
+
export type Optional<T> = T | false | null | undefined;
|
|
8
|
+
|
|
9
|
+
function withSeparators<T>(elements: T[], separator: T) {
|
|
10
|
+
const result: T[] = [];
|
|
11
|
+
|
|
12
|
+
for (let i = 0; i < elements.length; i++) {
|
|
13
|
+
result.push(elements[i]);
|
|
14
|
+
|
|
15
|
+
if (i !== elements.length - 1) {
|
|
16
|
+
result.push(separator);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type MenuConfig<T extends string> = Optional<
|
|
24
|
+
Optional<RegularMenuItem<T>>[]
|
|
25
|
+
>[];
|
|
26
|
+
|
|
27
|
+
export function createSectionedMenu<T extends string>(
|
|
28
|
+
...sections: MenuConfig<T>
|
|
29
|
+
): MenuItem<T>[] {
|
|
30
|
+
const nonEmptySections = sections
|
|
31
|
+
.flatMap((section) => (section ? [section] : []))
|
|
32
|
+
.map((section) => section.flatMap((item) => (item ? [item] : [])))
|
|
33
|
+
.filter((section) => section.length > 0);
|
|
34
|
+
|
|
35
|
+
return withSeparators<MenuItem<T>[]>(nonEmptySections, [
|
|
36
|
+
SEPARATOR_ITEM,
|
|
37
|
+
]).flat();
|
|
38
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { IItemScore, scoreFilePathFuzzy } from 'vscode-fuzzy-scorer';
|
|
2
|
+
|
|
3
|
+
export function fuzzyScore({ item, query }: { item: string; query: string }) {
|
|
4
|
+
return scoreFilePathFuzzy({ path: item, query });
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type IScoredItem = IItemScore & {
|
|
8
|
+
index: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function fuzzyFilter({
|
|
12
|
+
items,
|
|
13
|
+
query,
|
|
14
|
+
scoreThreshold = 0,
|
|
15
|
+
}: {
|
|
16
|
+
items: string[];
|
|
17
|
+
query: string;
|
|
18
|
+
scoreThreshold?: number;
|
|
19
|
+
}) {
|
|
20
|
+
const scoredItems = items.map(
|
|
21
|
+
(text, index): IScoredItem => ({
|
|
22
|
+
index,
|
|
23
|
+
...fuzzyScore({ item: text, query }),
|
|
24
|
+
}),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
if (!query) return scoredItems;
|
|
28
|
+
|
|
29
|
+
return scoredItems
|
|
30
|
+
.filter(({ score }) => score > scoreThreshold)
|
|
31
|
+
.sort((a, b) => b.score - a.score);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type IToken =
|
|
35
|
+
| { type: 'text'; text: string }
|
|
36
|
+
| { type: 'match'; text: string };
|
|
37
|
+
|
|
38
|
+
export type MatchRange = { start: number; end: number };
|
|
39
|
+
|
|
40
|
+
export function fuzzyTokenize({
|
|
41
|
+
item,
|
|
42
|
+
itemScore,
|
|
43
|
+
}: {
|
|
44
|
+
item: string;
|
|
45
|
+
itemScore: IItemScore;
|
|
46
|
+
}): IToken[] {
|
|
47
|
+
const tokens: IToken[] = [];
|
|
48
|
+
|
|
49
|
+
let lastMatchIndex = 0;
|
|
50
|
+
|
|
51
|
+
const matches: MatchRange[] = mergeRanges([
|
|
52
|
+
...(itemScore.labelMatch ?? []),
|
|
53
|
+
...(itemScore.descriptionMatch ?? []),
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
for (const match of matches) {
|
|
57
|
+
if (match.start > lastMatchIndex) {
|
|
58
|
+
tokens.push({
|
|
59
|
+
type: 'text',
|
|
60
|
+
text: item.slice(lastMatchIndex, match.start),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
tokens.push({
|
|
65
|
+
type: 'match',
|
|
66
|
+
text: item.slice(match.start, match.end),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
lastMatchIndex = match.end;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (lastMatchIndex < item.length) {
|
|
73
|
+
tokens.push({
|
|
74
|
+
type: 'text',
|
|
75
|
+
text: item.slice(lastMatchIndex),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return tokens;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function mergeRanges(ranges: MatchRange[]): MatchRange[] {
|
|
83
|
+
const merged: MatchRange[] = [];
|
|
84
|
+
|
|
85
|
+
const sorted = [...ranges].sort((a, b) => a.start - b.start);
|
|
86
|
+
|
|
87
|
+
let current: MatchRange | undefined;
|
|
88
|
+
|
|
89
|
+
for (const range of sorted) {
|
|
90
|
+
if (!current) {
|
|
91
|
+
current = range;
|
|
92
|
+
} else if (range.start <= current.end) {
|
|
93
|
+
current.end = Math.max(current.end, range.end);
|
|
94
|
+
} else {
|
|
95
|
+
merged.push(current);
|
|
96
|
+
current = range;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (current) {
|
|
101
|
+
merged.push(current);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return merged;
|
|
105
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Sketch } from '@noya-app/noya-file-format';
|
|
2
|
+
import { sketchColorToRgbaString } from './sketchColor';
|
|
3
|
+
|
|
4
|
+
const getRGBAColor = (value: Sketch.GradientStop[], num: 100 | 360) =>
|
|
5
|
+
[...value]
|
|
6
|
+
.sort((a, b) => a.position - b.position)
|
|
7
|
+
.map(
|
|
8
|
+
(g) =>
|
|
9
|
+
`${sketchColorToRgbaString(g.color)} ${g.position * num}${
|
|
10
|
+
num === 100 ? '%' : 'deg'
|
|
11
|
+
}`,
|
|
12
|
+
)
|
|
13
|
+
.join(', \n');
|
|
14
|
+
|
|
15
|
+
export function getGradientBackground(
|
|
16
|
+
value: Sketch.GradientStop[],
|
|
17
|
+
type: Sketch.GradientType,
|
|
18
|
+
direction?: number,
|
|
19
|
+
) {
|
|
20
|
+
if (type === Sketch.GradientType.Angular) {
|
|
21
|
+
return `conic-gradient(${getRGBAColor(value, 360)})`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const color = getRGBAColor(value, 100);
|
|
25
|
+
const position =
|
|
26
|
+
type === Sketch.GradientType.Radial
|
|
27
|
+
? 'radial-gradient(circle'
|
|
28
|
+
: `linear-gradient(${
|
|
29
|
+
typeof direction !== undefined ? direction : 180
|
|
30
|
+
}deg`;
|
|
31
|
+
|
|
32
|
+
return `${position}, ${color})`;
|
|
33
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Given a KeyboardEvent, returns the nudge amount
|
|
2
|
+
export default function handleNudge(e: {
|
|
3
|
+
key: string;
|
|
4
|
+
shiftKey: boolean;
|
|
5
|
+
altKey: boolean;
|
|
6
|
+
}): number | undefined {
|
|
7
|
+
let handled = false;
|
|
8
|
+
let amount = 0;
|
|
9
|
+
|
|
10
|
+
switch (e.key) {
|
|
11
|
+
case 'ArrowUp':
|
|
12
|
+
amount = 1;
|
|
13
|
+
handled = true;
|
|
14
|
+
break;
|
|
15
|
+
case 'ArrowDown':
|
|
16
|
+
amount = -1;
|
|
17
|
+
handled = true;
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!handled) return;
|
|
22
|
+
|
|
23
|
+
if (e.shiftKey) {
|
|
24
|
+
amount *= 10;
|
|
25
|
+
} else if (e.altKey) {
|
|
26
|
+
amount *= 0.1;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return handled ? amount : undefined;
|
|
30
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
hsvaToRgbaString,
|
|
3
|
+
RgbaColor,
|
|
4
|
+
rgbaToHex,
|
|
5
|
+
rgbaToHsva,
|
|
6
|
+
} from '@noya-app/noya-colorpicker';
|
|
7
|
+
import type { Sketch } from '@noya-app/noya-file-format';
|
|
8
|
+
|
|
9
|
+
export function sketchColorToRgba(value: Sketch.Color): RgbaColor {
|
|
10
|
+
return {
|
|
11
|
+
r: Math.floor(value.red * 255),
|
|
12
|
+
g: Math.floor(value.green * 255),
|
|
13
|
+
b: Math.floor(value.blue * 255),
|
|
14
|
+
a: value.alpha,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function sketchColorToRgbaString(value: Sketch.Color): string {
|
|
19
|
+
return hsvaToRgbaString(rgbaToHsva(sketchColorToRgba(value)));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function rgbaToSketchColor(value: RgbaColor): Sketch.Color {
|
|
23
|
+
return {
|
|
24
|
+
_class: 'color',
|
|
25
|
+
alpha: value.a,
|
|
26
|
+
red: value.r / 255,
|
|
27
|
+
green: value.g / 255,
|
|
28
|
+
blue: value.b / 255,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function sketchColorToHex(value: Sketch.Color): string {
|
|
33
|
+
return rgbaToHex(sketchColorToRgba(value)).toUpperCase();
|
|
34
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Sketch } from '@noya-app/noya-file-format';
|
|
2
|
+
|
|
3
|
+
export const SUPPORTED_IMAGE_UPLOAD_TYPES = [
|
|
4
|
+
'image/png' as const,
|
|
5
|
+
'image/jpeg' as const,
|
|
6
|
+
'image/webp' as const,
|
|
7
|
+
'application/pdf' as const,
|
|
8
|
+
'image/svg+xml' as const,
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
export const SUPPORTED_CANVAS_UPLOAD_TYPES = [
|
|
12
|
+
...SUPPORTED_IMAGE_UPLOAD_TYPES,
|
|
13
|
+
'' as const,
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
export type SupportedImageUploadType =
|
|
17
|
+
(typeof SUPPORTED_IMAGE_UPLOAD_TYPES)[number];
|
|
18
|
+
|
|
19
|
+
export type SupportedCanvasUploadType =
|
|
20
|
+
(typeof SUPPORTED_CANVAS_UPLOAD_TYPES)[number];
|
|
21
|
+
|
|
22
|
+
export type SketchPattern = {
|
|
23
|
+
// This _class doesn't exist in Sketch, but it's convenient in `switch`
|
|
24
|
+
// statements to be able to reference _class.
|
|
25
|
+
_class: 'pattern';
|
|
26
|
+
image?: Sketch.FileRef | Sketch.DataRef;
|
|
27
|
+
patternFillType: Sketch.PatternFillType;
|
|
28
|
+
patternTileScale: number;
|
|
29
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Children, cloneElement, isValidElement, ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
function createKey(key: string | number) {
|
|
4
|
+
return `s-${key}`;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export default function withSeparatorElements(
|
|
8
|
+
elements: ReactNode,
|
|
9
|
+
separator: ReactNode | (() => ReactNode)
|
|
10
|
+
) {
|
|
11
|
+
const childrenArray = Children.toArray(elements);
|
|
12
|
+
|
|
13
|
+
for (let i = childrenArray.length - 1; i > 0; i--) {
|
|
14
|
+
let sep =
|
|
15
|
+
typeof separator === "function"
|
|
16
|
+
? separator()
|
|
17
|
+
: isValidElement(separator)
|
|
18
|
+
? cloneElement(separator, { key: createKey(i) })
|
|
19
|
+
: separator;
|
|
20
|
+
|
|
21
|
+
if (isValidElement(sep) && sep.key == null) {
|
|
22
|
+
sep = cloneElement(sep, { key: createKey(i) });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!sep || typeof sep === "boolean") continue;
|
|
26
|
+
|
|
27
|
+
childrenArray.splice(i, 0, sep);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return childrenArray;
|
|
31
|
+
}
|
package/tsconfig.json
ADDED