@noya-app/noya-designsystem 0.1.41 → 0.1.42

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.
Files changed (52) hide show
  1. package/.turbo/turbo-build.log +10 -10
  2. package/CHANGELOG.md +10 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.mts +217 -121
  5. package/dist/index.d.ts +217 -121
  6. package/dist/index.js +7435 -6796
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +5257 -4660
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +4 -5
  11. package/src/__tests__/combobox.test.ts +578 -0
  12. package/src/components/ActivityIndicator.tsx +4 -4
  13. package/src/components/AnimatePresence.tsx +5 -5
  14. package/src/components/Avatar.tsx +5 -2
  15. package/src/components/BaseToolbar.tsx +61 -0
  16. package/src/components/Button.tsx +1 -1
  17. package/src/components/Checkbox.tsx +6 -5
  18. package/src/components/Combobox.tsx +220 -324
  19. package/src/components/ComboboxMenu.tsx +88 -0
  20. package/src/components/Dialog.tsx +10 -6
  21. package/src/components/DimensionInput.tsx +4 -4
  22. package/src/components/FillPreviewBackground.tsx +51 -44
  23. package/src/components/FloatingWindow.tsx +5 -2
  24. package/src/components/GradientPicker.tsx +14 -14
  25. package/src/components/IconButton.tsx +6 -12
  26. package/src/components/Icons.tsx +3 -3
  27. package/src/components/InputField.tsx +145 -160
  28. package/src/components/Label.tsx +4 -4
  29. package/src/components/LabeledElementView.tsx +35 -41
  30. package/src/components/ListView.tsx +49 -39
  31. package/src/components/Message.tsx +75 -0
  32. package/src/components/Progress.tsx +2 -2
  33. package/src/components/ScrollArea.tsx +7 -5
  34. package/src/components/SegmentedControl.tsx +171 -0
  35. package/src/components/SelectMenu.tsx +61 -10
  36. package/src/components/Slider.tsx +5 -2
  37. package/src/components/Sortable.tsx +17 -27
  38. package/src/components/Spacer.tsx +28 -9
  39. package/src/components/Switch.tsx +8 -8
  40. package/src/components/TextArea.tsx +23 -13
  41. package/src/components/Toolbar.tsx +134 -0
  42. package/src/components/Tooltip.tsx +10 -7
  43. package/src/components/internal/Menu.tsx +5 -4
  44. package/src/contexts/DesignSystemConfiguration.tsx +15 -24
  45. package/src/contexts/DialogContext.tsx +137 -49
  46. package/src/contexts/ImageDataContext.tsx +6 -12
  47. package/src/index.css +1 -3
  48. package/src/index.tsx +12 -3
  49. package/src/utils/combobox.ts +369 -0
  50. package/tailwind.config.ts +1 -2
  51. package/src/components/RadioGroup.tsx +0 -100
  52. package/src/utils/completions.ts +0 -21
