@noya-app/noya-designsystem 0.1.48 → 0.1.50

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 (55) hide show
  1. package/.turbo/turbo-build.log +10 -10
  2. package/CHANGELOG.md +19 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.mts +347 -173
  5. package/dist/index.d.ts +347 -173
  6. package/dist/index.js +3602 -2811
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +4761 -3993
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +6 -5
  11. package/src/__tests__/validateDropIndicator.test.ts +4 -4
  12. package/src/components/ActionMenu.tsx +15 -4
  13. package/src/components/Avatar.tsx +2 -2
  14. package/src/components/Banner.tsx +29 -0
  15. package/src/components/BaseToolbar.tsx +2 -2
  16. package/src/components/Button.tsx +2 -2
  17. package/src/components/Checkbox.tsx +2 -2
  18. package/src/components/Chip.tsx +15 -14
  19. package/src/components/Collection.tsx +25 -2
  20. package/src/components/Combobox.tsx +22 -23
  21. package/src/components/ComboboxMenu.tsx +1 -1
  22. package/src/components/Dialog.tsx +2 -2
  23. package/src/components/DimensionInput.tsx +14 -12
  24. package/src/components/EditableText.tsx +3 -1
  25. package/src/components/FillInputField.tsx +2 -2
  26. package/src/components/Grid.tsx +131 -113
  27. package/src/components/GridView.tsx +36 -18
  28. package/src/components/InputField.tsx +116 -171
  29. package/src/components/Label.tsx +14 -73
  30. package/src/components/LabeledField.tsx +13 -2
  31. package/src/components/List.tsx +106 -47
  32. package/src/components/ListView.tsx +52 -31
  33. package/src/components/MediaThumbnail.tsx +14 -6
  34. package/src/components/Message.tsx +8 -8
  35. package/src/components/NoyaLogo.tsx +41 -0
  36. package/src/components/Popover.tsx +9 -6
  37. package/src/components/Section.tsx +172 -0
  38. package/src/components/SegmentedControl.tsx +1 -1
  39. package/src/components/SelectMenu.tsx +9 -4
  40. package/src/components/Slider.tsx +16 -7
  41. package/src/components/Sortable.tsx +186 -47
  42. package/src/components/UserPointer.tsx +1 -1
  43. package/src/components/ai-assistant/AIAssistantLayout.tsx +102 -0
  44. package/src/components/connected-users-menu/ConnectedUsersMenuLayout.tsx +71 -0
  45. package/src/components/file-explorer/FileExplorerLayout.tsx +71 -0
  46. package/src/components/internal/Menu.tsx +20 -10
  47. package/src/components/internal/SelectItem.tsx +3 -2
  48. package/src/components/pipeline/PipelineResultLayout.tsx +32 -0
  49. package/src/index.css +118 -112
  50. package/src/index.tsx +9 -0
  51. package/src/theme/index.ts +2 -0
  52. package/src/utils/classNames.ts +76 -3
  53. package/src/utils/inputs.ts +21 -0
  54. package/src/utils/moveTreeItem.ts +99 -0
  55. package/tailwind.config.ts +82 -75
