@noya-app/noya-designsystem 0.1.64 → 0.1.65

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 (115) hide show
  1. package/.turbo/turbo-build.log +13 -10
  2. package/CHANGELOG.md +12 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.mts +501 -228
  5. package/dist/index.d.ts +501 -228
  6. package/dist/index.js +6738 -2571
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +6697 -2499
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +6 -6
  11. package/src/components/ActivityIndicator.tsx +9 -4
  12. package/src/components/ActivityLog.tsx +197 -0
  13. package/src/components/AnimatePresence.tsx +37 -22
  14. package/src/components/Avatar.tsx +42 -25
  15. package/src/components/Banner.tsx +57 -11
  16. package/src/components/BaseToolbar.tsx +29 -7
  17. package/src/components/Breadcrumbs.tsx +2 -2
  18. package/src/components/Button.tsx +23 -24
  19. package/src/components/Checkbox.tsx +6 -35
  20. package/src/components/Chip.tsx +45 -33
  21. package/src/components/Collection.tsx +16 -11
  22. package/src/components/ColorSwatch.tsx +3 -3
  23. package/src/components/ColorSwatchControl.tsx +2 -2
  24. package/src/components/Combobox.tsx +6 -6
  25. package/src/components/ComboboxMenu.tsx +6 -5
  26. package/src/components/CommandPalette.tsx +1 -1
  27. package/src/components/Dialog.tsx +6 -12
  28. package/src/components/Divider.tsx +8 -8
  29. package/src/components/DraggableMenuButton.tsx +9 -5
  30. package/src/components/Drawer.tsx +5 -5
  31. package/src/components/DropdownMenu.tsx +1 -1
  32. package/src/components/EditableText.tsx +2 -2
  33. package/src/components/Fade.tsx +13 -13
  34. package/src/components/FileUploadIndicator.tsx +11 -11
  35. package/src/components/FloatingWindow.tsx +2 -2
  36. package/src/components/Grid.tsx +18 -11
  37. package/src/components/GridView.tsx +32 -25
  38. package/src/components/IVirtualizedList.tsx +5 -0
  39. package/src/components/Icons.tsx +24 -4
  40. package/src/components/InputField.tsx +19 -19
  41. package/src/components/InspectorContainer.tsx +2 -2
  42. package/src/components/InspectorPrimitives.tsx +15 -9
  43. package/src/components/Label.tsx +4 -3
  44. package/src/components/LabeledElementView.tsx +3 -3
  45. package/src/components/LabeledField.tsx +6 -6
  46. package/src/components/List.tsx +103 -45
  47. package/src/components/ListMenu.tsx +91 -0
  48. package/src/components/ListNavigator.tsx +106 -0
  49. package/src/components/ListView.tsx +20 -1158
  50. package/src/components/MediaThumbnail.tsx +56 -15
  51. package/src/components/Message.tsx +5 -5
  52. package/src/components/Navigator.tsx +461 -0
  53. package/src/components/NoyaLogo.tsx +1 -1
  54. package/src/components/Popover.tsx +9 -5
  55. package/src/components/Progress.tsx +19 -9
  56. package/src/components/ResizableContainer.tsx +187 -0
  57. package/src/components/RingProgress.tsx +128 -0
  58. package/src/components/ScrollArea.tsx +4 -4
  59. package/src/components/ScrollableSidebar.tsx +10 -0
  60. package/src/components/SearchCompletionMenu.tsx +3 -3
  61. package/src/components/Section.tsx +107 -57
  62. package/src/components/SegmentedControl.tsx +29 -29
  63. package/src/components/SelectMenu.tsx +13 -20
  64. package/src/components/SelectionToolbar.tsx +4 -3
  65. package/src/components/Slider.tsx +9 -9
  66. package/src/components/Spacer.tsx +2 -2
  67. package/src/components/StackNavigator.tsx +378 -0
  68. package/src/components/Stepper.tsx +88 -0
  69. package/src/components/Switch.tsx +4 -4
  70. package/src/components/Tabs.tsx +3 -3
  71. package/src/components/Text.tsx +23 -13
  72. package/src/components/TextArea.tsx +3 -3
  73. package/src/components/Toast.tsx +4 -4
  74. package/src/components/Toolbar.tsx +50 -9
  75. package/src/components/ToolbarDrawer.tsx +2 -2
  76. package/src/components/Tooltip.tsx +1 -1
  77. package/src/components/TreeView.tsx +11 -4
  78. package/src/components/UserPointer.tsx +4 -4
  79. package/src/components/ai-assistant/AIAssistantLayout.tsx +8 -8
  80. package/src/components/blocks/ImageBlockComponent.tsx +13 -7
  81. package/src/components/connected-users-menu/ConnectedUsersMenuLayout.tsx +4 -4
  82. package/src/components/file-explorer/FileExplorerLayout.tsx +20 -8
  83. package/src/components/internal/Checkmark.tsx +1 -1
  84. package/src/components/internal/Menu.tsx +22 -18
  85. package/src/components/internal/MenuViewport.tsx +13 -6
  86. package/src/components/internal/SelectItem.tsx +3 -3
  87. package/src/components/internal/TextInput.tsx +5 -1
  88. package/src/components/listView/ListViewContexts.tsx +68 -0
  89. package/src/components/listView/ListViewEditableRowTitle.tsx +55 -0
  90. package/src/components/listView/ListViewRoot.tsx +521 -0
  91. package/src/components/listView/ListViewRow.tsx +475 -0
  92. package/src/components/listView/ListViewRowTitle.tsx +21 -0
  93. package/src/components/listView/types.ts +11 -0
  94. package/src/components/pipeline/PipelineResultLayout.tsx +6 -6
  95. package/src/components/sorting/DragRegistration.tsx +2 -1
  96. package/src/components/sorting/SharedDragProvider.tsx +91 -9
  97. package/src/components/sorting/Sortable.tsx +71 -17
  98. package/src/components/workspace/DrawerWorkspaceLayout.tsx +5 -5
  99. package/src/components/workspace/PanelWorkspaceLayout.tsx +65 -49
  100. package/src/components/workspace/VerticalTabMenu.tsx +25 -15
  101. package/src/components/workspace/WorkspaceLayout.tsx +30 -21
  102. package/src/contexts/DialogContext.tsx +17 -5
  103. package/src/hooks/useIndent.ts +3 -8
  104. package/src/index.css +24 -10
  105. package/src/index.tsx +10 -5
  106. package/src/utils/classNames.ts +52 -6
  107. package/src/utils/editableBlockStyles.ts +2 -2
  108. package/src/utils/formatByteSize.ts +1 -0
  109. package/src/utils/inputs.ts +8 -6
  110. package/src/utils/sketchColor.ts +0 -34
  111. package/tailwind.config.ts +1 -0
  112. package/src/components/FillInputField.tsx +0 -37
  113. package/src/components/FillPreviewBackground.tsx +0 -134
  114. package/src/components/GradientPicker.tsx +0 -90
  115. package/src/utils/getGradientBackground.tsx +0 -31
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noya-app/noya-designsystem",
3
- "version": "0.1.64",
3
+ "version": "0.1.65",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",
@@ -22,12 +22,12 @@
22
22
  "dependencies": {
23
23
  "@dnd-kit/core": "6.3.1",
24
24
  "@dnd-kit/sortable": "10.0.0",
25
- "@noya-app/noya-colorpicker": "0.1.27",
26
- "@noya-app/noya-utils": "0.1.6",
27
- "@noya-app/noya-geometry": "0.1.13",
28
- "@noya-app/noya-icons": "0.1.12",
25
+ "@noya-app/noya-colorpicker": "0.1.28",
26
+ "@noya-app/noya-utils": "0.1.7",
27
+ "@noya-app/noya-geometry": "0.1.14",
28
+ "@noya-app/noya-icons": "0.1.13",
29
29
  "@noya-app/noya-keymap": "0.1.4",
30
- "@noya-app/noya-tailwind-config": "0.1.6",
30
+ "@noya-app/noya-tailwind-config": "0.1.7",
31
31
  "radix-ui": "1.4.2",
32
32
  "@radix-ui/react-compose-refs": "^1.0.0",
33
33
  "@radix-ui/primitive": "^1.0.1",
@@ -9,6 +9,7 @@ interface Props {
9
9
  color?: string;
10
10
  trackColor?: string;
11
11
  className?: string;
12
+ style?: React.CSSProperties;
12
13
  }
13
14
 
14
15
  export const ActivityIndicator = React.memo(function ActivityIndicator({
@@ -17,8 +18,12 @@ export const ActivityIndicator = React.memo(function ActivityIndicator({
17
18
  color = "black",
18
19
  trackColor = "rgba(0, 0, 0, 0.1)",
19
20
  className,
21
+ style,
20
22
  }: Props) {
21
- const opacityStyle = React.useMemo(() => ({ opacity }), [opacity]);
23
+ const containerStyle = React.useMemo(
24
+ () => ({ opacity, ...style }),
25
+ [opacity, style]
26
+ );
22
27
  const dynamicStyles = React.useMemo(
23
28
  () => ({
24
29
  border: `2px solid ${trackColor}`,
@@ -30,10 +35,10 @@ export const ActivityIndicator = React.memo(function ActivityIndicator({
30
35
  );
31
36
  return (
32
37
  <span
33
- className={cx("flex justify-center items-center", className)}
34
- style={opacityStyle}
38
+ className={cx("n-flex n-justify-center n-items-center", className)}
39
+ style={containerStyle}
35
40
  >
36
- <span style={dynamicStyles} className="animate-spin rounded-[50%]" />
41
+ <span style={dynamicStyles} className="n-animate-spin n-rounded-[50%]" />
37
42
  </span>
38
43
  );
39
44
  });
@@ -0,0 +1,197 @@
1
+ import React from "react";
2
+ import { cx } from "../utils/classNames";
3
+ import { Avatar, AvatarStack } from "./Avatar";
4
+ import { Button } from "./Button";
5
+ import { Divider } from "./Divider";
6
+ import { DropdownMenu } from "./DropdownMenu";
7
+ import { IconProp, renderIcon } from "./Icons";
8
+ import { ExtraSmall, Small } from "./Text";
9
+ import { MenuItem } from "./internal/Menu";
10
+
11
+ export type ActivityIconSemanticColor = "add" | "remove" | "update";
12
+
13
+ const IconContainer = ({
14
+ children,
15
+ semanticColor,
16
+ }: {
17
+ children: React.ReactNode;
18
+ semanticColor?: ActivityIconSemanticColor;
19
+ }) => {
20
+ return (
21
+ <div
22
+ className={cx(
23
+ "n-w-[23px] n-h-[23px] n-rounded-full n-flex n-items-center n-justify-center n-flex-none",
24
+ semanticColor === "add" && "n-bg-secondary-pastel n-text-secondary",
25
+ semanticColor === "remove" &&
26
+ "n-bg-banner-error-background n-text-banner-error-text",
27
+ semanticColor === "update" && "n-bg-[#d1ecfe] n-text-[#1984cc]",
28
+ semanticColor === undefined && "n-bg-primary-pastel n-text-primary"
29
+ )}
30
+ >
31
+ {children}
32
+ </div>
33
+ );
34
+ };
35
+
36
+ const ActivityLine = () => {
37
+ return (
38
+ <div className="n-w-[3px] n-flex-1 n-relative">
39
+ <div className="n-rounded-full n-bg-primary-150 n-absolute n-w-full n-top-1 -n-bottom-3" />
40
+ </div>
41
+ );
42
+ };
43
+
44
+ const ActivityLeft = ({ children }: { children: React.ReactNode }) => {
45
+ return (
46
+ <div className="n-flex n-flex-none n-relative n-isolate n-flex-col n-items-center">
47
+ {children}
48
+ </div>
49
+ );
50
+ };
51
+
52
+ const ActivityRight = ({
53
+ children,
54
+ className,
55
+ }: {
56
+ children: React.ReactNode;
57
+ className?: string;
58
+ }) => {
59
+ return (
60
+ <div
61
+ className={cx(
62
+ "n-flex-1 n-rounded n-flex n-p-2 n-gap-2 n-bg-canvas-background n-border n-border-divider-subtle",
63
+ className
64
+ )}
65
+ >
66
+ {children}
67
+ </div>
68
+ );
69
+ };
70
+
71
+ const ActivityCard = ({ children }: { children: React.ReactNode }) => {
72
+ return <div className="n-flex n-gap-2">{children}</div>;
73
+ };
74
+
75
+ const ActivityEventIndicator = () => {
76
+ return (
77
+ <div className="n-w-1 n-bg-primary-300 n-rounded-full n-mx-0.5 n-flex-none" />
78
+ );
79
+ };
80
+
81
+ type ActivityUser = {
82
+ userId?: string;
83
+ name?: string;
84
+ image?: string;
85
+ };
86
+
87
+ export type ActivityLogItem<M extends string = string> = {
88
+ title: React.ReactNode;
89
+ description: React.ReactNode;
90
+ time?: React.ReactNode;
91
+ icon: IconProp;
92
+ iconSemanticColor?: ActivityIconSemanticColor;
93
+ menuItems?: MenuItem<M>[];
94
+ onSelectMenuItem?: (value: M) => void;
95
+ } & ({ type: "change"; users?: ActivityUser[] } | { type: "event" });
96
+
97
+ type ActivityLogProps<M extends string> = {
98
+ style?: React.CSSProperties;
99
+ className?: string;
100
+ items?: ActivityLogItem<M>[];
101
+ };
102
+
103
+ export function ActivityLog<M extends string = string>({
104
+ style,
105
+ className,
106
+ items,
107
+ }: ActivityLogProps<M>) {
108
+ return (
109
+ <div
110
+ className={cx(
111
+ "n-flex n-flex-col n-relative n-gap-4 n-isolate",
112
+ className
113
+ )}
114
+ style={style}
115
+ >
116
+ {items?.map((item, index, list) => (
117
+ <ActivityCard key={index}>
118
+ <ActivityLeft>
119
+ <IconContainer semanticColor={item.iconSemanticColor}>
120
+ {renderIcon(item.icon)}
121
+ </IconContainer>
122
+ {index !== list.length - 1 && <ActivityLine />}
123
+ </ActivityLeft>
124
+ <ActivityRight>
125
+ {item.type === "event" && (
126
+ <>
127
+ <ActivityEventIndicator />
128
+ <div className="n-flex n-flex-col n-justify-center n-flex-1 n-gap-0.5">
129
+ <Small className="n-leading-snug">{item.title}</Small>
130
+ {item.description && (
131
+ <ExtraSmall
132
+ className="n-leading-snug n-break-all"
133
+ color="textSubtle"
134
+ >
135
+ {item.description}
136
+ </ExtraSmall>
137
+ )}
138
+ </div>
139
+ {item.menuItems && item.onSelectMenuItem && (
140
+ <DropdownMenu
141
+ items={item.menuItems}
142
+ onSelect={item.onSelectMenuItem}
143
+ >
144
+ {({ open }) => (
145
+ <Button
146
+ className="n-self-start"
147
+ icon="DotsVerticalIcon"
148
+ variant="ghost"
149
+ active={open}
150
+ />
151
+ )}
152
+ </DropdownMenu>
153
+ )}
154
+ </>
155
+ )}
156
+ {item.type === "change" && (
157
+ <div className="n-flex n-flex-col n-flex-1">
158
+ <div className="n-flex n-items-start n-flex-1 n-gap-1.5">
159
+ {item.users && (
160
+ <AvatarStack
161
+ size={24}
162
+ overlapAmount={12}
163
+ className="n-flex-none n-self-start"
164
+ >
165
+ {item.users.map((user) => (
166
+ <Avatar
167
+ key={user.userId}
168
+ variant="borderless"
169
+ image={user.image}
170
+ name={user.name}
171
+ userId={user.userId}
172
+ />
173
+ ))}
174
+ </AvatarStack>
175
+ )}
176
+ <Small className="n-py-[3px] n-flex-1">{item.title}</Small>
177
+ <ExtraSmall
178
+ color="text"
179
+ className="n-flex n-items-center n-justify-center n-opacity-50 n-pt-[5px] n-whitespace-pre"
180
+ >
181
+ {item.time}
182
+ </ExtraSmall>
183
+ </div>
184
+ {item.description && (
185
+ <>
186
+ <Divider overflow={8} className="n-my-1.5" />
187
+ <Small>{item.description}</Small>
188
+ </>
189
+ )}
190
+ </div>
191
+ )}
192
+ </ActivityRight>
193
+ </ActivityCard>
194
+ ))}
195
+ </div>
196
+ );
197
+ }
@@ -11,7 +11,13 @@ import React, {
11
11
  useState,
12
12
  } from "react";
13
13
 
14
- type AnimatePresenceProps = {
14
+ export type AnimatePresenceChildState =
15
+ | "entering"
16
+ | "entered"
17
+ | "exiting"
18
+ | "exited";
19
+
20
+ export type AnimatePresenceProps = {
15
21
  children: React.ReactNode;
16
22
  /**
17
23
  * Duration of enter/exit animations in milliseconds
@@ -35,17 +41,16 @@ type AnimatePresenceProps = {
35
41
  */
36
42
  mode?: "wait" | "crossfade";
37
43
  className?: string;
44
+ onChildStateChange?: (key: string, state: AnimatePresenceChildState) => void;
38
45
  };
39
46
 
40
- const defaultDuration = 150;
41
-
42
- type AnimationState = "entering" | "entered" | "exiting" | "exited";
43
-
44
47
  type PresenceChildProps = {
45
48
  isPresent: boolean;
46
49
  onExitComplete: () => void;
47
50
  waitingToEnter?: boolean;
48
51
  className?: string;
52
+ childKey: string;
53
+ onStateChange?: (key: string, state: AnimatePresenceChildState) => void;
49
54
  } & Pick<
50
55
  AnimatePresenceProps,
51
56
  "animationStyles" | "children" | "duration" | "skipInitialAnimation"
@@ -56,15 +61,15 @@ const defaultAnimationStyles: Required<
56
61
  > = {
57
62
  entering: {
58
63
  opacity: 0,
59
- transition: `opacity ${defaultDuration}ms ease-in`,
64
+ transition: `opacity 200ms ease-in`,
60
65
  },
61
66
  entered: {
62
67
  opacity: 1,
63
- transition: `opacity ${defaultDuration}ms ease-in`,
68
+ transition: `opacity 200ms ease-in`,
64
69
  },
65
70
  exiting: {
66
71
  opacity: 0,
67
- transition: `opacity ${defaultDuration}ms ease-out`,
72
+ transition: `opacity 200ms ease-out`,
68
73
  },
69
74
  };
70
75
 
@@ -76,24 +81,28 @@ const PresenceChild = ({
76
81
  onExitComplete,
77
82
  skipInitialAnimation,
78
83
  className,
84
+ childKey,
85
+ onStateChange,
79
86
  }: PresenceChildProps) => {
80
- const [animationState, setAnimationState] = useState<AnimationState>(() =>
81
- skipInitialAnimation && isPresent
82
- ? "entered"
83
- : isPresent
84
- ? "entering"
85
- : "exited"
86
- );
87
+ const [animationState, setAnimationState] =
88
+ useState<AnimatePresenceChildState>(() =>
89
+ skipInitialAnimation && isPresent
90
+ ? "entered"
91
+ : isPresent
92
+ ? "entering"
93
+ : "exited"
94
+ );
87
95
  const timeoutRef = useRef<number>(undefined);
88
96
  const isInitialMount = useRef(true);
89
97
 
90
- const mergedAnimationStyles = {
98
+ const mergedAnimationStyles: Record<
99
+ AnimatePresenceChildState,
100
+ React.CSSProperties
101
+ > = {
91
102
  ...defaultAnimationStyles,
92
103
  ...animationStyles,
93
104
  exited: {},
94
- willChange: "opacity",
95
- backfaceVisibility: "hidden",
96
- } as Record<AnimationState, React.CSSProperties>;
105
+ };
97
106
 
98
107
  useLayoutEffect(() => {
99
108
  let rafId: number;
@@ -132,6 +141,11 @@ const PresenceChild = ({
132
141
  skipInitialAnimation,
133
142
  ]);
134
143
 
144
+ useEffect(() => {
145
+ if (!childKey) return;
146
+ onStateChange?.(childKey, animationState);
147
+ }, [animationState, childKey, onStateChange]);
148
+
135
149
  if (animationState === "exited" && !isPresent) {
136
150
  return null;
137
151
  }
@@ -151,11 +165,12 @@ type MountedChild = {
151
165
 
152
166
  export const AnimatePresence = ({
153
167
  children,
154
- duration = defaultDuration,
168
+ duration = 200,
155
169
  animationStyles,
156
170
  skipInitialAnimation,
157
171
  mode = "wait",
158
172
  className,
173
+ onChildStateChange,
159
174
  }: AnimatePresenceProps) => {
160
175
  const [mountedChildren, setMountedChildren] = useState<MountedChild[]>([]);
161
176
  const previousChildrenRef = useRef<React.ReactElement<any>[]>([]);
@@ -255,12 +270,14 @@ export const AnimatePresence = ({
255
270
  {mountedChildren.map(({ child, isPresent, skipInitialAnimation }) => (
256
271
  <PresenceChild
257
272
  key={child.key}
273
+ childKey={child.key?.toString() ?? ""}
258
274
  duration={duration}
259
275
  animationStyles={animationStyles}
260
276
  isPresent={isPresent}
261
277
  skipInitialAnimation={skipInitialAnimation}
262
278
  onExitComplete={() => removeChild(child.key?.toString() ?? "")}
263
279
  className={className}
280
+ onStateChange={onChildStateChange}
264
281
  >
265
282
  {child}
266
283
  </PresenceChild>
@@ -268,5 +285,3 @@ export const AnimatePresence = ({
268
285
  </>
269
286
  );
270
287
  };
271
-
272
- export type { AnimatePresenceProps };
@@ -7,9 +7,10 @@ import { colorFromString } from "../utils/colorFromString";
7
7
 
8
8
  const getInnerAvatarStyles = (
9
9
  size: number,
10
- variant: "normal" | "bare"
10
+ variant: "normal" | "bare" | "borderless"
11
11
  ): React.CSSProperties => {
12
- const borderSize = variant === "bare" ? 4 : size > 36 ? 8 : 6;
12
+ const borderSize =
13
+ variant === "bare" ? 4 : variant === "borderless" ? 0 : size > 36 ? 8 : 6;
13
14
  const dimension = `${size - borderSize}px`;
14
15
  return {
15
16
  width: dimension,
@@ -20,7 +21,7 @@ const getInnerAvatarStyles = (
20
21
  };
21
22
 
22
23
  const AvatarRoot = React.forwardRef<
23
- React.ElementRef<typeof AvatarPrimitive.Root>,
24
+ React.ComponentRef<typeof AvatarPrimitive.Root>,
24
25
  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> &
25
26
  Pick<AvatarProps, "size" | "variant" | "backgroundColor" | "disabled">
26
27
  >(
@@ -51,8 +52,8 @@ const AvatarRoot = React.forwardRef<
51
52
  ref={ref}
52
53
  style={dynamicStyles}
53
54
  className={cx(
54
- "flex items-center justify-center align-middle overflow-hidden select-none cursor-pointer rounded-full",
55
- variant !== "bare" && "border border-background",
55
+ "n-flex n-items-center n-justify-center n-align-middle n-overflow-hidden n-select-none n-cursor-pointer n-rounded-full",
56
+ variant !== "bare" && "n-border n-border-background",
56
57
  className
57
58
  )}
58
59
  {...props}
@@ -62,7 +63,7 @@ const AvatarRoot = React.forwardRef<
62
63
  );
63
64
 
64
65
  const AvatarImage = React.forwardRef<
65
- React.ElementRef<typeof AvatarPrimitive.Image>,
66
+ React.ComponentRef<typeof AvatarPrimitive.Image>,
66
67
  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image> &
67
68
  Pick<AvatarProps, "size" | "variant">
68
69
  >(({ className, size = 21, variant = "normal", ...props }, ref) => {
@@ -70,7 +71,7 @@ const AvatarImage = React.forwardRef<
70
71
  <AvatarPrimitive.Image
71
72
  ref={ref}
72
73
  className={cx(
73
- "object-cover overflow-hidden rounded-full w-full h-full",
74
+ "n-object-cover n-overflow-hidden n-rounded-full n-w-full n-h-full",
74
75
  className
75
76
  )}
76
77
  style={getInnerAvatarStyles(size, variant)}
@@ -84,7 +85,7 @@ function getMonogram(text?: string) {
84
85
  }
85
86
 
86
87
  const AvatarFallback = React.forwardRef<
87
- React.ElementRef<typeof AvatarPrimitive.Fallback>,
88
+ React.ComponentRef<typeof AvatarPrimitive.Fallback>,
88
89
  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback> &
89
90
  Pick<AvatarProps, "size" | "variant" | "backgroundColor">
90
91
  >(
@@ -102,7 +103,7 @@ const AvatarFallback = React.forwardRef<
102
103
  ...style,
103
104
  }}
104
105
  className={cx(
105
- "font-medium flex items-center justify-center text-white",
106
+ "n-font-medium n-flex n-items-center n-justify-center n-text-white",
106
107
  className
107
108
  )}
108
109
  {...props}
@@ -125,7 +126,7 @@ export type AvatarProps = {
125
126
  /** colors are generated from the fallback string or title, but you can override it here */
126
127
  backgroundColor?: string;
127
128
  /** @default normal */
128
- variant?: "normal" | "bare";
129
+ variant?: "normal" | "bare" | "borderless";
129
130
  disabled?: boolean;
130
131
  /** Intended to be used in place of a fallback monogram, like an icon */
131
132
  children?: React.ReactNode;
@@ -139,7 +140,7 @@ export const Avatar = React.memo(
139
140
  name,
140
141
  userId,
141
142
  size = 21,
142
- backgroundColor,
143
+ backgroundColor: backgroundColorProp,
143
144
  variant = "normal",
144
145
  children,
145
146
  className,
@@ -147,14 +148,17 @@ export const Avatar = React.memo(
147
148
  }: AvatarProps,
148
149
  forwardedRef: React.ForwardedRef<HTMLDivElement>
149
150
  ) {
150
- const getBackgroundColor =
151
- backgroundColor ?? colorFromString(userId ?? name ?? "");
151
+ const backgroundColor =
152
+ backgroundColorProp ??
153
+ (variant === "borderless" && image
154
+ ? undefined
155
+ : colorFromString(userId ?? name ?? ""));
152
156
 
153
157
  return (
154
158
  <AvatarRoot
155
159
  ref={forwardedRef}
156
160
  size={size}
157
- backgroundColor={getBackgroundColor}
161
+ backgroundColor={backgroundColor}
158
162
  variant={variant}
159
163
  className={className}
160
164
  {...props}
@@ -163,7 +167,7 @@ export const Avatar = React.memo(
163
167
  <AvatarImage src={image} alt={name} size={size} variant={variant} />
164
168
  ) : (
165
169
  <AvatarFallback
166
- backgroundColor={getBackgroundColor}
170
+ backgroundColor={backgroundColor}
167
171
  size={size}
168
172
  variant={variant}
169
173
  >
@@ -196,12 +200,14 @@ export const AvatarStack = React.memo(function AvatarStack({
196
200
  children,
197
201
  max = 3,
198
202
  className,
203
+ overlapAmount = size > 36 ? 12 : 6,
199
204
  }: {
200
205
  size?: number;
201
206
  /** @default 3 */
202
207
  max?: number;
203
208
  children: React.ReactNode;
204
209
  className?: string;
210
+ overlapAmount?: number;
205
211
  }) {
206
212
  const avatarCount = React.Children.count(children);
207
213
  // can add logic later to shorten values over 2 digits, etc.
@@ -209,7 +215,10 @@ export const AvatarStack = React.memo(function AvatarStack({
209
215
 
210
216
  const childrenArray = useMemo(() => {
211
217
  const processedChildren = React.Children.toArray(children)
212
- .map((child) => React.cloneElement(child as React.ReactElement<any>, { size }))
218
+ .map((child) =>
219
+ React.cloneElement(child as React.ReactElement<any>, { size })
220
+ )
221
+ .reverse()
213
222
  .slice(0, max);
214
223
 
215
224
  if (avatarCount > max) {
@@ -222,20 +231,28 @@ export const AvatarStack = React.memo(function AvatarStack({
222
231
  return processedChildren;
223
232
  }, [children, max, size, avatarCount, overflowCount]);
224
233
 
234
+ const containerStyle = useMemo(
235
+ () => ({
236
+ paddingLeft: overlapAmount,
237
+ }),
238
+ [overlapAmount]
239
+ );
240
+
241
+ const itemStyle = useMemo(
242
+ () => ({
243
+ marginLeft: -overlapAmount,
244
+ }),
245
+ [overlapAmount]
246
+ );
247
+
225
248
  return (
226
249
  <div
227
- className={cx(
228
- "flex flex-row-reverse",
229
- size > 36 ? "pl-3" : "pl-1.5",
230
- className
231
- )}
250
+ className={cx("n-flex n-flex-row-reverse", className)}
251
+ style={containerStyle}
232
252
  >
233
253
  {childrenArray.map((child, index) => {
234
254
  return (
235
- <div
236
- className={cx("relative", size > 36 ? "-ml-3" : "-ml-1.5")}
237
- key={index}
238
- >
255
+ <div key={index} className="n-relative" style={itemStyle}>
239
256
  {child}
240
257
  </div>
241
258
  );
@@ -1,29 +1,75 @@
1
+ import { ExclamationTriangleIcon, InfoCircledIcon } from "@noya-app/noya-icons";
1
2
  import React, { CSSProperties, ReactNode } from "react";
2
- import { cssVars } from "../theme";
3
- import { cx } from "../utils/classNames";
3
+ import { cx, mergeConflictingClassNames } from "../utils/classNames";
4
4
  import { Small } from "./Text";
5
5
 
6
- const bannerStyles = {
7
- backgroundColor: cssVars.colors.inputBackground,
8
- color: cssVars.colors.textDisabled,
9
- };
6
+ type BannerVariant = "muted" | "info" | "error";
10
7
 
11
8
  export type BannerProps = {
12
- label?: ReactNode;
9
+ children?: ReactNode;
13
10
  className?: string;
14
11
  style?: CSSProperties;
12
+ variant?: BannerVariant;
13
+ labelStyle?: CSSProperties;
14
+ labelClassName?: string;
15
15
  };
16
16
 
17
- export function Banner({ label = "No items", className, style }: BannerProps) {
17
+ export function Banner({
18
+ children: label = "No items",
19
+ className,
20
+ style,
21
+ variant = "muted",
22
+ labelStyle,
23
+ labelClassName: labelClassNameProp,
24
+ }: BannerProps) {
25
+ const labelClassName = mergeConflictingClassNames(
26
+ [
27
+ "n-flex-1 n-text-center",
28
+ variant === "muted"
29
+ ? "!n-text-text-disabled"
30
+ : variant === "error"
31
+ ? "!n-text-banner-error-text"
32
+ : "!n-text-banner-info-text",
33
+ labelClassNameProp,
34
+ ],
35
+ {
36
+ categories: ["textAlign"],
37
+ }
38
+ );
39
+
18
40
  return (
19
41
  <div
20
42
  className={cx(
21
- "flex p-1 items-center justify-center select-none rounded",
43
+ "n-flex n-py-1 n-px-1.5 n-items-center n-justify-center n-select-none n-rounded n-gap-1.5 n-min-h-input-height",
44
+ variant === "muted" && "n-bg-input-background",
45
+ variant === "info" &&
46
+ "n-bg-banner-info-background n-border n-border-banner-info-border",
47
+ variant === "error" &&
48
+ "n-bg-banner-error-background n-border n-border-banner-error-border",
22
49
  className
23
50
  )}
24
- style={{ ...bannerStyles, ...style }}
51
+ style={style}
25
52
  >
26
- <Small color="textDisabled">{label}</Small>
53
+ {(variant === "info" || variant === "error") && (
54
+ <Small
55
+ className={cx(
56
+ "n-flex-none n-self-start n-flex n-items-center",
57
+ variant === "error"
58
+ ? "!n-text-banner-error-text"
59
+ : "!n-text-banner-info-text"
60
+ )}
61
+ >
62
+ {`\u200B`}
63
+ {variant === "error" ? (
64
+ <ExclamationTriangleIcon className="n-text-heading5" />
65
+ ) : (
66
+ <InfoCircledIcon className="n-text-heading5" />
67
+ )}
68
+ </Small>
69
+ )}
70
+ <Small className={labelClassName} style={labelStyle}>
71
+ {label}
72
+ </Small>
27
73
  </div>
28
74
  );
29
75
  }