@rovula/ui 0.1.27 → 0.1.29

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 (77) hide show
  1. package/dist/cjs/bundle.css +513 -67
  2. package/dist/cjs/bundle.js +589 -589
  3. package/dist/cjs/bundle.js.map +1 -1
  4. package/dist/cjs/types/components/Avatar/Avatar.d.ts +1 -1
  5. package/dist/cjs/types/components/Avatar/Avatar.stories.d.ts +1 -1
  6. package/dist/cjs/types/components/Avatar/Avatar.styles.d.ts +1 -0
  7. package/dist/cjs/types/components/DataTable/DataTable.d.ts +195 -4
  8. package/dist/cjs/types/components/DataTable/DataTable.editing.d.ts +20 -0
  9. package/dist/cjs/types/components/DataTable/DataTable.editing.types.d.ts +145 -0
  10. package/dist/cjs/types/components/DataTable/DataTable.stories.d.ts +268 -6
  11. package/dist/cjs/types/components/Dropdown/Dropdown.d.ts +22 -0
  12. package/dist/cjs/types/components/Dropdown/Dropdown.stories.d.ts +4 -0
  13. package/dist/cjs/types/components/ScrollArea/ScrollArea.d.ts +3 -3
  14. package/dist/cjs/types/components/ScrollArea/ScrollArea.stories.d.ts +4 -0
  15. package/dist/cjs/types/components/Table/Table.d.ts +33 -3
  16. package/dist/cjs/types/components/Table/Table.stories.d.ts +86 -4
  17. package/dist/cjs/types/components/TextInput/TextInput.stories.d.ts +8 -0
  18. package/dist/cjs/types/components/TextInput/TextInput.styles.d.ts +1 -0
  19. package/dist/components/Avatar/Avatar.js +2 -1
  20. package/dist/components/Avatar/Avatar.styles.js +3 -0
  21. package/dist/components/Avatar/AvatarBase.js +1 -1
  22. package/dist/components/DataTable/DataTable.editing.js +385 -0
  23. package/dist/components/DataTable/DataTable.editing.types.js +1 -0
  24. package/dist/components/DataTable/DataTable.js +983 -50
  25. package/dist/components/DataTable/DataTable.stories.js +1077 -25
  26. package/dist/components/Dropdown/Dropdown.js +8 -6
  27. package/dist/components/ScrollArea/ScrollArea.js +2 -2
  28. package/dist/components/ScrollArea/ScrollArea.stories.js +68 -2
  29. package/dist/components/Table/Table.js +103 -13
  30. package/dist/components/Table/Table.stories.js +226 -9
  31. package/dist/components/TextInput/TextInput.js +6 -4
  32. package/dist/components/TextInput/TextInput.stories.js +8 -0
  33. package/dist/components/TextInput/TextInput.styles.js +7 -1
  34. package/dist/esm/bundle.css +513 -67
  35. package/dist/esm/bundle.js +1545 -1545
  36. package/dist/esm/bundle.js.map +1 -1
  37. package/dist/esm/types/components/Avatar/Avatar.d.ts +1 -1
  38. package/dist/esm/types/components/Avatar/Avatar.stories.d.ts +1 -1
  39. package/dist/esm/types/components/Avatar/Avatar.styles.d.ts +1 -0
  40. package/dist/esm/types/components/DataTable/DataTable.d.ts +195 -4
  41. package/dist/esm/types/components/DataTable/DataTable.editing.d.ts +20 -0
  42. package/dist/esm/types/components/DataTable/DataTable.editing.types.d.ts +145 -0
  43. package/dist/esm/types/components/DataTable/DataTable.stories.d.ts +268 -6
  44. package/dist/esm/types/components/Dropdown/Dropdown.d.ts +22 -0
  45. package/dist/esm/types/components/Dropdown/Dropdown.stories.d.ts +4 -0
  46. package/dist/esm/types/components/ScrollArea/ScrollArea.d.ts +3 -3
  47. package/dist/esm/types/components/ScrollArea/ScrollArea.stories.d.ts +4 -0
  48. package/dist/esm/types/components/Table/Table.d.ts +33 -3
  49. package/dist/esm/types/components/Table/Table.stories.d.ts +86 -4
  50. package/dist/esm/types/components/TextInput/TextInput.stories.d.ts +8 -0
  51. package/dist/esm/types/components/TextInput/TextInput.styles.d.ts +1 -0
  52. package/dist/index.d.ts +493 -122
  53. package/dist/src/theme/global.css +762 -96
  54. package/package.json +14 -2
  55. package/src/components/Avatar/Avatar.styles.ts +4 -1
  56. package/src/components/Avatar/Avatar.tsx +3 -2
  57. package/src/components/Avatar/AvatarBase.tsx +3 -3
  58. package/src/components/DataTable/DataTable.editing.tsx +861 -0
  59. package/src/components/DataTable/DataTable.editing.types.ts +192 -0
  60. package/src/components/DataTable/DataTable.stories.tsx +2169 -31
  61. package/src/components/DataTable/DataTable.test.tsx +696 -0
  62. package/src/components/DataTable/DataTable.tsx +2260 -94
  63. package/src/components/Dropdown/Dropdown.tsx +22 -6
  64. package/src/components/ScrollArea/ScrollArea.stories.tsx +146 -3
  65. package/src/components/ScrollArea/ScrollArea.tsx +6 -6
  66. package/src/components/Table/Table.stories.tsx +789 -44
  67. package/src/components/Table/Table.tsx +294 -28
  68. package/src/components/TextInput/TextInput.stories.tsx +80 -0
  69. package/src/components/TextInput/TextInput.styles.ts +7 -1
  70. package/src/components/TextInput/TextInput.tsx +21 -14
  71. package/src/test/setup.ts +50 -0
  72. package/src/theme/global.css +81 -42
  73. package/src/theme/presets/colors.js +12 -0
  74. package/src/theme/themes/variable.css +27 -28
  75. package/src/theme/tokens/baseline.css +2 -1
  76. package/src/theme/tokens/components/scrollbar.css +9 -4
  77. package/src/theme/tokens/components/table.css +63 -0
