@noya-app/noya-designsystem 0.0.14 → 0.0.16

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/package.json CHANGED
@@ -1,22 +1,23 @@
1
1
  {
2
2
  "name": "@noya-app/noya-designsystem",
3
- "version": "0.0.14",
3
+ "version": "0.0.16",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",
7
7
  "scripts": {
8
8
  "build": "tsup src/index.tsx --format cjs,esm --dts --sourcemap",
9
- "dev": "npm run build -- --watch"
9
+ "dev": "npm run build -- --watch",
10
+ "lint": "eslint . --max-warnings 0"
10
11
  },
11
12
  "dependencies": {
12
13
  "@dnd-kit/core": "3.1.1",
13
14
  "@dnd-kit/modifiers": "3.0.0",
14
15
  "@dnd-kit/sortable": "4.0.0",
15
- "@noya-app/noya-colorpicker": "0.0.8",
16
- "@noya-app/noya-utils": "0.0.11",
17
- "@noya-app/noya-geometry": "0.0.12",
18
- "@noya-app/noya-icons": "0.0.5",
19
- "@noya-app/noya-keymap": "0.0.7",
16
+ "@noya-app/noya-colorpicker": "0.0.10",
17
+ "@noya-app/noya-utils": "0.0.13",
18
+ "@noya-app/noya-geometry": "0.0.14",
19
+ "@noya-app/noya-icons": "0.0.7",
20
+ "@noya-app/noya-keymap": "0.0.9",
20
21
  "@radix-ui/primitive": "^1.0.0",
21
22
  "@radix-ui/react-avatar": "^1.0.1",
22
23
  "@radix-ui/react-compose-refs": "^1.0.0",
@@ -1,12 +1,12 @@
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';
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
6
 
7
- type ChipColorScheme = 'primary' | 'secondary' | 'error';
8
- type ChipSize = 'small' | 'medium';
9
- type ChipVariant = 'solid' | 'outlined' | 'ghost';
7
+ type ChipColorScheme = "primary" | "secondary" | "error";
8
+ type ChipSize = "small" | "medium";
9
+ type ChipVariant = "solid" | "outlined" | "ghost";
10
10
 
11
11
  const ChipElement = styled.span<{
12
12
  colorScheme?: ChipColorScheme;
@@ -16,69 +16,74 @@ const ChipElement = styled.span<{
16
16
  isInteractive: boolean;
17
17
  }>(({ theme, colorScheme, size, variant, monospace, isInteractive }) => {
18
18
  const color =
19
- colorScheme === 'primary'
19
+ colorScheme === "primary"
20
20
  ? theme.colors.primary
21
- : colorScheme === 'secondary'
22
- ? theme.colors.secondary
23
- : theme.colors.text;
21
+ : colorScheme === "secondary"
22
+ ? theme.colors.secondary
23
+ : theme.colors.text;
24
24
 
25
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;
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
33
 
34
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)';
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
42
 
43
43
  return {
44
- textTransform: 'initial',
44
+ textTransform: "initial",
45
45
  borderRadius: 4,
46
- userSelect: 'none',
47
- display: 'inline-flex',
48
- alignItems: 'center',
49
- lineHeight: '1.4',
50
- ...(size === 'medium'
46
+ userSelect: "none",
47
+ display: "inline-flex",
48
+ alignItems: "center",
49
+ lineHeight: "1.4",
50
+ whiteSpace: "pre",
51
+ ...(size === "medium"
51
52
  ? {
52
- fontSize: '11px',
53
- padding: '4px 8px',
53
+ fontSize: "11px",
54
+ padding: "4px 8px",
54
55
  }
55
56
  : {
56
- fontSize: '9px',
57
- padding: '0px 4px',
57
+ fontSize: "9px",
58
+ padding: "0px 4px",
59
+ lineHeight: "15px",
58
60
  }),
59
61
  ...(monospace && {
60
62
  fontFamily: theme.fonts.monospace,
61
63
  }),
62
- ...(variant === 'solid'
64
+ ...(variant === "solid"
63
65
  ? {
64
66
  color,
65
67
  backgroundColor,
66
68
  }
67
- : variant === 'outlined'
68
- ? {
69
- color,
70
- backgroundColor: 'transparent',
71
- boxShadow: `0 0 0 1px ${subtleColor} inset`,
72
- }
73
- : {}),
74
- '&:hover': {
75
- ...(variant === 'outlined' && {
69
+ : variant === "outlined"
70
+ ? {
71
+ color,
72
+ backgroundColor: "transparent",
73
+ boxShadow: `0 0 0 1px ${subtleColor} inset`,
74
+ }
75
+ : {}),
76
+ "&:hover": {
77
+ ...(variant === "outlined" && {
76
78
  backgroundColor: subtleColor,
77
- boxShadow: 'none',
79
+ boxShadow: "none",
78
80
  }),
79
81
  },
80
82
  ...(isInteractive && {
81
- cursor: 'pointer',
83
+ cursor: "pointer",
84
+ "&:hover": {
85
+ opacity: 0.85,
86
+ },
82
87
  }),
83
88
  };
84
89
  });
@@ -86,27 +91,27 @@ const ChipElement = styled.span<{
86
91
  const DeleteElement = styled(Cross1Icon)<{
87
92
  size: ChipSize;
88
93
  }>(({ size }) => ({
89
- position: 'relative',
90
- marginRight: '-2px',
91
- cursor: 'pointer',
94
+ position: "relative",
95
+ marginRight: "-2px",
96
+ cursor: "pointer",
92
97
  opacity: 0.5,
93
98
 
94
- ...(size === 'medium'
99
+ ...(size === "medium"
95
100
  ? {
96
- marginLeft: '2px',
101
+ marginLeft: "2px",
97
102
  // top: '-1px',
98
- transform: 'scale(0.75)',
103
+ transform: "scale(0.75)",
99
104
  }
100
105
  : {
101
- transform: 'scale(0.6)',
106
+ transform: "scale(0.6)",
102
107
  }),
103
108
 
104
- '&:hover': {
109
+ "&:hover": {
105
110
  opacity: 0.85,
106
111
  },
107
112
  }));
108
113
 
109
- const ignoredProps = new Set(['size', 'isOnlyChild']);
114
+ const ignoredProps = new Set(["size", "isOnlyChild"]);
110
115
 
111
116
  const AddElement = styled(PlusIcon).withConfig({
112
117
  shouldForwardProp: (prop) => !ignoredProps.has(prop),
@@ -114,23 +119,23 @@ const AddElement = styled(PlusIcon).withConfig({
114
119
  size: ChipSize;
115
120
  isOnlyChild: boolean;
116
121
  }>(({ size, isOnlyChild }) => ({
117
- position: 'relative',
118
- marginRight: '-2px',
119
- cursor: 'pointer',
122
+ position: "relative",
123
+ marginLeft: "-2px",
124
+ cursor: "pointer",
120
125
  opacity: 0.5,
121
126
 
122
- ...(size === 'medium'
127
+ ...(size === "medium"
123
128
  ? {
124
- marginLeft: '2px',
125
- top: '-1px',
126
- transform: 'scale(0.75)',
129
+ marginRight: "2px",
130
+ top: "-1px",
131
+ transform: "scale(0.75)",
127
132
  }
128
133
  : {
129
- transform: 'scale(0.6)',
130
- ...(isOnlyChild && { marginLeft: '-2px' }),
134
+ marginRight: "-2px",
135
+ transform: "scale(0.6)",
131
136
  }),
132
137
 
133
- '&:hover': {
138
+ "&:hover": {
134
139
  opacity: 0.85,
135
140
  },
136
141
  }));
@@ -157,8 +162,8 @@ export const Chip = memo(function Chip({
157
162
  deletable,
158
163
  addable,
159
164
  style,
160
- size = 'medium',
161
- variant = 'solid',
165
+ size = "medium",
166
+ variant = "solid",
162
167
  monospace = false,
163
168
  tabIndex,
164
169
  onDelete,
@@ -175,11 +180,11 @@ export const Chip = memo(function Chip({
175
180
  const handleClick = !children && !deletable && addable ? onAdd : onClick;
176
181
 
177
182
  const color =
178
- colorScheme === 'primary'
183
+ colorScheme === "primary"
179
184
  ? theme.colors.primary
180
- : colorScheme === 'secondary'
181
- ? theme.colors.secondary
182
- : theme.colors.text;
185
+ : colorScheme === "secondary"
186
+ ? theme.colors.secondary
187
+ : theme.colors.text;
183
188
 
184
189
  return (
185
190
  <ChipElement
@@ -192,20 +197,34 @@ export const Chip = memo(function Chip({
192
197
  isInteractive={!!handleClick}
193
198
  tabIndex={tabIndex}
194
199
  >
195
- {children}
196
- {deletable && (
197
- <DeleteElement
200
+ {addable && (
201
+ <AddElement
198
202
  size={size}
199
- {...(hoverDeleteProps as any)}
200
- onClick={onDelete}
203
+ isOnlyChild={!children && !deletable}
204
+ onClick={
205
+ onAdd
206
+ ? (event) => {
207
+ event.stopPropagation();
208
+ onAdd?.();
209
+ }
210
+ : undefined
211
+ }
201
212
  color={color}
202
213
  />
203
214
  )}
204
- {addable && (
205
- <AddElement
215
+ {children}
216
+ {deletable && (
217
+ <DeleteElement
206
218
  size={size}
207
- isOnlyChild={!children && !deletable}
208
- onClick={onAdd}
219
+ {...(hoverDeleteProps as any)}
220
+ onClick={
221
+ onDelete
222
+ ? (event) => {
223
+ event.stopPropagation();
224
+ onDelete?.();
225
+ }
226
+ : undefined
227
+ }
209
228
  color={color}
210
229
  />
211
230
  )}
@@ -1,4 +1,4 @@
1
- import * as DialogPrimitive from '@radix-ui/react-dialog';
1
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
2
2
  import React, {
3
3
  ComponentProps,
4
4
  ForwardedRef,
@@ -6,35 +6,35 @@ import React, {
6
6
  ReactNode,
7
7
  useImperativeHandle,
8
8
  useRef,
9
- } from 'react';
10
- import styled from 'styled-components';
11
- import { IconButton } from './IconButton';
12
- import { Spacer } from './Spacer';
9
+ } from "react";
10
+ import styled from "styled-components";
11
+ import { IconButton } from "./IconButton";
12
+ import { Spacer } from "./Spacer";
13
13
 
14
14
  const StyledOverlay = styled(DialogPrimitive.Overlay)({
15
- backgroundColor: 'rgba(0,0,0,0.5)',
16
- position: 'fixed',
15
+ backgroundColor: "rgba(0,0,0,0.5)",
16
+ position: "fixed",
17
17
  inset: 0,
18
18
  });
19
19
 
20
20
  const StyledContent = styled(DialogPrimitive.Content)(({ theme }) => ({
21
21
  boxShadow:
22
- 'hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px',
23
- position: 'fixed',
24
- top: '50%',
25
- left: '50%',
26
- transform: 'translate(-50%, -50%)',
27
- width: '90vw',
28
- maxWidth: '450px',
29
- maxHeight: '85vh',
22
+ "hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px",
23
+ position: "fixed",
24
+ top: "50%",
25
+ left: "50%",
26
+ transform: "translate(-50%, -50%)",
27
+ width: "90vw",
28
+ maxWidth: "450px",
29
+ maxHeight: "85vh",
30
30
  padding: theme.sizes.dialog.padding,
31
31
  borderRadius: 2,
32
32
  ...theme.textStyles.small,
33
33
  backgroundColor: theme.colors.popover.background,
34
- overflowY: 'auto',
34
+ overflowY: "auto",
35
35
  color: theme.colors.textMuted,
36
- '&:focus': { outline: 'none' },
37
- pointerEvents: 'all',
36
+ "&:focus": { outline: "none" },
37
+ pointerEvents: "all",
38
38
  zIndex: 1000,
39
39
  }));
40
40
 
@@ -51,13 +51,13 @@ const StyledDescription = styled(DialogPrimitive.Description)(({ theme }) => ({
51
51
  }));
52
52
 
53
53
  export const CloseButtonContainer = styled.div(({ theme }) => ({
54
- position: 'absolute',
54
+ position: "absolute",
55
55
  top: theme.sizes.dialog.padding,
56
56
  right: theme.sizes.dialog.padding,
57
57
  zIndex: 1,
58
58
  backgroundColor: theme.colors.popover.background,
59
- padding: '4px 6px',
60
- borderRadius: '2px',
59
+ padding: "4px 6px",
60
+ borderRadius: "2px",
61
61
  // boxShadow: `0 0 2px rgba(0, 0, 0, 0.2)`,
62
62
  border: `1px solid rgba(128,128,128,0.2)`,
63
63
  }));
@@ -70,12 +70,12 @@ interface Props {
70
70
  title?: ReactNode;
71
71
  description?: ReactNode;
72
72
  children?: ReactNode;
73
- style?: ComponentProps<typeof StyledContent>['style'];
74
- open: ComponentProps<typeof DialogPrimitive.Root>['open'];
75
- onOpenChange?: ComponentProps<typeof DialogPrimitive.Root>['onOpenChange'];
73
+ style?: ComponentProps<typeof StyledContent>["style"];
74
+ open: ComponentProps<typeof DialogPrimitive.Root>["open"];
75
+ onOpenChange?: ComponentProps<typeof DialogPrimitive.Root>["onOpenChange"];
76
76
  onOpenAutoFocus?: ComponentProps<
77
77
  typeof DialogPrimitive.Content
78
- >['onOpenAutoFocus'];
78
+ >["onOpenAutoFocus"];
79
79
  closeOnInteractOutside?: boolean;
80
80
  }
81
81
 
@@ -90,7 +90,7 @@ export const Dialog = forwardRef(function Dialog(
90
90
  onOpenAutoFocus,
91
91
  closeOnInteractOutside = true,
92
92
  }: Props,
93
- forwardedRef: ForwardedRef<IDialog>,
93
+ forwardedRef: ForwardedRef<IDialog>
94
94
  ) {
95
95
  const contentRef = useRef<HTMLDivElement>(null);
96
96
 
@@ -141,3 +141,28 @@ export const Dialog = forwardRef(function Dialog(
141
141
  </DialogPrimitive.Root>
142
142
  );
143
143
  });
144
+
145
+ export const FullscreenDialog = forwardRef(function FullscreenDialog(
146
+ { style, ...rest }: Props,
147
+ forwardedRef: ForwardedRef<IDialog>
148
+ ) {
149
+ return (
150
+ <Dialog
151
+ ref={forwardedRef}
152
+ style={{
153
+ width: "100%",
154
+ height: "100%",
155
+ maxWidth: "100%",
156
+ maxHeight: "100%",
157
+ top: "0",
158
+ left: "0",
159
+ transform: "none",
160
+ display: "flex",
161
+ flexDirection: "column",
162
+ padding: 0,
163
+ ...style,
164
+ }}
165
+ {...rest}
166
+ />
167
+ );
168
+ });
@@ -0,0 +1,116 @@
1
+ import { DragHandleDots2Icon } from "@noya-app/noya-icons";
2
+ import React, { memo, useCallback, useState } from "react";
3
+ import styled from "styled-components";
4
+ import { useDesignSystemTheme } from "../contexts/DesignSystemConfiguration";
5
+ import { DropdownMenu } from "./DropdownMenu";
6
+ import { MenuItem } from "./internal/Menu";
7
+
8
+ const DotButtonElement = styled.div(({ theme }) => ({
9
+ cursor: "pointer",
10
+ borderRadius: "4px",
11
+ display: "flex",
12
+ alignItems: "center",
13
+ justifyContent: "center",
14
+ padding: "2px 0",
15
+ "&:hover": {
16
+ background: theme.colors.inputBackgroundLight,
17
+ },
18
+ "&:active": {
19
+ background: theme.colors.activeBackground,
20
+ },
21
+ }));
22
+
23
+ const TriggerElement = styled.div({
24
+ pointerEvents: "none",
25
+ height: "15px",
26
+ });
27
+
28
+ /**
29
+ * A button that opens a menu when clicked, but also allows dragging the
30
+ */
31
+ export const DraggableMenuButton = memo(function DraggableMenuButton<
32
+ T extends string,
33
+ >({
34
+ items,
35
+ onSelect,
36
+ isVisible = true,
37
+ }: {
38
+ items: MenuItem<T>[];
39
+ onSelect: (value: T) => void;
40
+ isVisible?: boolean;
41
+ }) {
42
+ const color = useDesignSystemTheme().colors.icon;
43
+ const [open, setOpen] = useState(false);
44
+ const [downPosition, setDownPosition] = React.useState<{
45
+ x: number;
46
+ y: number;
47
+ } | null>(null);
48
+
49
+ const handlePointerDownCapture = useCallback(
50
+ (event: React.PointerEvent) => {
51
+ if (open) {
52
+ setDownPosition(null);
53
+ return;
54
+ }
55
+
56
+ setDownPosition({
57
+ x: event.clientX,
58
+ y: event.clientY,
59
+ });
60
+ },
61
+ [open]
62
+ );
63
+
64
+ const handlePointerUp = useCallback(
65
+ (event: React.PointerEvent) => {
66
+ if (open || !downPosition) {
67
+ setDownPosition(null);
68
+ return;
69
+ }
70
+
71
+ const dx = event.clientX - downPosition.x;
72
+ const dy = event.clientY - downPosition.y;
73
+
74
+ const distance = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
75
+
76
+ if (distance < 5) {
77
+ setOpen(true);
78
+ }
79
+
80
+ setDownPosition(null);
81
+ },
82
+ [downPosition, open]
83
+ );
84
+
85
+ const handleOpenChange = useCallback(
86
+ (isOpen: boolean) => {
87
+ if (!isOpen) setOpen(false);
88
+ },
89
+ [setOpen]
90
+ );
91
+
92
+ return (
93
+ <DotButtonElement
94
+ onPointerDownCapture={handlePointerDownCapture}
95
+ onPointerUp={handlePointerUp}
96
+ onClick={(event) => {
97
+ event.stopPropagation();
98
+ event.preventDefault();
99
+ }}
100
+ >
101
+ <DropdownMenu
102
+ open={open}
103
+ onOpenChange={handleOpenChange}
104
+ items={items}
105
+ onSelect={onSelect}
106
+ shouldBindKeyboardShortcuts={false}
107
+ >
108
+ <TriggerElement>
109
+ <DragHandleDots2Icon
110
+ color={isVisible || open ? color : "transparent"}
111
+ />
112
+ </TriggerElement>
113
+ </DropdownMenu>
114
+ </DotButtonElement>
115
+ );
116
+ });