@noya-app/noya-designsystem 0.1.63 → 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 (143) hide show
  1. package/.turbo/turbo-build.log +13 -10
  2. package/CHANGELOG.md +25 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.mts +648 -333
  5. package/dist/index.d.ts +648 -333
  6. package/dist/index.js +13813 -9157
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +13949 -9269
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +10 -22
  11. package/src/__tests__/validateDropIndicator.test.ts +26 -41
  12. package/src/__tests__/workspaceLayout.test.ts +281 -0
  13. package/src/components/ActionMenu.tsx +2 -0
  14. package/src/components/ActivityIndicator.tsx +13 -6
  15. package/src/components/ActivityLog.tsx +197 -0
  16. package/src/components/AnimatePresence.tsx +43 -26
  17. package/src/components/Avatar.tsx +45 -26
  18. package/src/components/Banner.tsx +57 -11
  19. package/src/components/BaseToolbar.tsx +67 -34
  20. package/src/components/Breadcrumbs.tsx +4 -2
  21. package/src/components/Button.tsx +28 -23
  22. package/src/components/Checkbox.tsx +6 -35
  23. package/src/components/Chip.tsx +47 -33
  24. package/src/components/Collection.tsx +20 -12
  25. package/src/components/ColorSwatch.tsx +42 -22
  26. package/src/components/ColorSwatchControl.tsx +7 -5
  27. package/src/components/Combobox.tsx +8 -6
  28. package/src/components/ComboboxMenu.tsx +6 -5
  29. package/src/components/CommandPalette.tsx +3 -1
  30. package/src/components/ContextMenu.tsx +3 -1
  31. package/src/components/Dialog.tsx +9 -13
  32. package/src/components/DimensionInput.tsx +2 -0
  33. package/src/components/Divider.tsx +18 -14
  34. package/src/components/DraggableMenuButton.tsx +11 -5
  35. package/src/components/Drawer.tsx +14 -6
  36. package/src/components/DropdownMenu.tsx +4 -2
  37. package/src/components/EditableText.tsx +4 -2
  38. package/src/components/Fade.tsx +13 -13
  39. package/src/components/FileUploadIndicator.tsx +108 -0
  40. package/src/components/FloatingWindow.tsx +4 -2
  41. package/src/components/Grid.tsx +58 -42
  42. package/src/components/GridView.tsx +65 -36
  43. package/src/components/IVirtualizedList.tsx +5 -0
  44. package/src/components/IconButton.tsx +2 -0
  45. package/src/components/Icons.tsx +24 -4
  46. package/src/components/InputField.tsx +21 -19
  47. package/src/components/InspectorContainer.tsx +2 -2
  48. package/src/components/InspectorPrimitives.tsx +15 -9
  49. package/src/components/Label.tsx +4 -3
  50. package/src/components/LabeledElementView.tsx +9 -5
  51. package/src/components/LabeledField.tsx +8 -6
  52. package/src/components/List.tsx +112 -46
  53. package/src/components/ListMenu.tsx +91 -0
  54. package/src/components/ListNavigator.tsx +106 -0
  55. package/src/components/ListView.tsx +22 -1147
  56. package/src/components/MediaThumbnail.tsx +84 -14
  57. package/src/components/Message.tsx +11 -12
  58. package/src/components/Navigator.tsx +461 -0
  59. package/src/components/NoyaLogo.tsx +1 -1
  60. package/src/components/Popover.tsx +12 -6
  61. package/src/components/Progress.tsx +31 -8
  62. package/src/components/ResizableContainer.tsx +187 -0
  63. package/src/components/RingProgress.tsx +128 -0
  64. package/src/components/ScrollArea.tsx +7 -5
  65. package/src/components/ScrollableSidebar.tsx +10 -0
  66. package/src/components/SearchCompletionMenu.tsx +5 -3
  67. package/src/components/Section.tsx +109 -57
  68. package/src/components/SegmentedControl.tsx +51 -25
  69. package/src/components/SelectMenu.tsx +16 -21
  70. package/src/components/SelectionToolbar.tsx +7 -4
  71. package/src/components/Slider.tsx +12 -10
  72. package/src/components/Spacer.tsx +2 -2
  73. package/src/components/StackNavigator.tsx +378 -0
  74. package/src/components/Stepper.tsx +88 -0
  75. package/src/components/Switch.tsx +7 -5
  76. package/src/components/Tabs.tsx +6 -4
  77. package/src/components/Text.tsx +29 -18
  78. package/src/components/TextArea.tsx +5 -3
  79. package/src/components/Toast.tsx +5 -5
  80. package/src/components/Toolbar.tsx +57 -10
  81. package/src/components/ToolbarDrawer.tsx +29 -0
  82. package/src/components/Tooltip.tsx +2 -2
  83. package/src/components/TreeView.tsx +13 -4
  84. package/src/components/UserPointer.tsx +6 -4
  85. package/src/components/ai-assistant/AIAssistantLayout.tsx +10 -8
  86. package/src/components/blocks/ImageBlockComponent.tsx +162 -0
  87. package/src/components/connected-users-menu/ConnectedUsersMenuLayout.tsx +6 -4
  88. package/src/components/file-explorer/FileExplorerLayout.tsx +22 -8
  89. package/src/components/internal/Checkmark.tsx +24 -0
  90. package/src/components/internal/Menu.tsx +24 -19
  91. package/src/components/internal/MenuViewport.tsx +15 -6
  92. package/src/components/internal/SelectItem.tsx +7 -26
  93. package/src/components/internal/TextInput.tsx +7 -1
  94. package/src/components/listView/ListViewContexts.tsx +68 -0
  95. package/src/components/listView/ListViewEditableRowTitle.tsx +55 -0
  96. package/src/components/listView/ListViewRoot.tsx +521 -0
  97. package/src/components/listView/ListViewRow.tsx +475 -0
  98. package/src/components/listView/ListViewRowTitle.tsx +21 -0
  99. package/src/components/listView/types.ts +11 -0
  100. package/src/components/pipeline/PipelineResultLayout.tsx +6 -6
  101. package/src/components/sorting/DragRegistration.tsx +10 -5
  102. package/src/components/sorting/SharedDragProvider.tsx +134 -27
  103. package/src/components/sorting/Sortable.tsx +90 -27
  104. package/src/components/sorting/createSharedDrag.tsx +7 -6
  105. package/src/components/sorting/sorting.ts +6 -9
  106. package/src/components/workspace/DrawerWorkspaceLayout.tsx +110 -29
  107. package/src/components/workspace/PanelWorkspaceLayout.tsx +148 -99
  108. package/src/components/workspace/VerticalTabMenu.tsx +46 -0
  109. package/src/components/workspace/WorkspaceLayout.tsx +76 -63
  110. package/src/components/workspace/constants.ts +4 -0
  111. package/src/components/workspace/panelStorage.ts +216 -0
  112. package/src/components/workspace/types.ts +11 -4
  113. package/src/contexts/DesignSystemConfiguration.tsx +12 -5
  114. package/src/contexts/DialogContext.tsx +19 -5
  115. package/src/contexts/FloatingWindowContext.tsx +2 -0
  116. package/src/contexts/GlobalInputBlurContext.tsx +2 -0
  117. package/src/contexts/ImageDataContext.tsx +2 -0
  118. package/src/contexts/LinkComponentContext.tsx +17 -0
  119. package/src/contexts/OpenPortalsContext.tsx +2 -0
  120. package/src/contexts/PortalScopeContext.tsx +2 -0
  121. package/src/hooks/mergeEventHandlers.ts +1 -1
  122. package/src/hooks/useHover.ts +30 -17
  123. package/src/hooks/useIndent.ts +5 -8
  124. package/src/hooks/useLabel.ts +2 -0
  125. package/src/hooks/useObjectURL.ts +5 -3
  126. package/src/hooks/usePlatform.ts +5 -3
  127. package/src/hooks/useTheme.ts +3 -1
  128. package/src/index.css +43 -10
  129. package/src/index.tsx +15 -7
  130. package/src/utils/classNames.ts +56 -9
  131. package/src/utils/combobox.ts +2 -0
  132. package/src/utils/editableBlockStyles.ts +2 -2
  133. package/src/utils/formatByteSize.ts +9 -0
  134. package/src/utils/inputs.ts +8 -6
  135. package/src/utils/sketchColor.ts +0 -34
  136. package/src/utils/withSeparatorElements.ts +1 -1
  137. package/tailwind.config.ts +1 -0
  138. package/src/components/FillInputField.tsx +0 -37
  139. package/src/components/FillPreviewBackground.tsx +0 -132
  140. package/src/components/GradientPicker.tsx +0 -88
  141. package/src/hooks/usePreservePanelSize.tsx +0 -183
  142. package/src/mediaQuery.ts +0 -13
  143. package/src/utils/getGradientBackground.tsx +0 -31