@@ -79,6 +79,17 @@ export type DropdownProps = {
79
79
  selectedOption: Options | null | undefined;
80
80
  onClick: (option: Options) => void;
81
81
  }) => ReactNode;
82
+ /**
83
+ * When `true`, keep focus on the input after selecting an option so the
84
+ * menu stays open (useful for inline row-editing where the user may Tab
85
+ * to the next field instead of closing the dropdown).
86
+ */
87
+ keepOpenAfterSelect?: boolean;
88
+ /**
89
+ * Render the options list via a React portal so it escapes containers
90
+ * with `overflow: hidden/auto` (e.g. DataTable scroll wrappers).
91
+ */
92
+ portal?: boolean;
82
93
  } & Omit<InputProps, "value" | "onSelect">;
83
94
 
84
95
  // ---------------------------------------------------------------------------
@@ -110,6 +121,8 @@ const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
110
121
  optionItemClassName,
111
122
  optionNotFoundItemClassName,
112
123
  segmentedInput = true,
124
+ keepOpenAfterSelect = false,
125
+ portal = false,
113
126
  ...props
114
127
  },
115
128
  ref,
@@ -142,12 +155,12 @@ const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
142
155
  setQuery("");
143
156
  if (option) {
144
157
  onSelect?.(option);
145
- // After selection Headless UI keeps focus on the input (via rAF refocus).
146
- // Blur after that rAF so the next click triggers onFocus → immediate open.
147
- setTimeout(() => inputRef.current?.blur(), 0);
158
+ if (!keepOpenAfterSelect) {
159
+ setTimeout(() => inputRef.current?.blur(), 0);
160
+ }
148
161
  }