@@ -0,0 +1,88 @@
1
+ import { Size } from "@noya-app/noya-geometry";
2
+ import React, { ForwardedRef, forwardRef, memo } from "react";
3
+ import { cx } from "../utils/classNames";
4
+ import { InternalComboboxItem } from "../utils/combobox";
5
+ import { fuzzyTokenize } from "../utils/fuzzyScorer";
6
+ import { IVirtualizedList, ListView } from "./ListView";
7
+ import { Spacer } from "./Spacer";
8
+
9
+ interface ComboboxMenuProps {
10
+ items: InternalComboboxItem[];
11
+ selectedIndex: number;
12
+ onSelectItem: (item: InternalComboboxItem) => void;
13
+ onHoverIndex: (index: number) => void;
14
+ listSize: Size;
15
+ style?: React.CSSProperties;
16
+ }
17
+
18
+ export const ComboboxMenu = memo(
19
+ forwardRef(function ComboboxMenu(
20
+ {
21
+ items,
22
+ selectedIndex,
23
+ onSelectItem,
24
+ onHoverIndex,
25
+ listSize,
26
+ style,
27
+ }: ComboboxMenuProps,
28
+ forwardedRef: ForwardedRef<IVirtualizedList>
29
+ ) {
30
+ return (
31
+ <ListView.Root
32
+ ref={forwardedRef}
33
+ scrollable
34
+ keyExtractor={(item) => item.id}
35
+ data={items}
36
+ virtualized={listSize}
37
+ pressEventName="onPointerDown"
38
+ sectionHeaderVariant="label"
39
+ style={style}
40
+ renderItem={(item, i) => {
41
+ if (item.type === "sectionHeader") {
42
+ return (
43
+ <ListView.Row key={item.id} isSectionHeader>
44
+ {item.name}
45
+ </ListView.Row>
46
+ );
47
+ }
48
+
49
+ const tokens = fuzzyTokenize({
50
+ item: item.name,
51
+ itemScore: item,
52
+ });
53
+
54
+ return (
55
+ <ListView.Row
56
+ key={item.id}
57
+ selected={i === selectedIndex}
58
+ onPress={() => onSelectItem(item)}
59
+ onHoverChange={(hovered) => {
60
+ if (hovered) {
61
+ onHoverIndex(i);
62
+ }
63
+ }}
64
+ >
65
+ {tokens.map((token, j) => (
66
+ <span
67
+ key={`${item.id}-token-${j}`}
68
+ className={cx(
69
+ token.type === "match" ? "font-bold" : "font-normal",
70
+ "whitespace-pre"
71
+ )}
72
+ >
73
+ {token.text}
74
+ </span>
75
+ ))}
76
+ {item.icon && (
77
+ <>
78
+ <Spacer.Horizontal />
79
+ {item.icon}
80
+ </>
81
+ )}
82
+ </ListView.Row>
83
+ );
84
+ }}
85
+ />
86
+ );
87
+ })
88
+ );
@@ -7,9 +7,9 @@ import React, {
7
7
  useImperativeHandle,
8
8
  useRef,
9
9
  } from "react";
10
+ import { cx } from "../utils/classNames";
10
11
  import { IconButton } from "./IconButton";
11
12
  import { Spacer } from "./Spacer";
12
- import { cx } from "../utils/classNames";
13
13
 
14
14
  const StyledOverlay = forwardRef<
15
15
  React.ElementRef<typeof DialogPrimitive.Overlay>,
@@ -99,6 +99,7 @@ interface Props {
99
99
  typeof DialogPrimitive.Content
100
100
  >["onOpenAutoFocus"];
101
101
  closeOnInteractOutside?: boolean;
102
+ showCloseButton?: boolean;
102
103
  className?: string;
103
104
  }
104
105
 
@@ -113,6 +114,7 @@ export const Dialog = forwardRef(function Dialog(
113
114
  onOpenAutoFocus,
114
115
  closeOnInteractOutside = true,
115
116
  className,
117
+ showCloseButton = true,
116
118
  }: Props,
117
119
  forwardedRef: ForwardedRef<IDialog>
118
120
  ) {
@@ -138,11 +140,13 @@ export const Dialog = forwardRef(function Dialog(
138
140
  onInteractOutside: (event) => event.preventDefault(),
139
141
  })}
140
142
  >