@@ -82,6 +82,7 @@ export const Grid = memoGeneric(
82
82
  size = "medium",
83
83
  testHoveredId,
84
84
  testRenamingId,
85
+ testShowDropIndicatorId,
85
86
  scrollable = false,
86
87
  // acceptsDrop,
87
88
  // onMoveItem,
@@ -90,6 +91,7 @@ export const Grid = memoGeneric(
90
91
  onClickItem,
91
92
  onDoubleClickItem,
92
93
  renderEmptyState,
94
+ // getDropTargetParentIndex,
93
95
  } = props;
94
96
 
95
97
  const [hoveredId, setHoveredId] = useState<string>();
@@ -143,130 +145,146 @@ export const Grid = memoGeneric(
143
145
 
144
146
  return (
145
147
  <GridView.Root
148
+ role="listbox"
149
+ aria-orientation="horizontal"
150
+ aria-multiselectable={true}
146
151
  minColumnWidth={minColumnWidth}
147
- scrollable={scrollable}
152
+ scrollable={items.length > 0 && scrollable}
153
+ contentClassName={cx(items.length === 0 && "flex flex-1")}
148
154
  gap={gap}
149
155
  className={cx(isDropTargetActive && "bg-primary-pastel", className)}
150
156
  {...dropTargetProps}
151
157
  ref={fileDropTargetRef}
152
158
  >
153
- <GridView.Section renderEmptyState={renderEmptyState}>
154
- {items.map((item) => {
155
- const id = getId(item);
156
- const isDropdownOpen = dropdownOpenId === id;
157
- const isHovered =
158
- testHoveredId === id || currentHoveredId === id || isDropdownOpen;
159
- const itemIsRenamable = getRenamable?.(item) ?? renamable;
160
- const isSelected = selectedIds.includes(id);
161
- const isRenaming = itemIsRenamable && onRename && renamingId === id;
162
- const isTestingRenaming = testRenamingId === id;
163
- const thumbnail = renderThumbnail?.({
164
- item,
165
- selected: isSelected,
166
- });
167
- const action =
168
- (isHovered || isSelected) &&
169
- !isRenaming &&
170
- (typeof renderAction === "function"
171
- ? renderAction(item, isSelected)
172
- : renderAction === "menu" &&
173
- menuItems && (
174
- <ActionMenu
175
- menuItems={menuItems}
176
- onSelect={(action) => handleMenuAction(action, item)}
177
- selected={isSelected}
178
- onOpenChange={(open) => {
179
- setDropdownOpenId(open ? id : undefined);
159
+ {items.length > 0 ? (
160
+ <GridView.Section>
161
+ {items.map((item) => {
162
+ const id = getId(item);
163
+ const isDropdownOpen = dropdownOpenId === id;
164
+ const isHovered =
165
+ testHoveredId === id ||
166
+ currentHoveredId === id ||
167
+ isDropdownOpen;
168
+ const itemIsRenamable = getRenamable?.(item) ?? renamable;
169
+ const isSelected = selectedIds.includes(id);
170
+ const isRenaming =
171
+ itemIsRenamable && onRename && renamingId === id;
172
+ const isTestingRenaming = testRenamingId === id;
173
+ const thumbnail = renderThumbnail?.({
174
+ item,
175
+ selected: isSelected,
176
+ });
177
+ const onOpenChange = (open: boolean) => {
178
+ setDropdownOpenId(open ? id : undefined);
180
179
 
181
- if (open && !isSelected) {
182
- handleSelect(id);
183
- }
184
- }}
185
- />
186
- ));
180
+ if (open && !isSelected) {
181
+ handleSelect(id);
182
+ }
183
+ };
184
+ const action =
185
+ (isHovered || isSelected) &&
186
+ !isRenaming &&
187
+ (typeof renderAction === "function"
188
+ ? renderAction({ item, selected: isSelected, onOpenChange })
189
+ : renderAction === "menu" &&
190
+ menuItems && (
191
+ <ActionMenu
192
+ menuItems={menuItems}
193
+ onSelect={(action) => handleMenuAction(action, item)}
194
+ selected={isSelected}
195
+ onOpenChange={onOpenChange}
196
+ />
197
+ ));
187
198
 
188
- return (
189
- <GridView.Item<M>
190
- key={id}
191
- id={id}
192
- hovered={isHovered}
193
- onHoverChange={(isHovering) => {
194
- setHoveredId(isHovering ? id : undefined);
195
- }}
196
- title={
197
- <EditableText
198
- value={getName(item)}
199
- focused={isRenaming}
200
- testFocused={isTestingRenaming}
201
- readOnly={!itemIsRenamable || !onRename}
202
- onSubmit={(value) => {
203
- if (value !== getName(item)) {
199
+ return (
200
+ <GridView.Item<M>
201
+ key={id}
202
+ id={id}
203
+ role="option"
204
+ aria-selected={isSelected}
205
+ aria-label={getName(item)}
206
+ hovered={isHovered}
207
+ testShowInsideDropIndicator={testShowDropIndicatorId === id}
208
+ onHoverChange={(isHovering) => {
209
+ setHoveredId(isHovering ? id : undefined);
210
+ }}
211
+ title={
212
+ <EditableText
213
+ tabIndex={isRenaming ? undefined : -1}
214
+ value={getName(item)}
215
+ focused={isRenaming}
216
+ testFocused={isTestingRenaming}
217
+ readOnly={!itemIsRenamable || !onRename}
218
+ onSubmit={(value) => {
204
219
  onRename?.(item, value);
205
- }
206
- setRenamingId(undefined);
207
- }}
208
- onBlur={() => {
209
- setRenamingId(undefined);
210
- }}
211
- // max height ensures preview text and input are one line
212
- textClassName="bg-listview-editing-background max-h-[27px]"
213
- >
214
- {(props) => (
215
- <span
216
- {...props}
217
- className={cx(
218
- isSelected && "text-primary",
219
- "truncate select-none"
220
- )}
221
- />
222
- )}
223
- </EditableText>
224
- }
225
- subtitle={
226
- <span className={cx(isSelected && "text-primary")}>
227
- {renderDetail?.(item, isSelected)}
228
- </span>
229
- }
230
- loading={false}
231
- selected={isSelected}
232
- onClick={() => {
233
- onClickItem?.(id);
234
- }}
235
- onSelect={(event) => {
236
- handleSelect(id, event);
237
- }}
238
- onKeyDown={(event) => {
239
- if (event.key === "Enter") {
240
- setRenamingId(id);
220
+ setRenamingId(undefined);
221
+ }}
222
+ onBlur={() => {
223
+ setRenamingId(undefined);
224
+ }}
225
+ // max height ensures preview text and input are one line
226
+ textClassName="bg-listview-editing-background max-h-input-height"
227
+ >
228
+ {(props) => (
229
+ <span
230
+ {...props}
231
+ className={cx(
232
+ isSelected && "text-primary",
233
+ "truncate select-none"
234
+ )}
235
+ />
236
+ )}
237
+ </EditableText>
241
238
  }
242
- }}
243
- onDoubleClick={() => {
244
- onDoubleClickItem?.(id);
245
- }}
246
- menuItems={menuItems}
247
- onSelectMenuItem={(action) => handleMenuAction(action, item)}
248
- action={action}
249
- onMenuOpenChange={(open: boolean) => {
250
- if (open && !isSelected) {
251
- handleSelect(id);
239
+ subtitle={
240
+ <span className={cx(isSelected && "text-primary")}>
241
+ {renderDetail?.(item, isSelected)}
242
+ </span>
252
243
  }
253
- }}
254
- >
255
- {thumbnail && (
256
- <div
257
- className={cx(
258
- "rounded overflow-hidden flex-none flex w-full",
259
- isSelected && "text-primary"
260
- )}
261
- tabIndex={-1}
262
- >
263
- {thumbnail}
264
- </div>
265
- )}
266
- </GridView.Item>
267
- );
268
- })}
269
- </GridView.Section>
244
+ loading={false}
245
+ selected={isSelected}
246
+ onClick={() => {
247
+ onClickItem?.(id);
248
+ }}
249
+ onSelect={(event) => {
250
+ handleSelect(id, event);
251
+ }}
252
+ onKeyDown={(event) => {
253
+ if (event.key === "Enter") {
254
+ setRenamingId(id);
255
+ }
256
+ }}
257
+ onDoubleClick={() => {
258
+ onDoubleClickItem?.(id);
259
+ }}
260
+ menuItems={menuItems}
261
+ onSelectMenuItem={(action) => handleMenuAction(action, item)}
262
+ action={action}
263
+ onMenuOpenChange={(open: boolean) => {
264
+ if (open && !isSelected) {
265
+ handleSelect(id);
266
+ }
267
+ }}
268
+ // getDropTargetParentIndex={getDropTargetParentIndex}
269
+ >
270
+ {thumbnail && (
271
+ <div
272
+ className={cx(
273
+ "rounded overflow-hidden flex-none flex w-full",
274
+ isSelected && "text-primary"
275
+ )}
276
+ tabIndex={-1}
277
+ >
278
+ {thumbnail}
279
+ </div>
280
+ )}
281
+ </GridView.Item>
282
+ );
283
+ })}
284
+ </GridView.Section>
285
+ ) : (
286
+ renderEmptyState?.()
287
+ )}
270
288
  </GridView.Root>
271
289
  );
272
290
  })
@@ -12,7 +12,7 @@ import React, {
12
12
  useMemo,
13
13
  } from "react";
14
14
  import { useHover } from "../hooks/useHover";
15
- import { cx } from "../utils/classNames";
15
+ import { cx, mergeConflictingClassNames } from "../utils/classNames";
16
16
  import withSeparatorElements from "../utils/withSeparatorElements";
17
17
  import { ActivityIndicator } from "./ActivityIndicator";
18
18
  import { ContextMenu } from "./ContextMenu";
@@ -96,6 +96,8 @@ interface ItemProps<MenuItemType extends string = string> {
96
96
  action?: ReactNode;
97
97
  onKeyDown?: (event: React.KeyboardEvent) => void;
98
98
  hovered?: boolean;
99
+ testShowInsideDropIndicator?: boolean;
100
+ role?: string;
99
101
  }
100
102
 
101
103
  const GridViewItem = forwardRefGeneric(function GridViewItem<
@@ -120,6 +122,8 @@ const GridViewItem = forwardRefGeneric(function GridViewItem<
120
122
  action,
121
123
  onKeyDown,
122
124
  hovered = false,
125
+ testShowInsideDropIndicator: showInsideDropIndicator = false,
126
+ ...props
123
127
  }: ItemProps<MenuItemType>,
124
128
  forwardedRef: ForwardedRef<HTMLDivElement>
125
129
  ) {
@@ -157,13 +161,16 @@ const GridViewItem = forwardRefGeneric(function GridViewItem<
157
161
 
158
162
  let element = (
159
163
  <div
164
+ {...props}
160
165
  className={cx(
161
- "flex flex-col relative focus:shadow-[0px_0px_0px_1px_var(--sidebar-background),_0px_0px_0px_3px_var(--primary)] focus:outline-none rounded p-2 -m-2",
166
+ "flex flex-col relative rounded p-2 -m-2 text-text",
162
167
  "cursor-pointer",
163
168
  "active:opacity-70",
164
169
  selected && "bg-primary-pastel",
170
+ hovered && "bg-list-view-hover-background",
165
171
  disabled && "opacity-50",
166
- hovered && "ring-1 ring-primary ring-inset opacity-85"
172
+ showInsideDropIndicator && "ring-2 ring-primary ring-inset",
173
+ "focus:ring-2 focus:ring-primary focus:ring-inset focus:outline-none"
167
174
  )}
168
175
  id={id}
169
176
  ref={forwardedRef}
@@ -177,8 +184,7 @@ const GridViewItem = forwardRefGeneric(function GridViewItem<
177
184
  <div
178
185
  className={cx(
179
186
  "flex items-center justify-center rounded",
180
- bordered ? "border border-divider" : "border border-transparent",
181
- selected ? "bg-primary-pastel" : "bg-input-background text-text"
187
+ bordered ? "border border-divider" : "border border-transparent"
182
188
  )}
183
189
  style={style}
184
190
  >
@@ -267,6 +273,9 @@ interface GridViewRootProps extends Partial<GridViewContextValue> {
267
273
  onDragEnter?: React.DragEventHandler<HTMLDivElement>;
268
274
  onDragLeave?: React.DragEventHandler<HTMLDivElement>;
269
275
  onDrop?: React.DragEventHandler<HTMLDivElement>;
276
+ role?: string;
277
+ renderEmptyState?: () => ReactNode;
278
+ contentClassName?: string;
270
279
  }
271
280
 
272
281
  const GridViewRoot = forwardRef(function GridViewRoot(
@@ -280,6 +289,7 @@ const GridViewRoot = forwardRef(function GridViewRoot(
280
289
  bordered = false,
281
290
  disabled = false,
282
291
  className,
292
+ contentClassName,
283
293
  ...props
284
294
  }: GridViewRootProps,
285
295
  forwardedRef: ForwardedRef<HTMLDivElement>
@@ -305,25 +315,29 @@ const GridViewRoot = forwardRef(function GridViewRoot(
305
315
  [bordered, disabled, gap, minColumnWidth, textPosition]
306
316
  );
307
317
 
318
+ const mergedClassName = useMemo(() => {
319
+ const baseClassName = cx(
320
+ "flex flex-col -mx-3",
321
+ scrollable ? "basis-0 min-h-0" : "flex-none",
322
+ className
323
+ );
324
+
325
+ return mergeConflictingClassNames([baseClassName, className], {
326
+ categories: ["flex"],
327
+ });
328
+ }, [className, scrollable]);
329
+
330
+ const content = <div className={cx("p-3", contentClassName)}>{children}</div>;
331
+
308
332
  return (
309
333
  <GridViewContext.Provider value={contextValue}>
310
334
  <div
311
335
  onClick={handleClick}
312
- className={cx(
313
- "flex flex-col -mx-3 -mb-1",
314
- scrollable ? "basis-0 min-h-0" : "flex-none",
315
- className
316
- )}
336
+ className={mergedClassName}
317
337
  {...props}
318
338
  ref={forwardedRef}
319
339
  >
320
- {scrollable ? (
321
- <ScrollArea>
322
- <div className="p-3">{children}</div>
323
- </ScrollArea>
324
- ) : (
325
- <div className="p-3">{children}</div>
326
- )}
340
+ {scrollable ? <ScrollArea>{content}</ScrollArea> : content}
327
341
  </div>
328
342
  </GridViewContext.Provider>
329
343
  );
@@ -350,7 +364,11 @@ function GridViewSection({
350
364
  return isEmpty && renderEmptyState ? (
351
365
  renderEmptyState()
352
366
  ) : (
353
- <div className={cx("grid text-text", className)} style={styles}>
367
+ <div
368
+ role="presentation"
369
+ className={cx("grid text-text", className)}
370
+ style={styles}
371
+ >
354
372
  {children}
355
373
  </div>
356
374
  );