@rebasepro/ui 0.6.0 → 0.7.0

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 (72) hide show
  1. package/dist/icons/index.d.ts +1 -1
  2. package/dist/index.css +13 -13
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.es.js +2119 -74
  5. package/dist/index.es.js.map +1 -1
  6. package/dist/src/index.css +13 -13
  7. package/dist/styles.d.ts +9 -9
  8. package/dist/views/CardView.d.ts +32 -0
  9. package/dist/views/CollectionView/CollectionCardView.d.ts +30 -0
  10. package/dist/views/CollectionView/CollectionKanbanView.d.ts +31 -0
  11. package/dist/views/CollectionView/CollectionListView.d.ts +30 -0
  12. package/dist/views/CollectionView/CollectionTableView.d.ts +37 -0
  13. package/dist/views/CollectionView/CollectionView.d.ts +121 -0
  14. package/dist/views/CollectionView/CollectionViewToolbar.d.ts +31 -0
  15. package/dist/views/CollectionView/CollectionViewTypes.d.ts +106 -0
  16. package/dist/views/CollectionView/DefaultCellRenderer.d.ts +9 -0
  17. package/dist/views/CollectionView/index.d.ts +24 -0
  18. package/dist/views/CollectionView/utils.d.ts +5 -0
  19. package/dist/views/Kanban/Board.d.ts +3 -0
  20. package/dist/views/Kanban/BoardColumn.d.ts +20 -0
  21. package/dist/views/Kanban/BoardColumnTitle.d.ts +8 -0
  22. package/dist/views/Kanban/BoardSortableList.d.ts +14 -0
  23. package/dist/views/Kanban/board_types.d.ts +61 -0
  24. package/dist/views/Kanban/index.d.ts +5 -0
  25. package/dist/views/KanbanView.d.ts +24 -0
  26. package/dist/views/ListView.d.ts +40 -0
  27. package/dist/views/TableView.d.ts +6 -0
  28. package/dist/views/index.d.ts +5 -0
  29. package/package.json +1 -1
  30. package/src/components/Alert.tsx +1 -1
  31. package/src/components/BooleanSwitchWithLabel.tsx +1 -1
  32. package/src/components/Button.tsx +5 -5
  33. package/src/components/Chip.tsx +39 -37
  34. package/src/components/DateTimeField.tsx +2 -2
  35. package/src/components/Dialog.tsx +1 -1
  36. package/src/components/ExpandablePanel.tsx +1 -1
  37. package/src/components/FileUpload.tsx +1 -1
  38. package/src/components/FilterChip.tsx +2 -2
  39. package/src/components/IconButton.tsx +1 -1
  40. package/src/components/MultiSelect.tsx +1 -1
  41. package/src/components/Select.tsx +2 -2
  42. package/src/components/Sheet.tsx +8 -4
  43. package/src/components/Table.tsx +2 -2
  44. package/src/components/TextField.tsx +3 -3
  45. package/src/components/VirtualTable/VirtualTableRow.tsx +1 -1
  46. package/src/icons/index.ts +3 -0
  47. package/src/index.css +13 -13
  48. package/src/index.ts +2 -0
  49. package/src/styles.ts +9 -9
  50. package/src/views/CardView.tsx +281 -0
  51. package/src/views/CollectionView/CollectionCardView.tsx +270 -0
  52. package/src/views/CollectionView/CollectionKanbanView.tsx +251 -0
  53. package/src/views/CollectionView/CollectionListView.tsx +245 -0
  54. package/src/views/CollectionView/CollectionTableView.tsx +229 -0
  55. package/src/views/CollectionView/CollectionView.tsx +388 -0
  56. package/src/views/CollectionView/CollectionViewToolbar.tsx +229 -0
  57. package/src/views/CollectionView/CollectionViewTypes.ts +137 -0
  58. package/src/views/CollectionView/DefaultCellRenderer.tsx +404 -0
  59. package/src/views/CollectionView/index.ts +44 -0
  60. package/src/views/CollectionView/utils.ts +14 -0
  61. package/src/views/Kanban/Board.tsx +421 -0
  62. package/src/views/Kanban/BoardColumn.tsx +135 -0
  63. package/src/views/Kanban/BoardColumnTitle.tsx +47 -0
  64. package/src/views/Kanban/BoardSortableList.tsx +170 -0
  65. package/src/views/Kanban/board_types.ts +70 -0
  66. package/src/views/Kanban/index.tsx +5 -0
  67. package/src/views/KanbanView.tsx +32 -0
  68. package/src/views/ListView.tsx +281 -0
  69. package/src/views/TableView.tsx +7 -0
  70. package/src/views/index.ts +5 -0
  71. package/dist/index.umd.js +0 -6958
  72. package/dist/index.umd.js.map +0 -1