@@ -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
+ }
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import { isDeepEqual } from "@noya-app/noya-utils";
2
4
  import React, {
3
5
  Children,
@@ -9,7 +11,13 @@ import React, {
9
11
  useState,
10
12
  } from "react";
11
13
 
12
- type AnimatePresenceProps = {
14
+ export type AnimatePresenceChildState =
15
+ | "entering"
16
+ | "entered"
17
+ | "exiting"
18
+ | "exited";
19
+
20
+ export type AnimatePresenceProps = {
13
21
  children: React.ReactNode;
14
22
  /**
15
23
  * Duration of enter/exit animations in milliseconds
@@ -33,17 +41,16 @@ type AnimatePresenceProps = {
33
41
  */
34
42
  mode?: "wait" | "crossfade";
35
43
  className?: string;
44
+ onChildStateChange?: (key: string, state: AnimatePresenceChildState) => void;
36
45
  };
37
46
 
38
- const defaultDuration = 150;
39
-
40
- type AnimationState = "entering" | "entered" | "exiting" | "exited";
41
-
42
47
  type PresenceChildProps = {
43
48
  isPresent: boolean;
44
49
  onExitComplete: () => void;
45
50
  waitingToEnter?: boolean;
46
51
  className?: string;
52
+ childKey: string;
53
+ onStateChange?: (key: string, state: AnimatePresenceChildState) => void;
47
54
  } & Pick<
48
55
  AnimatePresenceProps,
49
56
  "animationStyles" | "children" | "duration" | "skipInitialAnimation"
@@ -54,15 +61,15 @@ const defaultAnimationStyles: Required<
54
61
  > = {
55
62
  entering: {
56
63
  opacity: 0,
57
- transition: `opacity ${defaultDuration}ms ease-in`,
64
+ transition: `opacity 200ms ease-in`,
58
65
  },
59
66
  entered: {
60
67
  opacity: 1,
61
- transition: `opacity ${defaultDuration}ms ease-in`,
68
+ transition: `opacity 200ms ease-in`,
62
69
  },
63
70
  exiting: {
64
71
  opacity: 0,
65
- transition: `opacity ${defaultDuration}ms ease-out`,
72
+ transition: `opacity 200ms ease-out`,
66
73
  },
67
74
  };
68
75
 
@@ -74,24 +81,28 @@ const PresenceChild = ({
74
81
  onExitComplete,
75
82
  skipInitialAnimation,
76
83
  className,
84
+ childKey,
85
+ onStateChange,
77
86
  }: PresenceChildProps) => {
78
- const [animationState, setAnimationState] = useState<AnimationState>(() =>
79
- skipInitialAnimation && isPresent
80
- ? "entered"
81
- : isPresent
82
- ? "entering"
83
- : "exited"
84
- );
85
- const timeoutRef = useRef<number>();
87
+ const [animationState, setAnimationState] =
88
+ useState<AnimatePresenceChildState>(() =>
89
+ skipInitialAnimation && isPresent
90
+ ? "entered"
91
+ : isPresent
92
+ ? "entering"
93
+ : "exited"
94
+ );
95
+ const timeoutRef = useRef<number>(undefined);
86
96
  const isInitialMount = useRef(true);
87
97
 
88
- const mergedAnimationStyles = {
98
+ const mergedAnimationStyles: Record<
99
+ AnimatePresenceChildState,
100
+ React.CSSProperties
101
+ > = {
89
102
  ...defaultAnimationStyles,
90
103
  ...animationStyles,
91
104
  exited: {},
92
- willChange: "opacity",
93
- backfaceVisibility: "hidden",
94
- } as Record<AnimationState, React.CSSProperties>;
105
+ };
95
106
 
96
107
  useLayoutEffect(() => {
97
108
  let rafId: number;
@@ -130,6 +141,11 @@ const PresenceChild = ({
130
141
  skipInitialAnimation,
131
142
  ]);
132
143
 
144
+ useEffect(() => {
145
+ if (!childKey) return;
146
+ onStateChange?.(childKey, animationState);
147
+ }, [animationState, childKey, onStateChange]);
148
+
133
149
  if (animationState === "exited" && !isPresent) {
134
150
  return null;
135
151
  }
@@ -142,28 +158,29 @@ const PresenceChild = ({
142
158
  };
143
159
 
144
160
  type MountedChild = {
145
- child: React.ReactElement;
161
+ child: React.ReactElement<any>;
146
162
  isPresent: boolean;
147
163
  skipInitialAnimation?: boolean;
148
164
  };
149
165
 
150
166
  export const AnimatePresence = ({
151
167
  children,
152
- duration = defaultDuration,
168
+ duration = 200,
153
169
  animationStyles,
154
170
  skipInitialAnimation,
155
171
  mode = "wait",
156
172
  className,
173
+ onChildStateChange,
157
174
  }: AnimatePresenceProps) => {
158
175
  const [mountedChildren, setMountedChildren] = useState<MountedChild[]>([]);
159
- const previousChildrenRef = useRef<React.ReactElement[]>([]);
176
+ const previousChildrenRef = useRef<React.ReactElement<any>[]>([]);
160
177
  const removingChildrenRef = useRef<Set<string>>(new Set());
161
178
  const waitingToEnterRef = useRef<MountedChild[]>([]);
162
179
 
163
180
  useEffect(() => {
164
181
  const newChildren = Children.toArray(children).filter(
165
182
  isValidElement
166
- ) as React.ReactElement[];
183
+ ) as React.ReactElement<any>[];
167
184
 
168
185
  setMountedChildren((prev) => {
169
186
  if (!prev.length && skipInitialAnimation) {
@@ -253,12 +270,14 @@ export const AnimatePresence = ({
253
270
  {mountedChildren.map(({ child, isPresent, skipInitialAnimation }) => (
254
271
  <PresenceChild
255
272
  key={child.key}
273
+ childKey={child.key?.toString() ?? ""}
256
274
  duration={duration}
257
275
  animationStyles={animationStyles}
258
276
  isPresent={isPresent}
259
277
  skipInitialAnimation={skipInitialAnimation}
260
278
  onExitComplete={() => removeChild(child.key?.toString() ?? "")}
261
279
  className={className}
280
+ onStateChange={onChildStateChange}
262
281
  >
263
282
  {child}
264
283
  </PresenceChild>
@@ -266,5 +285,3 @@ export const AnimatePresence = ({
266
285
  </>
267
286
  );
268
287
  };
269
-
270
- export type { AnimatePresenceProps };
@@ -1,13 +1,16 @@
1
- import * as AvatarPrimitive from "@radix-ui/react-avatar";
1
+ "use client";
2
+
3
+ import { Avatar as AvatarPrimitive } from "radix-ui";
2
4
  import React, { forwardRef, useMemo } from "react";
3
5
  import { cx } from "../utils/classNames";
4
6
  import { colorFromString } from "../utils/colorFromString";
5
7
 
6
8
  const getInnerAvatarStyles = (
7
9
  size: number,
8
- variant: "normal" | "bare"
10
+ variant: "normal" | "bare" | "borderless"
9
11
  ): React.CSSProperties => {
10
- const borderSize = variant === "bare" ? 4 : size > 36 ? 8 : 6;
12
+ const borderSize =
13
+ variant === "bare" ? 4 : variant === "borderless" ? 0 : size > 36 ? 8 : 6;
11
14
  const dimension = `${size - borderSize}px`;
12
15
  return {
13
16
  width: dimension,
@@ -18,7 +21,7 @@ const getInnerAvatarStyles = (
18
21
  };
19
22
 
20
23
  const AvatarRoot = React.forwardRef<
21
- React.ElementRef<typeof AvatarPrimitive.Root>,
24
+ React.ComponentRef<typeof AvatarPrimitive.Root>,
22
25
  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> &
23
26
  Pick<AvatarProps, "size" | "variant" | "backgroundColor" | "disabled">
24
27
  >(
@@ -49,8 +52,8 @@ const AvatarRoot = React.forwardRef<
49
52
  ref={ref}
50
53
  style={dynamicStyles}
51
54
  className={cx(
52
- "flex items-center justify-center align-middle overflow-hidden select-none cursor-pointer rounded-full",
53
- 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",
54
57
  className
55
58
  )}
56
59
  {...props}
@@ -60,7 +63,7 @@ const AvatarRoot = React.forwardRef<
60
63
  );
61
64
 
62
65
  const AvatarImage = React.forwardRef<
63
- React.ElementRef<typeof AvatarPrimitive.Image>,
66
+ React.ComponentRef<typeof AvatarPrimitive.Image>,
64
67
  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image> &
65
68
  Pick<AvatarProps, "size" | "variant">
66
69
  >(({ className, size = 21, variant = "normal", ...props }, ref) => {
@@ -68,7 +71,7 @@ const AvatarImage = React.forwardRef<
68
71
  <AvatarPrimitive.Image
69
72
  ref={ref}
70
73
  className={cx(
71
- "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",
72
75
  className
73
76
  )}
74
77
  style={getInnerAvatarStyles(size, variant)}
@@ -82,7 +85,7 @@ function getMonogram(text?: string) {
82
85
  }
83
86
 
84
87
  const AvatarFallback = React.forwardRef<
85
- React.ElementRef<typeof AvatarPrimitive.Fallback>,
88
+ React.ComponentRef<typeof AvatarPrimitive.Fallback>,
86
89
  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback> &
87
90
  Pick<AvatarProps, "size" | "variant" | "backgroundColor">
88
91
  >(
@@ -100,7 +103,7 @@ const AvatarFallback = React.forwardRef<
100
103
  ...style,
101
104
  }}
102
105
  className={cx(
103
- "font-medium flex items-center justify-center text-white",
106
+ "n-font-medium n-flex n-items-center n-justify-center n-text-white",
104
107
  className
105
108
  )}
106
109
  {...props}
@@ -123,7 +126,7 @@ export type AvatarProps = {
123
126
  /** colors are generated from the fallback string or title, but you can override it here */
124
127
  backgroundColor?: string;
125
128
  /** @default normal */
126
- variant?: "normal" | "bare";
129
+ variant?: "normal" | "bare" | "borderless";
127
130
  disabled?: boolean;
128
131
  /** Intended to be used in place of a fallback monogram, like an icon */
129
132
  children?: React.ReactNode;
@@ -137,7 +140,7 @@ export const Avatar = React.memo(
137
140
  name,
138
141
  userId,
139
142
  size = 21,
140
- backgroundColor,
143
+ backgroundColor: backgroundColorProp,
141
144
  variant = "normal",
142
145
  children,
143
146
  className,
@@ -145,14 +148,17 @@ export const Avatar = React.memo(
145
148
  }: AvatarProps,
146
149
  forwardedRef: React.ForwardedRef<HTMLDivElement>
147
150
  ) {
148
- const getBackgroundColor =
149
- backgroundColor ?? colorFromString(userId ?? name ?? "");
151
+ const backgroundColor =
152
+ backgroundColorProp ??
153
+ (variant === "borderless" && image
154
+ ? undefined
155
+ : colorFromString(userId ?? name ?? ""));
150
156
 
151
157
  return (
152
158
  <AvatarRoot
153
159
  ref={forwardedRef}
154
160
  size={size}
155
- backgroundColor={getBackgroundColor}
161
+ backgroundColor={backgroundColor}
156
162
  variant={variant}
157
163
  className={className}
158
164
  {...props}
@@ -161,7 +167,7 @@ export const Avatar = React.memo(
161
167
  <AvatarImage src={image} alt={name} size={size} variant={variant} />
162
168
  ) : (
163
169
  <AvatarFallback
164
- backgroundColor={getBackgroundColor}
170
+ backgroundColor={backgroundColor}
165
171
  size={size}
166
172
  variant={variant}
167
173
  >
@@ -194,12 +200,14 @@ export const AvatarStack = React.memo(function AvatarStack({
194
200
  children,
195
201
  max = 3,
196
202
  className,
203
+ overlapAmount = size > 36 ? 12 : 6,
197
204
  }: {
198
205
  size?: number;
199
206
  /** @default 3 */
200
207
  max?: number;
201
208
  children: React.ReactNode;
202
209
  className?: string;
210
+ overlapAmount?: number;
203
211
  }) {
204
212
  const avatarCount = React.Children.count(children);
205
213
  // can add logic later to shorten values over 2 digits, etc.
@@ -207,7 +215,10 @@ export const AvatarStack = React.memo(function AvatarStack({
207
215
 
208
216
  const childrenArray = useMemo(() => {
209
217
  const processedChildren = React.Children.toArray(children)
210
- .map((child) => React.cloneElement(child as React.ReactElement, { size }))
218
+ .map((child) =>
219
+ React.cloneElement(child as React.ReactElement<any>, { size })
220
+ )
221
+ .reverse()
211
222
  .slice(0, max);
212
223
 
213
224
  if (avatarCount > max) {
@@ -220,20 +231,28 @@ export const AvatarStack = React.memo(function AvatarStack({
220
231
  return processedChildren;
221
232
  }, [children, max, size, avatarCount, overflowCount]);
222
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
+
223
248
  return (
224
249
  <div
225
- className={cx(
226
- "flex flex-row-reverse",
227
- size > 36 ? "pl-3" : "pl-1.5",
228
- className
229
- )}
250
+ className={cx("n-flex n-flex-row-reverse", className)}
251
+ style={containerStyle}
230
252
  >
231
253
  {childrenArray.map((child, index) => {
232
254
  return (
233
- <div
234
- className={cx("relative", size > 36 ? "-ml-3" : "-ml-1.5")}
235
- key={index}
236
- >
255
+ <div key={index} className="n-relative" style={itemStyle}>
237
256
  {child}
238
257
  </div>
239
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
  }