@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
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@noya-app/noya-designsystem",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"main": "./dist/index.js",
|
|
5
|
+
"module": "./dist/index.mjs",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsup src/index.tsx --format cjs,esm --dts --sourcemap",
|
|
9
|
+
"dev": "npm run build -- --watch"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@dnd-kit/core": "3.1.1",
|
|
13
|
+
"@dnd-kit/modifiers": "3.0.0",
|
|
14
|
+
"@dnd-kit/sortable": "4.0.0",
|
|
15
|
+
"@noya-app/noya-colorpicker": "0.0.2",
|
|
16
|
+
"@radix-ui/primitive": "^1.0.0",
|
|
17
|
+
"@radix-ui/react-avatar": "^1.0.1",
|
|
18
|
+
"@radix-ui/react-compose-refs": "^1.0.0",
|
|
19
|
+
"@radix-ui/react-context-menu": "^2.0.1",
|
|
20
|
+
"@radix-ui/react-dialog": "^1.0.2",
|
|
21
|
+
"@radix-ui/react-dropdown-menu": "^2.0.1",
|
|
22
|
+
"@radix-ui/react-popover": "^1.0.2",
|
|
23
|
+
"@radix-ui/react-progress": "^1.0.1",
|
|
24
|
+
"@radix-ui/react-radio-group": "^1.1.0",
|
|
25
|
+
"@radix-ui/react-scroll-area": "^1.0.2",
|
|
26
|
+
"@radix-ui/react-slider": "^1.1.0",
|
|
27
|
+
"@radix-ui/react-switch": "^1.0.1",
|
|
28
|
+
"@radix-ui/react-toast": "^1.1.4",
|
|
29
|
+
"@radix-ui/react-toggle-group": "^1.0.1",
|
|
30
|
+
"@radix-ui/react-tooltip": "^1.0.2",
|
|
31
|
+
"@radix-ui/react-utils": "^0.0.5",
|
|
32
|
+
"immer": "9.0.5",
|
|
33
|
+
"kiwi.js": "^1.1.3",
|
|
34
|
+
"react-use-gesture": "9.1.3",
|
|
35
|
+
"react-virtualized": "^9.22.3",
|
|
36
|
+
"react-window": "^1.8.8",
|
|
37
|
+
"styled-components": "5.3.0",
|
|
38
|
+
"vscode-fuzzy-scorer": "^0.0.4"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/react-virtualized": "^9.21.21",
|
|
42
|
+
"@types/react-window": "^1.8.5",
|
|
43
|
+
"@types/styled-components": "5.1.26"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"react": "*",
|
|
47
|
+
"react-dom": "*"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Bun Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`filter single match 1`] = `
|
|
4
|
+
[
|
|
5
|
+
{
|
|
6
|
+
"index": 0,
|
|
7
|
+
"labelMatch": [
|
|
8
|
+
{
|
|
9
|
+
"end": 9,
|
|
10
|
+
"start": 5,
|
|
11
|
+
},
|
|
12
|
+
],
|
|
13
|
+
"score": 32768,
|
|
14
|
+
},
|
|
15
|
+
]
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
exports[`filter multiple matches 1`] = `
|
|
19
|
+
[
|
|
20
|
+
{
|
|
21
|
+
"index": 1,
|
|
22
|
+
"labelMatch": [
|
|
23
|
+
{
|
|
24
|
+
"end": 1,
|
|
25
|
+
"start": 0,
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
"score": 131072,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"index": 0,
|
|
32
|
+
"labelMatch": [
|
|
33
|
+
{
|
|
34
|
+
"end": 6,
|
|
35
|
+
"start": 5,
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
"score": 32768,
|
|
39
|
+
},
|
|
40
|
+
]
|
|
41
|
+
`;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { fuzzyFilter, fuzzyScore, fuzzyTokenize } from "../utils/fuzzyScorer";
|
|
3
|
+
|
|
4
|
+
test("prefix match", () => {
|
|
5
|
+
const result = fuzzyScore({ item: "font-bold", query: "font" });
|
|
6
|
+
|
|
7
|
+
expect(result.labelMatch).toEqual([{ start: 0, end: 4 }]);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("suffix match", () => {
|
|
11
|
+
const result = fuzzyScore({ item: "font-bold", query: "bold" });
|
|
12
|
+
|
|
13
|
+
expect(result.labelMatch).toEqual([{ start: 5, end: 9 }]);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("non-contiguous match", () => {
|
|
17
|
+
const result = fuzzyScore({ item: "font-bold", query: "fb" });
|
|
18
|
+
|
|
19
|
+
expect(result.labelMatch).toEqual([
|
|
20
|
+
{ start: 0, end: 1 },
|
|
21
|
+
{ start: 5, end: 6 },
|
|
22
|
+
]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("filter single match", () => {
|
|
26
|
+
const result = fuzzyFilter({
|
|
27
|
+
items: ["font-bold", "font-italic", "font-regular"],
|
|
28
|
+
query: "bold",
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
expect(result).toMatchSnapshot();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("filter multiple matches", () => {
|
|
35
|
+
const result = fuzzyFilter({
|
|
36
|
+
items: ["font-bold", "border"],
|
|
37
|
+
query: "b",
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
expect(result).toMatchSnapshot();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("tokenizes prefix match", () => {
|
|
44
|
+
const item = "font-bold";
|
|
45
|
+
const query = "font";
|
|
46
|
+
const result = fuzzyTokenize({
|
|
47
|
+
item,
|
|
48
|
+
itemScore: fuzzyScore({ item, query }),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
expect(result).toEqual([
|
|
52
|
+
{ type: "match", text: "font" },
|
|
53
|
+
{ type: "text", text: "-bold" },
|
|
54
|
+
]);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("tokenizes suffix match", () => {
|
|
58
|
+
const item = "font-bold";
|
|
59
|
+
const query = "bold";
|
|
60
|
+
const result = fuzzyTokenize({
|
|
61
|
+
item,
|
|
62
|
+
itemScore: fuzzyScore({ item, query }),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
expect(result).toEqual([
|
|
66
|
+
{ type: "text", text: "font-" },
|
|
67
|
+
{ type: "match", text: "bold" },
|
|
68
|
+
]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("tokenizes non-contiguous match", () => {
|
|
72
|
+
const item = "font-bold";
|
|
73
|
+
const query = "fb";
|
|
74
|
+
const result = fuzzyTokenize({
|
|
75
|
+
item,
|
|
76
|
+
itemScore: fuzzyScore({ item, query }),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(result).toEqual([
|
|
80
|
+
{ type: "match", text: "f" },
|
|
81
|
+
{ type: "text", text: "ont-" },
|
|
82
|
+
{ type: "match", text: "b" },
|
|
83
|
+
{ type: "text", text: "old" },
|
|
84
|
+
]);
|
|
85
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React, { memo } from 'react';
|
|
2
|
+
import styled, { keyframes } from 'styled-components';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
size?: number;
|
|
6
|
+
opacity?: number;
|
|
7
|
+
color?: string;
|
|
8
|
+
trackColor?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const spin = keyframes`
|
|
12
|
+
0% { transform: rotate(0deg); }
|
|
13
|
+
100% { transform: rotate(360deg); }
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
const Spinner = styled.div`
|
|
17
|
+
display: flex;
|
|
18
|
+
justify-content: center;
|
|
19
|
+
align-items: center;
|
|
20
|
+
opacity: ${({ opacity }: Props) => opacity};
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
// Use css syntax to support keyframes
|
|
24
|
+
const SpinnerCircle = styled.div<Props>`
|
|
25
|
+
border: 2px solid ${({ trackColor }) => trackColor};
|
|
26
|
+
border-top: 2px solid ${({ color }) => color};
|
|
27
|
+
border-radius: 50%;
|
|
28
|
+
width: ${({ size }) => `${size}px`};
|
|
29
|
+
height: ${({ size }) => `${size}px`};
|
|
30
|
+
animation: ${spin} 1s linear infinite;
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
export const ActivityIndicator = memo(function ActivityIndicator({
|
|
34
|
+
size = 16,
|
|
35
|
+
opacity = 1,
|
|
36
|
+
color = 'black',
|
|
37
|
+
trackColor = 'rgba(0, 0, 0, 0.1)',
|
|
38
|
+
}: Props) {
|
|
39
|
+
return (
|
|
40
|
+
<Spinner opacity={opacity}>
|
|
41
|
+
<SpinnerCircle size={size} color={color} trackColor={trackColor} />
|
|
42
|
+
</Spinner>
|
|
43
|
+
);
|
|
44
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as AvatarPrimitive from '@radix-ui/react-avatar';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
|
|
5
|
+
const AvatarRoot = styled(AvatarPrimitive.Root)<{
|
|
6
|
+
size: number;
|
|
7
|
+
overflow: number;
|
|
8
|
+
}>(({ theme, size, overflow }) => ({
|
|
9
|
+
display: 'inline-flex',
|
|
10
|
+
alignItems: 'center',
|
|
11
|
+
justifyContent: 'center',
|
|
12
|
+
verticalAlign: 'middle',
|
|
13
|
+
overflow: 'hidden',
|
|
14
|
+
userSelect: 'none',
|
|
15
|
+
width: size,
|
|
16
|
+
height: size,
|
|
17
|
+
borderRadius: '100%',
|
|
18
|
+
backgroundColor: theme.colors.secondaryLight,
|
|
19
|
+
...(overflow && {
|
|
20
|
+
margin: `-${overflow}px`,
|
|
21
|
+
}),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
const AvatarImage = styled(AvatarPrimitive.Image)({
|
|
25
|
+
width: '100%',
|
|
26
|
+
height: '100%',
|
|
27
|
+
objectFit: 'cover',
|
|
28
|
+
borderRadius: 'inherit',
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const AvatarFallback = styled(AvatarPrimitive.Fallback)<{
|
|
32
|
+
size: number;
|
|
33
|
+
}>(({ theme, size }) => ({
|
|
34
|
+
fontSize: '0.8rem',
|
|
35
|
+
fontWeight: 500,
|
|
36
|
+
width: '100%',
|
|
37
|
+
height: '100%',
|
|
38
|
+
display: 'flex',
|
|
39
|
+
alignItems: 'center',
|
|
40
|
+
justifyContent: 'center',
|
|
41
|
+
color: 'white',
|
|
42
|
+
lineHeight: `${size}px`,
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
export function Avatar({
|
|
46
|
+
image,
|
|
47
|
+
name,
|
|
48
|
+
fallback,
|
|
49
|
+
size = 19,
|
|
50
|
+
overflow = 0,
|
|
51
|
+
}: {
|
|
52
|
+
image?: string;
|
|
53
|
+
name?: string;
|
|
54
|
+
fallback?: string;
|
|
55
|
+
size?: number;
|
|
56
|
+
overflow?: number;
|
|
57
|
+
}) {
|
|
58
|
+
return (
|
|
59
|
+
<AvatarRoot size={size} overflow={overflow}>
|
|
60
|
+
<AvatarImage src={image} alt={name} />
|
|
61
|
+
<AvatarFallback size={size}>{fallback}</AvatarFallback>
|
|
62
|
+
</AvatarRoot>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
CSSProperties,
|
|
3
|
+
ForwardedRef,
|
|
4
|
+
forwardRef,
|
|
5
|
+
ReactNode,
|
|
6
|
+
useCallback,
|
|
7
|
+
} from 'react';
|
|
8
|
+
import styled from 'styled-components';
|
|
9
|
+
import { Tooltip } from './Tooltip';
|
|
10
|
+
|
|
11
|
+
type ButtonVariant =
|
|
12
|
+
| 'normal'
|
|
13
|
+
| 'primary'
|
|
14
|
+
| 'secondary'
|
|
15
|
+
| 'secondaryBright'
|
|
16
|
+
| 'white'
|
|
17
|
+
| 'thin'
|
|
18
|
+
| 'floating'
|
|
19
|
+
| 'none';
|
|
20
|
+
|
|
21
|
+
type ButtonSize = 'normal' | 'large';
|
|
22
|
+
|
|
23
|
+
/* ----------------------------------------------------------------------------
|
|
24
|
+
* Element
|
|
25
|
+
* ------------------------------------------------------------------------- */
|
|
26
|
+
|
|
27
|
+
export const ButtonElement = styled.button<{
|
|
28
|
+
active: boolean;
|
|
29
|
+
variant: ButtonVariant;
|
|
30
|
+
size: ButtonSize;
|
|
31
|
+
flex?: CSSProperties['flex'];
|
|
32
|
+
}>(({ theme, active, disabled, variant, size, flex }) => ({
|
|
33
|
+
...(size === 'large' ? theme.textStyles.heading4 : theme.textStyles.small),
|
|
34
|
+
textDecoration: 'none',
|
|
35
|
+
lineHeight: '1',
|
|
36
|
+
flex: flex ?? '0 0 auto',
|
|
37
|
+
position: 'relative',
|
|
38
|
+
border: '0',
|
|
39
|
+
outline: 'none',
|
|
40
|
+
WebkitAppRegion: 'no-drag',
|
|
41
|
+
userSelect: 'none',
|
|
42
|
+
minWidth: variant === 'normal' ? '31px' : undefined,
|
|
43
|
+
textAlign: 'left',
|
|
44
|
+
borderRadius: '4px',
|
|
45
|
+
paddingTop: variant === 'none' ? '0px' : '4px',
|
|
46
|
+
paddingRight: variant === 'none' ? '0px' : variant === 'thin' ? '1px' : '6px',
|
|
47
|
+
paddingBottom: variant === 'none' ? '0px' : '4px',
|
|
48
|
+
paddingLeft: variant === 'none' ? '0px' : variant === 'thin' ? '1px' : '6px',
|
|
49
|
+
...(size === 'large' && {
|
|
50
|
+
paddingTop: '12px',
|
|
51
|
+
paddingRight: '16px',
|
|
52
|
+
paddingBottom: '12px',
|
|
53
|
+
paddingLeft: '16px',
|
|
54
|
+
}),
|
|
55
|
+
background: active
|
|
56
|
+
? theme.colors.primary
|
|
57
|
+
: variant === 'none' || variant === 'thin'
|
|
58
|
+
? 'transparent'
|
|
59
|
+
: theme.colors.inputBackground,
|
|
60
|
+
color: active ? 'white' : theme.colors.text,
|
|
61
|
+
opacity: disabled ? 0.25 : 1,
|
|
62
|
+
'&:focus': {
|
|
63
|
+
boxShadow: `0 0 0 1px ${theme.colors.sidebar.background}, 0 0 0 3px ${theme.colors.primary}`,
|
|
64
|
+
},
|
|
65
|
+
'&:hover': {
|
|
66
|
+
background: theme.colors.inputBackgroundLight,
|
|
67
|
+
},
|
|
68
|
+
'&:active': {
|
|
69
|
+
background: theme.colors.activeBackground,
|
|
70
|
+
},
|
|
71
|
+
...(variant === 'primary' && {
|
|
72
|
+
background: theme.colors.primary,
|
|
73
|
+
color: 'white',
|
|
74
|
+
'&:hover': {
|
|
75
|
+
background: theme.colors.primaryLight,
|
|
76
|
+
},
|
|
77
|
+
'&:active': {
|
|
78
|
+
background: theme.colors.primary,
|
|
79
|
+
},
|
|
80
|
+
}),
|
|
81
|
+
...(variant === 'secondary' && {
|
|
82
|
+
background: theme.colors.secondary,
|
|
83
|
+
color: 'white',
|
|
84
|
+
'&:hover': {
|
|
85
|
+
background: theme.colors.secondaryLight,
|
|
86
|
+
},
|
|
87
|
+
'&:active': {
|
|
88
|
+
background: theme.colors.secondary,
|
|
89
|
+
},
|
|
90
|
+
}),
|
|
91
|
+
...(variant === 'secondaryBright' && {
|
|
92
|
+
background: '#0ab557',
|
|
93
|
+
color: 'white',
|
|
94
|
+
'&:hover': {
|
|
95
|
+
opacity: 0.8,
|
|
96
|
+
},
|
|
97
|
+
'&:active': {
|
|
98
|
+
opacity: 0.9,
|
|
99
|
+
},
|
|
100
|
+
}),
|
|
101
|
+
...(variant === 'white' && {
|
|
102
|
+
background: 'white',
|
|
103
|
+
'&:hover': {
|
|
104
|
+
opacity: 0.8,
|
|
105
|
+
},
|
|
106
|
+
'&:active': {
|
|
107
|
+
opacity: 0.9,
|
|
108
|
+
},
|
|
109
|
+
}),
|
|
110
|
+
...(variant === 'floating' && {
|
|
111
|
+
background: 'white',
|
|
112
|
+
color: theme.colors.text,
|
|
113
|
+
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.1)',
|
|
114
|
+
fontSize: '12px',
|
|
115
|
+
padding: '0px 4px',
|
|
116
|
+
}),
|
|
117
|
+
display: 'flex',
|
|
118
|
+
alignItems: 'center',
|
|
119
|
+
justifyContent: 'center',
|
|
120
|
+
|
|
121
|
+
'& *': {
|
|
122
|
+
pointerEvents: 'none',
|
|
123
|
+
},
|
|
124
|
+
}));
|
|
125
|
+
|
|
126
|
+
/* ----------------------------------------------------------------------------
|
|
127
|
+
* Content
|
|
128
|
+
* ------------------------------------------------------------------------- */
|
|
129
|
+
|
|
130
|
+
const ButtonContent = styled.span(({ theme }) => ({
|
|
131
|
+
// Line height of small text - maybe figure out better way to ensure
|
|
132
|
+
// icons don't have a smaller height
|
|
133
|
+
minHeight: '19px',
|
|
134
|
+
display: 'flex',
|
|
135
|
+
alignItems: 'center',
|
|
136
|
+
flex: '1',
|
|
137
|
+
justifyContent: 'center',
|
|
138
|
+
|
|
139
|
+
// Helps with alignment with the default small text
|
|
140
|
+
lineHeight: '15px',
|
|
141
|
+
}));
|
|
142
|
+
|
|
143
|
+
/* ----------------------------------------------------------------------------
|
|
144
|
+
* Root
|
|
145
|
+
* ------------------------------------------------------------------------- */
|
|
146
|
+
|
|
147
|
+
export interface ButtonRootProps {
|
|
148
|
+
id?: string;
|
|
149
|
+
flex?: CSSProperties['flex'];
|
|
150
|
+
tabIndex?: number;
|
|
151
|
+
children: ReactNode;
|
|
152
|
+
active?: boolean;
|
|
153
|
+
disabled?: boolean;
|
|
154
|
+
variant?: ButtonVariant;
|
|
155
|
+
size?: ButtonSize;
|
|
156
|
+
tooltip?: ReactNode;
|
|
157
|
+
onClick?: (event: React.MouseEvent) => void;
|
|
158
|
+
onPointerDown?: (event: React.PointerEvent) => void;
|
|
159
|
+
contentStyle?: CSSProperties;
|
|
160
|
+
as?: React.ElementType;
|
|
161
|
+
href?: string;
|
|
162
|
+
target?: string;
|
|
163
|
+
rel?: string;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export const Button = forwardRef(function Button(
|
|
167
|
+
{
|
|
168
|
+
id,
|
|
169
|
+
flex,
|
|
170
|
+
tabIndex,
|
|
171
|
+
tooltip,
|
|
172
|
+
active = false,
|
|
173
|
+
disabled = false,
|
|
174
|
+
variant = 'normal',
|
|
175
|
+
size = 'normal',
|
|
176
|
+
contentStyle,
|
|
177
|
+
onClick,
|
|
178
|
+
children,
|
|
179
|
+
...rest // Propagate any other props so this component works as a Slot
|
|
180
|
+
}: ButtonRootProps,
|
|
181
|
+
forwardedRef: ForwardedRef<HTMLButtonElement>,
|
|
182
|
+
) {
|
|
183
|
+
const buttonElement = (
|
|
184
|
+
<ButtonElement
|
|
185
|
+
ref={forwardedRef}
|
|
186
|
+
id={id}
|
|
187
|
+
flex={flex}
|
|
188
|
+
tabIndex={tabIndex}
|
|
189
|
+
active={active}
|
|
190
|
+
disabled={disabled}
|
|
191
|
+
variant={variant}
|
|
192
|
+
size={size}
|
|
193
|
+
onClick={onClick}
|
|
194
|
+
// Prevent double clicking a button from triggering any callbacks in ancestors
|
|
195
|
+
onDoubleClick={useCallback((event: React.MouseEvent) => {
|
|
196
|
+
event.stopPropagation();
|
|
197
|
+
}, [])}
|
|
198
|
+
{...rest}
|
|
199
|
+
>
|
|
200
|
+
<ButtonContent style={contentStyle}>{children}</ButtonContent>
|
|
201
|
+
</ButtonElement>
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
return tooltip ? (
|
|
205
|
+
<Tooltip content={tooltip}>{buttonElement}</Tooltip>
|
|
206
|
+
) : (
|
|
207
|
+
buttonElement
|
|
208
|
+
);
|
|
209
|
+
});
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { Cross1Icon, PlusIcon } from '@noya-app/noya-icons';
|
|
2
|
+
import React, { memo } from 'react';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
import { useDesignSystemTheme } from '../contexts/DesignSystemConfiguration';
|
|
5
|
+
import { useHover } from '../hooks/useHover';
|
|
6
|
+
|
|
7
|
+
type ChipColorScheme = 'primary' | 'secondary' | 'error';
|
|
8
|
+
type ChipSize = 'small' | 'medium';
|
|
9
|
+
type ChipVariant = 'solid' | 'outlined' | 'ghost';
|
|
10
|
+
|
|
11
|
+
const ChipElement = styled.span<{
|
|
12
|
+
colorScheme?: ChipColorScheme;
|
|
13
|
+
size: ChipSize;
|
|
14
|
+
monospace: boolean;
|
|
15
|
+
variant: ChipVariant;
|
|
16
|
+
isInteractive: boolean;
|
|
17
|
+
}>(({ theme, colorScheme, size, variant, monospace, isInteractive }) => {
|
|
18
|
+
const color =
|
|
19
|
+
colorScheme === 'primary'
|
|
20
|
+
? theme.colors.primary
|
|
21
|
+
: colorScheme === 'secondary'
|
|
22
|
+
? theme.colors.secondary
|
|
23
|
+
: theme.colors.text;
|
|
24
|
+
|
|
25
|
+
const backgroundColor =
|
|
26
|
+
colorScheme === 'primary'
|
|
27
|
+
? 'rgb(238, 229, 255)'
|
|
28
|
+
: colorScheme === 'secondary'
|
|
29
|
+
? 'rgb(205, 238, 231)'
|
|
30
|
+
: colorScheme === 'error'
|
|
31
|
+
? 'rgb(255, 219, 219)'
|
|
32
|
+
: theme.colors.inputBackground;
|
|
33
|
+
|
|
34
|
+
const subtleColor =
|
|
35
|
+
colorScheme === 'primary'
|
|
36
|
+
? 'rgba(238, 229, 255, 0.2)'
|
|
37
|
+
: colorScheme === 'secondary'
|
|
38
|
+
? 'rgba(205, 238, 231, 0.2)'
|
|
39
|
+
: colorScheme === 'error'
|
|
40
|
+
? 'rgba(255, 219, 219, 0.2)'
|
|
41
|
+
: 'rgba(0, 0, 0, 0.1)';
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
textTransform: 'initial',
|
|
45
|
+
borderRadius: 4,
|
|
46
|
+
userSelect: 'none',
|
|
47
|
+
display: 'inline-flex',
|
|
48
|
+
alignItems: 'center',
|
|
49
|
+
lineHeight: '1.4',
|
|
50
|
+
...(size === 'medium'
|
|
51
|
+
? {
|
|
52
|
+
fontSize: '11px',
|
|
53
|
+
padding: '4px 8px',
|
|
54
|
+
}
|
|
55
|
+
: {
|
|
56
|
+
fontSize: '9px',
|
|
57
|
+
padding: '0px 4px',
|
|
58
|
+
}),
|
|
59
|
+
...(monospace && {
|
|
60
|
+
fontFamily: theme.fonts.monospace,
|
|
61
|
+
}),
|
|
62
|
+
...(variant === 'solid'
|
|
63
|
+
? {
|
|
64
|
+
color,
|
|
65
|
+
backgroundColor,
|
|
66
|
+
}
|
|
67
|
+
: variant === 'outlined'
|
|
68
|
+
? {
|
|
69
|
+
color,
|
|
70
|
+
backgroundColor: 'transparent',
|
|
71
|
+
boxShadow: `0 0 0 1px ${subtleColor} inset`,
|
|
72
|
+
}
|
|
73
|
+
: {}),
|
|
74
|
+
'&:hover': {
|
|
75
|
+
...(variant === 'outlined' && {
|
|
76
|
+
backgroundColor: subtleColor,
|
|
77
|
+
boxShadow: 'none',
|
|
78
|
+
}),
|
|
79
|
+
},
|
|
80
|
+
...(isInteractive && {
|
|
81
|
+
cursor: 'pointer',
|
|
82
|
+
}),
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const DeleteElement = styled(Cross1Icon)<{
|
|
87
|
+
size: ChipSize;
|
|
88
|
+
}>(({ size }) => ({
|
|
89
|
+
position: 'relative',
|
|
90
|
+
marginRight: '-2px',
|
|
91
|
+
cursor: 'pointer',
|
|
92
|
+
opacity: 0.5,
|
|
93
|
+
|
|
94
|
+
...(size === 'medium'
|
|
95
|
+
? {
|
|
96
|
+
marginLeft: '2px',
|
|
97
|
+
// top: '-1px',
|
|
98
|
+
transform: 'scale(0.75)',
|
|
99
|
+
}
|
|
100
|
+
: {
|
|
101
|
+
transform: 'scale(0.6)',
|
|
102
|
+
}),
|
|
103
|
+
|
|
104
|
+
'&:hover': {
|
|
105
|
+
opacity: 0.85,
|
|
106
|
+
},
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
const ignoredProps = new Set(['size', 'isOnlyChild']);
|
|
110
|
+
|
|
111
|
+
const AddElement = styled(PlusIcon).withConfig({
|
|
112
|
+
shouldForwardProp: (prop) => !ignoredProps.has(prop),
|
|
113
|
+
})<{
|
|
114
|
+
size: ChipSize;
|
|
115
|
+
isOnlyChild: boolean;
|
|
116
|
+
}>(({ size, isOnlyChild }) => ({
|
|
117
|
+
position: 'relative',
|
|
118
|
+
marginRight: '-2px',
|
|
119
|
+
cursor: 'pointer',
|
|
120
|
+
opacity: 0.5,
|
|
121
|
+
|
|
122
|
+
...(size === 'medium'
|
|
123
|
+
? {
|
|
124
|
+
marginLeft: '2px',
|
|
125
|
+
top: '-1px',
|
|
126
|
+
transform: 'scale(0.75)',
|
|
127
|
+
}
|
|
128
|
+
: {
|
|
129
|
+
transform: 'scale(0.6)',
|
|
130
|
+
...(isOnlyChild && { marginLeft: '-2px' }),
|
|
131
|
+
}),
|
|
132
|
+
|
|
133
|
+
'&:hover': {
|
|
134
|
+
opacity: 0.85,
|
|
135
|
+
},
|
|
136
|
+
}));
|
|
137
|
+
|
|
138
|
+
export interface ChipProps {
|
|
139
|
+
colorScheme?: ChipColorScheme;
|
|
140
|
+
variant?: ChipVariant;
|
|
141
|
+
size?: ChipSize;
|
|
142
|
+
children?: React.ReactNode;
|
|
143
|
+
deletable?: boolean;
|
|
144
|
+
addable?: boolean;
|
|
145
|
+
monospace?: boolean;
|
|
146
|
+
onDelete?: () => void;
|
|
147
|
+
onAdd?: () => void;
|
|
148
|
+
onClick?: () => void;
|
|
149
|
+
onHoverDeleteChange?: (hovering: boolean) => void;
|
|
150
|
+
style?: React.CSSProperties;
|
|
151
|
+
tabIndex?: number;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export const Chip = memo(function Chip({
|
|
155
|
+
colorScheme,
|
|
156
|
+
children,
|
|
157
|
+
deletable,
|
|
158
|
+
addable,
|
|
159
|
+
style,
|
|
160
|
+
size = 'medium',
|
|
161
|
+
variant = 'solid',
|
|
162
|
+
monospace = false,
|
|
163
|
+
tabIndex,
|
|
164
|
+
onDelete,
|
|
165
|
+
onClick,
|
|
166
|
+
onAdd,
|
|
167
|
+
onHoverDeleteChange,
|
|
168
|
+
}: ChipProps) {
|
|
169
|
+
const theme = useDesignSystemTheme();
|
|
170
|
+
|
|
171
|
+
const { hoverProps: hoverDeleteProps } = useHover({
|
|
172
|
+
onHoverChange: onHoverDeleteChange,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const handleClick = !children && !deletable && addable ? onAdd : onClick;
|
|
176
|
+
|
|
177
|
+
const color =
|
|
178
|
+
colorScheme === 'primary'
|
|
179
|
+
? theme.colors.primary
|
|
180
|
+
: colorScheme === 'secondary'
|
|
181
|
+
? theme.colors.secondary
|
|
182
|
+
: theme.colors.text;
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<ChipElement
|
|
186
|
+
variant={variant}
|
|
187
|
+
colorScheme={colorScheme}
|
|
188
|
+
style={style}
|
|
189
|
+
onClick={handleClick}
|
|
190
|
+
size={size}
|
|
191
|
+
monospace={monospace}
|
|
192
|
+
isInteractive={!!handleClick}
|
|
193
|
+
tabIndex={tabIndex}
|
|
194
|
+
>
|
|
195
|
+
{children}
|
|
196
|
+
{deletable && (
|
|
197
|
+
<DeleteElement
|
|
198
|
+
size={size}
|
|
199
|
+
{...(hoverDeleteProps as any)}
|
|
200
|
+
onClick={onDelete}
|
|
201
|
+
color={color}
|
|
202
|
+
/>
|
|
203
|
+
)}
|
|
204
|
+
{addable && (
|
|
205
|
+
<AddElement
|
|
206
|
+
size={size}
|
|
207
|
+
isOnlyChild={!children && !deletable}
|
|
208
|
+
onClick={onAdd}
|
|
209
|
+
color={color}
|
|
210
|
+
/>
|
|
211
|
+
)}
|
|
212
|
+
</ChipElement>
|
|
213
|
+
);
|
|
214
|
+
});
|