@@ -0,0 +1,404 @@
1
+ import React from "react";
2
+ import { ImageIcon } from "lucide-react";
3
+ import { cls } from "../../util";
4
+ import { Typography } from "../../components/Typography";
5
+ import { Chip } from "../../components/Chip";
6
+ import { BooleanSwitch } from "../../components/BooleanSwitch";
7
+ import { Markdown } from "../../components/Markdown";
8
+ import { Tooltip } from "../../components/Tooltip";
9
+ import { iconSize } from "../../icons";
10
+ import type { CellRendererProps, CollectionPropertyConfig, CollectionEnumValueConfig } from "./CollectionViewTypes";
11
+
12
+ // ——— Helpers ———
13
+
14
+ /**
15
+ * Resolve an enum key to its display label and optional color.
16
+ */
17
+ function resolveEnumLabel(
18
+ enumValues: CollectionPropertyConfig["enum"],
19
+ value: unknown
20
+ ): { label: string; color?: string } | undefined {
21
+ if (!enumValues) return undefined;
22
+ const key = String(value);
23
+ if (enumValues instanceof Map) {
24
+ const config: CollectionEnumValueConfig | undefined = enumValues.get(value as string | number);
25
+ if (!config) return undefined;
26
+ if (typeof config === "string") return { label: config };
27
+ return { label: config.label, color: config.color };
28
+ }
29
+ const config: CollectionEnumValueConfig | undefined = enumValues[key];
30
+ if (!config) return undefined;
31
+ if (typeof config === "string") return { label: config };
32
+ return { label: config.label, color: config.color };
33
+ }
34
+
35
+ /**
36
+ * Format a date value using Intl.DateTimeFormat.
37
+ */
38
+ function formatDate(value: unknown, includeTime: boolean): string {
39
+ if (value == null) return "—";
40
+ const date = value instanceof Date ? value : new Date(String(value));
41
+ if (isNaN(date.getTime())) return String(value);
42
+
43
+ const options: Intl.DateTimeFormatOptions = includeTime
44
+ ? { year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" }
45
+ : { year: "numeric", month: "short", day: "numeric" };
46
+
47
+ return new Intl.DateTimeFormat(undefined, options).format(date);
48
+ }
49
+
50
+ /**
51
+ * Type guard for geopoint-like objects.
52
+ */
53
+ function isGeoPoint(value: unknown): value is { lat: number; lng: number } {
54
+ if (typeof value !== "object" || value === null) return false;
55
+ const obj = value as Record<string, unknown>;
56
+ return typeof obj.lat === "number" && typeof obj.lng === "number";
57
+ }
58
+
59
+ // ——— Component ———
60
+
61
+ /**
62
+ * Default cell renderer for the headless CollectionView.
63
+ * Handles all built-in property types without depending on any Rebase core types.
64
+ *
65
+ * @group Preview components
66
+ */
67
+ export function DefaultCellRenderer({
68
+ row,
69
+ propertyKey,
70
+ property,
71
+ value,
72
+ size,
73
+ width,
74
+ height
75
+ }: CellRendererProps) {
76
+
77
+ // 1. Null / undefined → em-dash
78
+ if (value === null || value === undefined) {
79
+ return (
80
+ <Typography variant="body2"
81
+ color="secondary"
82
+ className="px-1">
83
+
84
+ </Typography>
85
+ );
86
+ }
87
+
88
+ // 2. Custom Preview component override
89
+ if (property.Preview) {
90
+ const PreviewComponent = property.Preview;
91
+ return (
92
+ <PreviewComponent
93
+ row={row}
94
+ propertyKey={propertyKey}
95
+ property={property}
96
+ value={value}
97
+ size={size}
98
+ width={width}
99
+ height={height}
100
+ />
101
+ );
102
+ }
103
+
104
+ // ——— Type-specific renderers ———
105
+
106
+ switch (property.type) {
107
+ case "string":
108
+ return renderString(property, value);
109
+
110
+ case "number":
111
+ return renderNumber(property, value);
112
+
113
+ case "boolean":
114
+ return renderBoolean(value);
115
+
116
+ case "date":
117
+ return renderDate(property, value);
118
+
119
+ case "array":
120
+ return renderArray(property, value);
121
+
122
+ case "map":
123
+ return renderMap(value);
124
+
125
+ case "reference":
126
+ case "relation":
127
+ return renderReference(value);
128
+
129
+ case "geopoint":
130
+ return renderGeopoint(value);
131
+
132
+ default:
133
+ return (
134
+ <Typography variant="body2"
135
+ color="secondary"
136
+ noWrap
137
+ className="px-1">
138
+ {JSON.stringify(value)}
139
+ </Typography>
140
+ );
141
+ }
142
+ }
143
+
144
+ // ——— Per-type render functions ———
145
+
146
+ function renderString(property: CollectionPropertyConfig, value: unknown): React.ReactElement {
147
+ const strValue = String(value);
148
+
149
+ // Enum → Chip
150
+ if (property.enum) {
151
+ const resolved = resolveEnumLabel(property.enum, value);
152
+ return (
153
+ <Chip size="small"
154
+ colorScheme={resolved?.color ? { color: resolved.color, text: "#fff" } : undefined}>
155
+ {resolved?.label ?? strValue}
156
+ </Chip>
157
+ );
158
+ }
159
+
160
+ // Preview as tag → Chip
161
+ if (property.previewAsTag) {
162
+ return (
163
+ <Chip size="small">
164
+ {strValue}
165
+ </Chip>
166
+ );
167
+ }
168
+
169
+ // URL → clickable link
170
+ if (property.url) {
171
+ const href = typeof property.url === "string" ? property.url : strValue;
172
+ return (
173
+ <Typography variant="body2"
174
+ noWrap
175
+ component="a"
176
+ href={href}
177
+ target="_blank"
178
+ rel="noopener noreferrer"
179
+ className={cls(
180
+ "px-1 text-primary hover:underline cursor-pointer"
181
+ )}>
182
+ {strValue}
183
+ </Typography>
184
+ );
185
+ }
186
+
187
+ // Email → mailto link
188
+ if (property.email) {
189
+ return (
190
+ <Typography variant="body2"
191
+ noWrap
192
+ component="a"
193
+ href={`mailto:${strValue}`}
194
+ className={cls(
195
+ "px-1 text-primary hover:underline cursor-pointer"
196
+ )}>
197
+ {strValue}
198
+ </Typography>
199
+ );
200
+ }
201
+
202
+ // Markdown → rendered markdown
203
+ if (property.markdown) {
204
+ return (
205
+ <div className="px-1 max-h-[100px] overflow-hidden">
206
+ <Markdown source={strValue} size="small" />
207
+ </div>
208
+ );
209
+ }
210
+
211
+ // Storage → image preview with fallback
212
+ if (property.storage) {
213
+ return (
214
+ <div className={cls(
215
+ "px-1 flex items-center justify-center",
216
+ "w-[40px] h-[40px] rounded overflow-hidden"
217
+ )}>
218
+ {strValue ? (
219
+ <img
220
+ src={strValue}
221
+ alt=""
222
+ className="w-full h-full object-cover"
223
+ onError={(e) => {
224
+ const target = e.currentTarget;
225
+ target.style.display = "none";
226
+ const fallback = target.nextElementSibling;
227
+ if (fallback instanceof HTMLElement) {
228
+ fallback.style.display = "flex";
229
+ }
230
+ }}
231
+ />
232
+ ) : null}
233
+ <div className={cls(
234
+ "w-full h-full items-center justify-center rounded",
235
+ "bg-surface-100 dark:bg-surface-800 text-text-secondary dark:text-text-secondary-dark",
236
+ strValue ? "hidden" : "flex"
237
+ )}>
238
+ <ImageIcon size={iconSize.small} />
239
+ </div>
240
+ </div>
241
+ );
242
+ }
243
+
244
+ // Default string → truncated text
245
+ return (
246
+ <Tooltip title={strValue.length > 50 ? strValue : undefined}
247
+ side="bottom">
248
+ <Typography variant="body2"
249
+ noWrap
250
+ className="px-1">
251
+ {strValue}
252
+ </Typography>
253
+ </Tooltip>
254
+ );
255
+ }
256
+
257
+ function renderNumber(property: CollectionPropertyConfig, value: unknown): React.ReactElement {
258
+ const numStr = String(value);
259
+
260
+ // Enum → Chip
261
+ if (property.enum) {
262
+ const resolved = resolveEnumLabel(property.enum, value);
263
+ return (
264
+ <Chip size="small"
265
+ colorScheme={resolved?.color ? { color: resolved.color, text: "#fff" } : undefined}>
266
+ {resolved?.label ?? numStr}
267
+ </Chip>
268
+ );
269
+ }
270
+
271
+ // Default → right-aligned
272
+ return (
273
+ <Typography variant="body2"
274
+ noWrap
275
+ align="right"
276
+ className="px-1 tabular-nums">
277
+ {numStr}
278
+ </Typography>
279
+ );
280
+ }
281
+
282
+ function renderBoolean(value: unknown): React.ReactElement {
283
+ return (
284
+ <div className="px-1 flex items-center">
285
+ <BooleanSwitch value={!!value}
286
+ size="small"
287
+ disabled />
288
+ </div>
289
+ );
290
+ }
291
+
292
+ function renderDate(property: CollectionPropertyConfig, value: unknown): React.ReactElement {
293
+ const includeTime = property.mode === "date_time";
294
+ const formatted = formatDate(value, includeTime);
295
+
296
+ return (
297
+ <Typography variant="body2"
298
+ noWrap
299
+ className="px-1 tabular-nums">
300
+ {formatted}
301
+ </Typography>
302
+ );
303
+ }
304
+
305
+ function renderArray(property: CollectionPropertyConfig, value: unknown): React.ReactElement {
306
+ if (!Array.isArray(value)) {
307
+ return (
308
+ <Typography variant="body2"
309
+ color="secondary"
310
+ className="px-1">
311
+
312
+ </Typography>
313
+ );
314
+ }
315
+
316
+ // Array of enum values → row of chips
317
+ if (property.of?.enum && value.length > 0) {
318
+ return (
319
+ <div className="px-1 flex flex-wrap gap-1 overflow-hidden">
320
+ {value.map((item, i) => {
321
+ const resolved = resolveEnumLabel(property.of!.enum, item);
322
+ return (
323
+ <Chip key={i}
324
+ size="smallest"
325
+ colorScheme={resolved?.color ? { color: resolved.color, text: "#fff" } : undefined}>
326
+ {resolved?.label ?? String(item)}
327
+ </Chip>
328
+ );
329
+ })}
330
+ </div>
331
+ );
332
+ }
333
+
334
+ // Array of strings → comma-separated
335
+ if (value.length > 0 && value.every((item) => typeof item === "string")) {
336
+ return (
337
+ <Typography variant="body2"
338
+ noWrap
339
+ className="px-1">
340
+ {(value as string[]).join(", ")}
341
+ </Typography>
342
+ );
343
+ }
344
+
345
+ // Generic array → item count
346
+ return (
347
+ <Typography variant="body2"
348
+ color="secondary"
349
+ className="px-1">
350
+ {value.length} {value.length === 1 ? "item" : "items"}
351
+ </Typography>
352
+ );
353
+ }
354
+
355
+ function renderMap(value: unknown): React.ReactElement {
356
+ if (typeof value !== "object" || value === null) {
357
+ return (
358
+ <Typography variant="body2"
359
+ color="secondary"
360
+ className="px-1">
361
+
362
+ </Typography>
363
+ );
364
+ }
365
+
366
+ const fieldCount = Object.keys(value as Record<string, unknown>).length;
367
+ return (
368
+ <Typography variant="body2"
369
+ color="secondary"
370
+ className="px-1">
371
+ {fieldCount} {fieldCount === 1 ? "field" : "fields"}
372
+ </Typography>
373
+ );
374
+ }
375
+
376
+ function renderReference(value: unknown): React.ReactElement {
377
+ return (
378
+ <Typography variant="body2"
379
+ noWrap
380
+ className="px-1">
381
+ {String(value)}
382
+ </Typography>
383
+ );
384
+ }
385
+
386
+ function renderGeopoint(value: unknown): React.ReactElement {
387
+ if (isGeoPoint(value)) {
388
+ return (
389
+ <Typography variant="body2"
390
+ noWrap
391
+ className="px-1 tabular-nums">
392
+ {value.lat.toFixed(6)}, {value.lng.toFixed(6)}
393
+ </Typography>
394
+ );
395
+ }
396
+
397
+ return (
398
+ <Typography variant="body2"
399
+ color="secondary"
400
+ className="px-1">
401
+ {String(value)}
402
+ </Typography>
403
+ );
404
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Headless Collection View components.
3
+ *
4
+ * Data-agnostic collection rendering — table, card, list, and kanban views
5
+ * driven entirely by property configurations and callbacks.
6
+ *
7
+ * Zero imports from any entity or data layer.
8
+ *
9
+ * @module CollectionView
10
+ */
11
+
12
+ // Types
13
+ export type {
14
+ CollectionViewMode,
15
+ CollectionViewSize,
16
+ CollectionEnumValueConfig,
17
+ CollectionPropertyConfig,
18
+ CollectionDataController,
19
+ CellRendererProps,
20
+ CellRendererOverride,
21
+ CollectionSelectionController,
22
+ KanbanPropertyOption
23
+ } from "./CollectionViewTypes";
24
+
25
+ // Components
26
+ export { CollectionView } from "./CollectionView";
27
+ export type { CollectionViewProps } from "./CollectionView";
28
+
29
+ export { CollectionViewToolbar } from "./CollectionViewToolbar";
30
+ export type { CollectionViewToolbarProps } from "./CollectionViewToolbar";
31
+
32
+ export { CollectionTableView } from "./CollectionTableView";
33
+ export type { CollectionTableViewProps } from "./CollectionTableView";
34
+
35
+ export { CollectionCardView } from "./CollectionCardView";
36
+ export type { CollectionCardViewProps } from "./CollectionCardView";
37
+
38
+ export { CollectionListView } from "./CollectionListView";
39
+ export type { CollectionListViewProps } from "./CollectionListView";
40
+
41
+ export { CollectionKanbanView } from "./CollectionKanbanView";
42
+ export type { CollectionKanbanViewProps } from "./CollectionKanbanView";
43
+
44
+ export { DefaultCellRenderer } from "./DefaultCellRenderer";
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Traverse an object by dot-separated path.
3
+ * Returns `undefined` if any segment is missing.
4
+ */
5
+ export function getValueInPath(obj: Record<string, unknown> | undefined, path: string): unknown {
6
+ if (!obj) return undefined;
7
+ const parts = path.split(".");
8
+ let current: unknown = obj;
9
+ for (const part of parts) {
10
+ if (current === null || current === undefined || typeof current !== "object") return undefined;
11
+ current = (current as Record<string, unknown>)[part];
12
+ }
13
+ return current;
14
+ }