149
162
  },
150
- [onSelect],
163
+ [onSelect, keepOpenAfterSelect],
151
164
  );
152
165
 
153
166
  const handleInputChange = useCallback(
@@ -259,7 +272,6 @@ const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
259
272
  />
260
273
  }
261
274
  label={label}
262
- placeholder=" "
263
275
  autoComplete="off"
264
276
  rounded={rounded}
265
277
  variant={variant}
@@ -288,8 +300,12 @@ const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
288
300
  * z-[51] beats the Dialog overlay (z-50) for items that visually overflow.
289
301
  */}
290
302
  <ComboboxOptions
303
+ {...(portal
304
+ ? { portal: true, anchor: { to: "bottom start" as const, gap: 4 } }
305
+ : {})}
291
306
  className={cn(
292
- "absolute top-full left-0 w-full -mt-3 z-[51]",
307
+ !portal && "absolute top-full left-0 w-full -mt-3 z-[51]",
308
+ portal && "z-[51] !max-h-60 w-[var(--input-width)]",
293
309
  "min-w-[154px] max-h-60 overflow-y-auto",
294
310
  "rounded-md bg-modal-dropdown-surface border border-bg-stroke3 text-text-g-contrast-high",
295
311
  optionContainerClassName,
@@ -10,7 +10,7 @@ const meta = {
10
10
  },
11
11
  decorators: [
12
12
  (Story) => (
13
- <div className="p-10 flex gap-8 flex-wrap bg-workspace-surface min-h-screen">
13
+ <div className="p-10 flex gap-8 flex-wrap bg-page-bg-main min-h-screen">
14
14
  <Story />
15
15
  </div>
16
16
  ),
@@ -19,7 +19,10 @@ const meta = {
19
19
 
20
20
  export default meta;
21
21
 
22
- const ITEMS = Array.from({ length: 12 }, (_, i) => `Option Description ${i + 1}`);
22
+ const ITEMS = Array.from(
23
+ { length: 12 },
24
+ (_, i) => `Option Description ${i + 1}`,
25
+ );
23
26
 
24
27
  // ---------------------------------------------------------------------------
25
28
  // Size variants (Figma: Size=M / S / XS)
@@ -32,6 +35,7 @@ export const Sizes = {
32
35
  <div key={size}>
33
36
  <p className="typography-small4 text-text-g-contrast-medium mb-2 uppercase">
34
37
  Size {size}
38
+ {size === "m" ? " (default)" : ""}
35
39
  </p>
36
40
  <ScrollArea
37
41
  scrollbarSize={size}
@@ -61,7 +65,9 @@ export const VerticalScroll = {
61
65
  <div>
62
66
  <p className="typography-small4 text-text-g-contrast-medium mb-2">
63
67
  Single scrollable list — use{" "}
64
- <code className="text-xs">&lt;ScrollArea className="max-h-[...]"&gt;</code>
68
+ <code className="text-xs">
69
+ &lt;ScrollArea className="max-h-[...]"&gt;
70
+ </code>
65
71
  </p>
66
72
  <ScrollArea className="max-h-[270px] w-[230px] rounded-lg bg-modal-surface">
67
73
  {ITEMS.map((label) => (
@@ -227,3 +233,140 @@ export const WithDropdownMenu = {
227
233
  );
228
234
  },
229
235
  } satisfies StoryObj;
236
+
237
+ // ---------------------------------------------------------------------------
238
+ // Both axes — vertical + horizontal scroll simultaneously
239
+ // ---------------------------------------------------------------------------
240
+
241
+ const WIDE_COLS = [
242
+ "ID",
243
+ "Name",
244
+ "Category",
245
+ "Type",
246
+ "Format",
247
+ "Source",
248
+ "Default",
249
+ "Required",
250
+ "Nullable",
251
+ "Description",
252
+ ];
253
+ const WIDE_ROWS = Array.from({ length: 30 }, (_, i) => ({
254
+ id: `ROW-${String(i + 1).padStart(3, "0")}`,
255
+ name: `item_name_${i + 1}`,
256
+ category: ["Alpha", "Beta", "Gamma", "Delta", "Epsilon"][i % 5],
257
+ type: ["String", "Number", "Boolean", "Date", "Enum"][i % 5],
258
+ format: ["Text", "Integer", "true/false", "ISO 8601", "1–5"][i % 5],
259
+ source: ["System", "Manual", "Device", "Auth", "Import"][i % 5],
260
+ defaultVal: ["-", "auto", "true", "now()", "0"][i % 5],
261
+ required: i % 3 === 0 ? "Yes" : "No",
262
+ nullable: i % 2 === 0 ? "Yes" : "No",
263
+ description: `Description text for row ${i + 1}`,
264
+ }));
265
+
266
+ const WideContent = () => (
267
+ <div className="min-w-[800px]">
268
+ <div className="flex sticky top-0 z-10 bg-modal-surface border-b border-[var(--dropdown-menu-seperator-bg)]">
269
+ {WIDE_COLS.map((col) => (
270
+ <div
271
+ key={col}
272
+ className="shrink-0 w-[120px] px-3 py-2 typography-small2 text-text-g-contrast-medium whitespace-nowrap"
273
+ >
274
+ {col}
275
+ </div>
276
+ ))}
277
+ </div>
278
+ {WIDE_ROWS.map((row) => (
279
+ <div
280
+ key={row.id}
281
+ className="flex border-b border-[var(--dropdown-menu-seperator-bg)] hover:bg-[var(--transparent-grey2-8,rgba(145,158,171,0.08))] transition-colors"
282
+ >
283
+ <div className="shrink-0 w-[120px] px-3 py-2 typography-subtitle4 text-text-g-contrast-high whitespace-nowrap font-medium">
284
+ {row.id}
285
+ </div>
286
+ <div className="shrink-0 w-[120px] px-3 py-2 typography-subtitle4 text-text-g-contrast-high whitespace-nowrap">
287
+ {row.name}
288
+ </div>
289
+ <div className="shrink-0 w-[120px] px-3 py-2 typography-subtitle4 text-text-g-contrast-high whitespace-nowrap">
290
+ {row.category}
291
+ </div>
292
+ <div className="shrink-0 w-[120px] px-3 py-2 typography-subtitle4 text-text-g-contrast-high whitespace-nowrap">
293
+ {row.type}
294
+ </div>
295
+ <div className="shrink-0 w-[120px] px-3 py-2 typography-subtitle4 text-text-g-contrast-high whitespace-nowrap">
296
+ {row.format}
297
+ </div>
298
+ <div className="shrink-0 w-[120px] px-3 py-2 typography-subtitle4 text-text-g-contrast-high whitespace-nowrap">
299
+ {row.source}
300
+ </div>
301
+ <div className="shrink-0 w-[120px] px-3 py-2 typography-subtitle4 text-text-g-contrast-high whitespace-nowrap">
302
+ {row.defaultVal}
303
+ </div>
304
+ <div className="shrink-0 w-[120px] px-3 py-2 typography-subtitle4 text-text-g-contrast-high whitespace-nowrap">
305
+ {row.required}
306
+ </div>
307
+ <div className="shrink-0 w-[120px] px-3 py-2 typography-subtitle4 text-text-g-contrast-high whitespace-nowrap">
308
+ {row.nullable}
309
+ </div>
310
+ <div className="shrink-0 w-[120px] px-3 py-2 typography-subtitle4 text-text-g-contrast-high whitespace-nowrap">
311
+ {row.description}
312
+ </div>
313
+ </div>
314
+ ))}
315
+ </div>
316
+ );
317
+
318
+ export const BothAxes = {
319
+ name: "Both Axes",
320
+ render: () => {
321
+ const cases: { label: string; xSize: string; ySize: string }[] = [
322
+ {
323
+ label: "X = M (16px) + Y = M (16px) — same",
324
+ xSize: "ui-scrollbar-x-m",
325
+ ySize: "ui-scrollbar-y-m",
326
+ },
327
+ {
328
+ label: "X = S (10px) + Y = S (10px) — same",
329
+ xSize: "ui-scrollbar-x-s",
330
+ ySize: "ui-scrollbar-y-s",
331
+ },
332
+ {
333
+ label: "X = XS (4px) + Y = XS (4px) — same",
334
+ xSize: "ui-scrollbar-x-xs",
335
+ ySize: "ui-scrollbar-y-xs",
336
+ },
337
+ {
338
+ label: "X = M (16px) + Y = S (10px) — mixed",
339
+ xSize: "ui-scrollbar-x-m",
340
+ ySize: "ui-scrollbar-y-s",
341
+ },
342
+ {
343
+ label: "X = S (10px) + Y = M (16px) — mixed",
344
+ xSize: "ui-scrollbar-x-s",
345
+ ySize: "ui-scrollbar-y-m",
346
+ },
347
+ {
348
+ label: "X = M (16px) + Y = XS (4px) — mixed",
349
+ xSize: "ui-scrollbar-x-m",
350
+ ySize: "ui-scrollbar-y-xs",
351
+ },
352
+ ];
353
+
354
+ return (
355
+ <div className="flex flex-col gap-8 w-full">
356
+ {cases.map(({ label, xSize, ySize }) => (
357
+ <div key={label} className="flex flex-col gap-2">
358
+ <p className="typography-subtitle4 text-text-g-contrast-high">
359
+ {label}
360
+ </p>
361
+ <ScrollArea
362
+ direction="both"
363
+ className={`h-[220px] w-[480px] rounded-lg bg-modal-surface ${xSize} ${ySize}`}
364
+ >
365
+ <WideContent />
366
+ </ScrollArea>
367
+ </div>
368
+ ))}
369
+ </div>
370
+ );
371
+ },
372
+ } satisfies StoryObj;
@@ -6,9 +6,9 @@ export type ScrollbarSize = "m" | "s" | "xs";
6
6
  export interface ScrollAreaProps extends React.HTMLAttributes<HTMLDivElement> {
7
7
  /**
8
8
  * Scrollbar thickness.
9
- * - `m` — 12 px (shows track border)
10
- * - `s` — 6 px (default, shows no track border)
11
- * - `xs` — 2 px (shows no track border)
9
+ * - `m` — 12 px (default, shows track border + 2px thumb inset)
10
+ * - `s` — 6 px (shows track border + 2px thumb inset)
11
+ * - `xs` — 2 px (no track border, no thumb inset)
12
12
  */
13
13
  scrollbarSize?: ScrollbarSize;
14
14
  /**
@@ -19,8 +19,8 @@ export interface ScrollAreaProps extends React.HTMLAttributes<HTMLDivElement> {
19
19
  }
20
20
 
21
21
  const sizeClass: Record<ScrollbarSize, string> = {
22
- m: "ui-scrollbar ui-scrollbar-m",
23
- s: "ui-scrollbar ui-scrollbar-s",
22
+ m: "ui-scrollbar", // default = M, no extra class needed
23
+ s: "ui-scrollbar ui-scrollbar-s",
24
24
  xs: "ui-scrollbar ui-scrollbar-xs",
25
25
  };
26
26
 
@@ -53,7 +53,7 @@ const directionClass: Record<NonNullable<ScrollAreaProps["direction"]>, string>
53
53
  */
54
54
  export const ScrollArea = React.forwardRef<HTMLDivElement, ScrollAreaProps>(
55
55
  (
56
- { className, scrollbarSize = "s", direction = "vertical", children, ...props },
56
+ { className, scrollbarSize = "m", direction = "vertical", children, ...props },
57
57
  ref
58
58
  ) => (
59
59
  <div