141
- <CloseButtonContainer>
142
- <DialogPrimitive.Close asChild>
143
- <IconButton iconName="Cross1Icon" />
144
- </DialogPrimitive.Close>
145
- </CloseButtonContainer>
143
+ {showCloseButton && (
144
+ <CloseButtonContainer>
145
+ <DialogPrimitive.Close asChild>
146
+ <IconButton iconName="Cross1Icon" />
147
+ </DialogPrimitive.Close>
148
+ </CloseButtonContainer>
149
+ )}
146
150
  {title && (
147
151
  <>
148
152
  <StyledTitle>{title}</StyledTitle>
@@ -1,5 +1,5 @@
1
1
  import { round } from "@noya-app/noya-utils";
2
- import React, { memo, useCallback } from "react";
2
+ import * as React from "react";
3
3
  import { InputField } from "./InputField";
4
4
 
5
5
  export type SetNumberMode = "replace" | "adjust";
@@ -29,7 +29,7 @@ interface Props {
29
29
  trigger?: "change" | "submit";
30
30
  }
31
31
 
32
- export const DimensionInput = memo(function DimensionInput({
32
+ export const DimensionInput = React.memo(function DimensionInput({
33
33
  id,
34
34
  value,
35
35
  onSetValue,
@@ -39,12 +39,12 @@ export const DimensionInput = memo(function DimensionInput({
39
39
  disabled,
40
40
  trigger = "submit",
41
41
  }: Props) {
42
- const handleNudgeValue = useCallback(
42
+ const handleNudgeValue = React.useCallback(
43
43
  (value: number) => onSetValue(value, "adjust"),
44
44
  [onSetValue]
45
45
  );
46
46
 
47
- const handleSetValue = useCallback(
47
+ const handleSetValue = React.useCallback(
48
48
  (value: number) => onSetValue(value, "replace"),
49
49
  [onSetValue]
50
50
  );
@@ -1,6 +1,6 @@
1
1
  import { Sketch } from "@noya-app/noya-file-format";
2
2
  // import { useSketchImage } from 'noya-renderer';
3
- import React, { memo, useMemo } from "react";
3
+ import * as React from "react";
4
4
  import { useImageData } from "../contexts/ImageDataContext";
5
5
  import { useObjectURL } from "../hooks/useObjectURL";
6
6
  import { cssVars } from "../theme";
@@ -14,7 +14,7 @@ const dotsHorizontalSvg = (fillColor: string) => `
14
14
  </svg>
15
15
  `;
16
16
 
17
- const Background = memo(function Background({
17
+ const Background = React.memo(function Background({
18
18
  background,
19
19
  }: {
20
20
  background?: string;
@@ -42,57 +42,64 @@ function getPatternSizeAndPosition(
42
42
  }
43
43
  }
44
44
 
45
- export const PatternPreviewBackground = memo(function PatternPreviewBackground({
46
- fillType,
47
- tileScale,
48
- imageRef,
49
- }: {
50
- fillType: Sketch.PatternFillType;
51
- tileScale: number;
52
- imageRef: string;
53
- }) {
54
- const image = useImageData(imageRef);
55
-
56
- const url = useObjectURL(image);
57
-
58
- const size = getPatternSizeAndPosition(fillType, tileScale);
59
-
60
- const background = useMemo(
61
- () =>
62
- [
63
- size,
64
- `url(${url})`,
65
- fillType === Sketch.PatternFillType.Tile ? "repeat" : "no-repeat",
66
- ].join(" "),
67
- [fillType, size, url]
68
- );
69
-
70
- return <Background background={background} />;
71
- });
45
+ export const PatternPreviewBackground = React.memo(
46
+ function PatternPreviewBackground({
47
+ fillType,
48
+ tileScale,
49
+ imageRef,
50
+ }: {
51
+ fillType: Sketch.PatternFillType;
52
+ tileScale: number;
53
+ imageRef: string;
54
+ }) {
55
+ const image = useImageData(imageRef);
56
+
57
+ const url = useObjectURL(image);
58
+
59
+ const size = getPatternSizeAndPosition(fillType, tileScale);
60
+
61
+ const background = React.useMemo(
62
+ () =>
63
+ [
64
+ size,
65
+ `url(${url})`,
66
+ fillType === Sketch.PatternFillType.Tile ? "repeat" : "no-repeat",
67
+ ].join(" "),
68
+ [fillType, size, url]
69
+ );
70
+
71
+ return <Background background={background} />;
72
+ }
73
+ );
72
74
 
73
- const ColorPreviewBackground = memo(function ColorPreviewBackground({
75
+ const ColorPreviewBackground = React.memo(function ColorPreviewBackground({
74
76
  color,
75
77
  }: {
76
78
  color: Sketch.Color;
77
79
  }) {
78
- const background = useMemo(() => sketchColorToRgbaString(color), [color]);
79
-
80
- return <Background background={background} />;
81
- });
82
-
83
- const GradientPreviewBackground = memo(function GradientPreviewBackground({
84
- gradient,
85
- }: {
86
- gradient: Sketch.Gradient;
87
- }) {
88
- const background = useMemo(
89
- () => getGradientBackground(gradient.stops, gradient.gradientType, 180),
90
- [gradient.gradientType, gradient.stops]
80
+ const background = React.useMemo(
81
+ () => sketchColorToRgbaString(color),
82
+ [color]
91
83
  );
92
84
 
93
85
  return <Background background={background} />;
94
86
  });
95
87
 
88
+ const GradientPreviewBackground = React.memo(
89
+ function GradientPreviewBackground({
90
+ gradient,
91
+ }: {
92
+ gradient: Sketch.Gradient;
93
+ }) {
94
+ const background = React.useMemo(
95
+ () => getGradientBackground(gradient.stops, gradient.gradientType, 180),
96
+ [gradient.gradientType, gradient.stops]
97
+ );
98
+
99
+ return <Background background={background} />;
100
+ }
101
+ );
102
+
96
103
  interface Props {
97
104
  value?: Sketch.Color | Sketch.Gradient | SketchPattern;
98
105
  }
@@ -101,7 +108,7 @@ const background = `center url("data:image/svg+xml;utf8,${dotsHorizontalSvg(
101
108
  cssVars.colors.placeholderDots
102
109
  )}") no-repeat`;
103
110
 
104
- export const FillPreviewBackground = memo(function FillPreviewBackground({
111
+ export const FillPreviewBackground = React.memo(function FillPreviewBackground({
105
112
  value,
106
113
  }: Props) {
107
114
  if (!value) return <Background background={background} />;
@@ -129,7 +129,9 @@ function defaultRenderToolbar({
129
129
  {toolbarContent}
130
130
  <IconButton
131
131
  iconName="Cross3Icon"
132
+ className="h-8 w-8 rounded-full"
132
133
  onClick={onClose}
134
+ size={20}
133
135
  style={{
134
136
  marginLeft: "6px",
135
137
  marginRight: "-6px",
@@ -250,7 +252,7 @@ export const FloatingWindow: React.FC<FloatingWindowProps> = ({
250
252
  ]
251
253
  );
252
254
 
253
- const handleMouseUp = useCallback((e: MouseEvent) => {
255
+ const handleMouseUp = useCallback((_e: MouseEvent) => {
254
256
  setIsDragging(false);
255
257
  setIsResizing(false);
256
258
  setResizeDirection(null);
@@ -285,7 +287,8 @@ export const FloatingWindow: React.FC<FloatingWindowProps> = ({
285
287
  width: size.width,
286
288
  height: size.height,
287
289
  outline: `1px solid ${cssVars.colors.divider}`,
288
- backgroundColor: cssVars.colors.canvasBackground,
290
+ backgroundColor: cssVars.colors.background,
291
+ zIndex: cssVars.zIndex.menu,
289
292
  }}
290
293
  >
291
294
  <div
@@ -9,11 +9,11 @@ import {
9
9
  equalColorObjects,
10
10
  hsvaToRgba,
11
11
  rgbaToHsva,
12
- } from '@noya-app/noya-colorpicker';
13
- import type { Sketch } from '@noya-app/noya-file-format';
14
- import React, { memo, useCallback, useMemo } from 'react';
15
- import { Spacer } from '../components/Spacer';
16
- import { rgbaToSketchColor, sketchColorToRgba } from '../utils/sketchColor';
12
+ } from "@noya-app/noya-colorpicker";
13
+ import type { Sketch } from "@noya-app/noya-file-format";
14
+ import * as React from "react";
15
+ import { Spacer } from "../components/Spacer";
16
+ import { rgbaToSketchColor, sketchColorToRgba } from "../utils/sketchColor";
17
17
 
18
18
  interface Props {
19
19
  value: Sketch.GradientStop[];
@@ -25,7 +25,7 @@ interface Props {
25
25
  onSelectStop: (index: number) => void;
26
26
  }
27
27
 
28
- export const GradientPicker = memo(function GradientPicker({
28
+ export const GradientPicker = React.memo(function GradientPicker({
29
29
  value,
30
30
  selectedStop,
31
31
  onChangeColor,
@@ -34,33 +34,33 @@ export const GradientPicker = memo(function GradientPicker({
34
34
  onDelete,
35
35
  onSelectStop,
36
36
  }: Props) {
37
- const colorModel: ColorModel<RgbaColor> = useMemo(
37
+ const colorModel: ColorModel<RgbaColor> = React.useMemo(
38
38
  () => ({
39
39
  defaultColor: { r: 0, g: 0, b: 0, a: 1 },
40
40
  toHsva: rgbaToHsva,
41
41
  fromHsva: hsvaToRgba,
42
42
  equal: equalColorObjects,
43
43
  }),
44
- [],
44
+ []
45
45
  );
46
46
 
47
- const rgbaColor = useMemo(
47
+ const rgbaColor = React.useMemo(
48
48
  () => sketchColorToRgba(value[selectedStop].color),
49
- [value, selectedStop],
49
+ [value, selectedStop]
50
50
  );
51
51
 
52
- const handleChangeColor = useCallback(
52
+ const handleChangeColor = React.useCallback(
53
53
  (value: RgbaColor) => {
54
54
  onChangeColor(rgbaToSketchColor(value));
55
55
  },
56
- [onChangeColor],
56
+ [onChangeColor]
57
57
  );
58
58
 
59
- const handleAddGradientStop = useCallback(
59
+ const handleAddGradientStop = React.useCallback(
60
60
  (value: RgbaColor, position: number) => {
61
61
  onAdd(rgbaToSketchColor(value), position);
62
62
  },
63
- [onAdd],
63
+ [onAdd]
64
64
  );
65
65
 
66
66
  return (
@@ -1,10 +1,4 @@
1
- import React, {
2
- CSSProperties,
3
- ForwardedRef,
4
- forwardRef,
5
- memo,
6
- useMemo,
7
- } from "react";
1
+ import * as React from "react";
8
2
  import { cssVars } from "../theme";
9
3
  import { Button, ButtonRootProps } from "./Button";
10
4
  import { IconName, Icons } from "./Icons";
@@ -12,20 +6,20 @@ import { IconName, Icons } from "./Icons";
12
6
  type Props = Omit<ButtonRootProps, "children" | "variant" | "flex" | "size"> & {
13
7
  iconName: IconName;
14
8
  className?: string;
15
- style?: CSSProperties;
9
+ style?: React.CSSProperties;
16
10
  selected?: boolean;
17
11
  color?: string;
18
12
  size?: number;
19
13
  };
20
14
 
21
- export const IconButton = memo(
22
- forwardRef(function IconButton(
15
+ export const IconButton = React.memo(
16
+ React.forwardRef(function IconButton(
23
17
  { selected, iconName, color, size, contentStyle, ...props }: Props,
24
- forwardedRef: ForwardedRef<HTMLButtonElement>
18
+ forwardedRef: React.ForwardedRef<HTMLButtonElement>
25
19
  ) {
26
20
  const Icon = Icons[iconName];
27
21
 
28
- const style = useMemo((): CSSProperties => {
22
+ const style = React.useMemo((): React.CSSProperties => {
29
23
  return {
30
24
  padding: "0 2px",
31
25
  ...(size && { minHeight: size }),
@@ -1,13 +1,13 @@
1
1
  import * as Icons from "@noya-app/noya-icons";
2
- import React, { ReactNode } from "react";
2
+ import * as React from "react";
3
3
 
4
4
  export type IconName = keyof typeof Icons;
5
5
 
6
6
  export { Icons };
7
7
 
8
8
  export function renderIcon(
9
- iconName: Exclude<ReactNode, string> | IconName
10
- ): ReactNode {
9
+ iconName: Exclude<React.ReactNode, string> | IconName
10
+ ): React.ReactNode {
11
11
  if (typeof iconName === "string") {
12
12
  const Icon = Icons[iconName as IconName];
13
13