@object-ui/fields 3.3.0 → 3.3.2
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.
- package/CHANGELOG.md +151 -0
- package/README.md +24 -0
- package/dist/{LookupField-BHr7uTX1.cjs → LookupField-CiIjeL_i.cjs} +1 -1
- package/dist/{LookupField-B6MOSQVJ.js → LookupField-De-GqsfT.js} +176 -179
- package/dist/index.cjs +3 -3
- package/dist/index.js +633 -655
- package/package.json +34 -9
- package/src/__tests__/cell-renderers.test.tsx +39 -18
- package/src/__tests__/datetime-cell.test.tsx +2 -2
- package/src/__tests__/datetime-timezone.test.tsx +9 -9
- package/src/__tests__/validation-feedback.test.tsx +8 -8
- package/src/datetime-widgets.test.tsx +1 -1
- package/src/index.tsx +54 -28
- package/src/record-picker.test.tsx +30 -23
- package/src/remaining-widgets.test.tsx +1 -1
- package/src/widgets/AddressField.tsx +4 -3
- package/src/widgets/AutoNumberField.tsx +5 -3
- package/src/widgets/CodeField.tsx +2 -2
- package/src/widgets/ColorField.tsx +2 -2
- package/src/widgets/CurrencyField.tsx +2 -2
- package/src/widgets/DateField.tsx +2 -2
- package/src/widgets/DateTimeField.tsx +2 -2
- package/src/widgets/EmailField.tsx +2 -2
- package/src/widgets/FileField.tsx +2 -2
- package/src/widgets/FormulaField.tsx +14 -12
- package/src/widgets/GeolocationField.tsx +4 -3
- package/src/widgets/GridField.tsx +3 -3
- package/src/widgets/ImageField.tsx +2 -2
- package/src/widgets/LocationField.tsx +2 -2
- package/src/widgets/LookupField.tsx +3 -5
- package/src/widgets/NumberField.tsx +2 -2
- package/src/widgets/ObjectField.tsx +2 -2
- package/src/widgets/PercentField.tsx +2 -2
- package/src/widgets/PhoneField.tsx +2 -2
- package/src/widgets/QRCodeField.tsx +2 -2
- package/src/widgets/RichTextField.tsx +2 -2
- package/src/widgets/SelectField.tsx +9 -7
- package/src/widgets/SummaryField.tsx +12 -10
- package/src/widgets/TextAreaField.tsx +2 -2
- package/src/widgets/TextField.tsx +2 -2
- package/src/widgets/TimeField.tsx +2 -2
- package/src/widgets/UrlField.tsx +2 -2
- package/src/widgets/UserField.tsx +2 -2
- package/src/widgets/VectorField.tsx +2 -1
- package/dist/AddressField-Do46PdqT.cjs +0 -1
- package/dist/AutoNumberField-cS2tsxd-.cjs +0 -1
- package/dist/AvatarField-CxhGS3wF.cjs +0 -1
- package/dist/BooleanField-BgkbTHPY.cjs +0 -1
- package/dist/CodeField-DE3dLruS.cjs +0 -1
- package/dist/ColorField-DBTFFYpv.cjs +0 -1
- package/dist/CurrencyField-C28LkJts.cjs +0 -1
- package/dist/DateField-WGWwddDF.cjs +0 -1
- package/dist/DateTimeField-BYsZs8f7.cjs +0 -1
- package/dist/EmailField-1PW03WW8.cjs +0 -1
- package/dist/FileField-BqZkp1O6.cjs +0 -1
- package/dist/FormulaField-MZ9le4RE.cjs +0 -1
- package/dist/GeolocationField-onbAOdIe.cjs +0 -1
- package/dist/GridField-D4OWfaU4.cjs +0 -1
- package/dist/ImageField-BZf-AL2e.cjs +0 -1
- package/dist/LocationField-DhGJtUzZ.cjs +0 -1
- package/dist/MasterDetailField-YTAU1hzg.cjs +0 -1
- package/dist/NumberField-BCk42NjA.cjs +0 -1
- package/dist/ObjectField-iYK5eab_.cjs +0 -1
- package/dist/PasswordField-BAReSDz7.cjs +0 -1
- package/dist/PercentField-nzACEXtC.cjs +0 -1
- package/dist/PhoneField-CvbkGpN3.cjs +0 -1
- package/dist/QRCodeField-D8fov03S.cjs +0 -1
- package/dist/RatingField-DBgj9cgE.cjs +0 -1
- package/dist/RichTextField-7VwN6GcU.cjs +0 -1
- package/dist/SelectField-BUOTiYaS.cjs +0 -1
- package/dist/SignatureField-DW_Cz6t9.cjs +0 -1
- package/dist/SliderField-Bgi3-TAD.cjs +0 -1
- package/dist/SummaryField-Ed2wJBvs.cjs +0 -1
- package/dist/TextAreaField-O503xJff.cjs +0 -1
- package/dist/TextField-BrwtPIQC.cjs +0 -1
- package/dist/TimeField-Bhoxv2yp.cjs +0 -1
- package/dist/UrlField-VFO089Gr.cjs +0 -1
- package/dist/UserField-BRe9SCCk.cjs +0 -1
- package/dist/VectorField-ByrWkiud.cjs +0 -1
- /package/dist/{useFieldTranslation-BiQad4ar.cjs → useFieldTranslation-BYPwtuNZ.cjs} +0 -0
|
@@ -1113,29 +1113,36 @@ describe('RecordPickerDialog — renderGrid slot', () => {
|
|
|
1113
1113
|
expect(screen.getByTestId('custom-grid')).toBeInTheDocument();
|
|
1114
1114
|
});
|
|
1115
1115
|
|
|
1116
|
-
//
|
|
1117
|
-
|
|
1118
|
-
expect.
|
|
1119
|
-
|
|
1120
|
-
expect.
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
expect.
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1116
|
+
// Wait for data to load, then check the last renderGrid call has records
|
|
1117
|
+
await waitFor(() => {
|
|
1118
|
+
expect(renderGrid).toHaveBeenLastCalledWith(
|
|
1119
|
+
expect.objectContaining({
|
|
1120
|
+
columns: expect.arrayContaining([
|
|
1121
|
+
expect.objectContaining({ field: 'name' }),
|
|
1122
|
+
]),
|
|
1123
|
+
records: expect.arrayContaining([
|
|
1124
|
+
expect.objectContaining({ id: '1', name: 'Acme Corp' }),
|
|
1125
|
+
]),
|
|
1126
|
+
loading: false,
|
|
1127
|
+
totalCount: 1,
|
|
1128
|
+
}),
|
|
1129
|
+
);
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
// Verify all expected props are present in the last call
|
|
1133
|
+
const lastCallArgs = renderGrid.mock.calls[renderGrid.mock.calls.length - 1][0];
|
|
1134
|
+
expect(lastCallArgs).toMatchObject({
|
|
1135
|
+
currentPage: 1,
|
|
1136
|
+
pageSize: 10,
|
|
1137
|
+
sortField: null,
|
|
1138
|
+
sortDirection: 'asc',
|
|
1139
|
+
multiple: false,
|
|
1140
|
+
idField: 'id',
|
|
1141
|
+
});
|
|
1142
|
+
expect(lastCallArgs.onSort).toBeTypeOf('function');
|
|
1143
|
+
expect(lastCallArgs.onPageChange).toBeTypeOf('function');
|
|
1144
|
+
expect(lastCallArgs.onRowClick).toBeTypeOf('function');
|
|
1145
|
+
expect(lastCallArgs.isSelected).toBeTypeOf('function');
|
|
1139
1146
|
});
|
|
1140
1147
|
|
|
1141
1148
|
it('hides built-in table when renderGrid is provided', async () => {
|
|
@@ -37,7 +37,7 @@ describe('Remaining Field Widgets', () => {
|
|
|
37
37
|
|
|
38
38
|
it('should render dash for null value', () => {
|
|
39
39
|
render(<AutoNumberField {...baseProps} value={null as any} />);
|
|
40
|
-
expect(screen.getByText('
|
|
40
|
+
expect(screen.getByText('—')).toBeInTheDocument();
|
|
41
41
|
});
|
|
42
42
|
});
|
|
43
43
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Input, Label } from '@object-ui/components';
|
|
2
|
+
import { Input, Label, EmptyValue } from '@object-ui/components';
|
|
3
3
|
import { FieldWidgetProps } from './types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -34,11 +34,12 @@ export function AddressField({ value, onChange, field, readonly, ...props }: Fie
|
|
|
34
34
|
[addr.state, addr.zipCode].filter(Boolean).join(' '),
|
|
35
35
|
addr.country,
|
|
36
36
|
].filter(Boolean);
|
|
37
|
-
return parts.join(', ')
|
|
37
|
+
return parts.join(', ');
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
if (readonly) {
|
|
41
|
-
|
|
41
|
+
const formatted = formatAddress(address);
|
|
42
|
+
return formatted ? <span className="text-sm">{formatted}</span> : <EmptyValue />;
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
return (
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { EmptyValue } from '@object-ui/components';
|
|
2
3
|
import { FieldWidgetProps } from './types';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -6,11 +7,12 @@ import { FieldWidgetProps } from './types';
|
|
|
6
7
|
* Values are automatically generated by the system and cannot be edited
|
|
7
8
|
*/
|
|
8
9
|
export function AutoNumberField({ value, field, ...props }: FieldWidgetProps<string | number>) {
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
if (value == null) {
|
|
11
|
+
return <EmptyValue className={props.className} />;
|
|
12
|
+
}
|
|
11
13
|
return (
|
|
12
14
|
<span className={`text-sm font-mono text-gray-700 ${props.className || ''}`}>
|
|
13
|
-
{
|
|
15
|
+
{String(value)}
|
|
14
16
|
</span>
|
|
15
17
|
);
|
|
16
18
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Textarea, cn } from '@object-ui/components';
|
|
2
|
+
import { Textarea, cn, EmptyValue } from '@object-ui/components';
|
|
3
3
|
import { FieldWidgetProps } from './types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -15,7 +15,7 @@ export function CodeField({ value, onChange, field, readonly, ...props }: FieldW
|
|
|
15
15
|
if (readonly) {
|
|
16
16
|
return (
|
|
17
17
|
<pre className={cn("text-sm bg-muted p-2 rounded overflow-x-auto border", props.className)}>
|
|
18
|
-
<code>{value ||
|
|
18
|
+
<code>{value || <EmptyValue />}</code>
|
|
19
19
|
</pre>
|
|
20
20
|
);
|
|
21
21
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Input } from '@object-ui/components';
|
|
2
|
+
import { Input, EmptyValue } from '@object-ui/components';
|
|
3
3
|
import { FieldWidgetProps } from './types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -16,7 +16,7 @@ export function ColorField({ value, onChange, field, readonly, ...props }: Field
|
|
|
16
16
|
className="w-6 h-6 rounded border border-input"
|
|
17
17
|
style={{ backgroundColor: value || '#000000' }}
|
|
18
18
|
/>
|
|
19
|
-
<span className="text-sm">{value ||
|
|
19
|
+
<span className="text-sm">{value || <EmptyValue />}</span>
|
|
20
20
|
</div>
|
|
21
21
|
);
|
|
22
22
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Input } from '@object-ui/components';
|
|
2
|
+
import { Input, EmptyValue } from '@object-ui/components';
|
|
3
3
|
import { FieldWidgetProps } from './types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -22,7 +22,7 @@ export function CurrencyField({ value, onChange, field, readonly, errorMessage,
|
|
|
22
22
|
const precision = currencyField?.precision ?? 2;
|
|
23
23
|
|
|
24
24
|
if (readonly) {
|
|
25
|
-
if (value == null) return <
|
|
25
|
+
if (value == null) return <EmptyValue />;
|
|
26
26
|
return (
|
|
27
27
|
<span className="text-sm font-medium tabular-nums">
|
|
28
28
|
{formatCurrency(Number(value), currency)}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Input } from '@object-ui/components';
|
|
2
|
+
import { Input, EmptyValue } from '@object-ui/components';
|
|
3
3
|
import { FieldWidgetProps } from './types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -8,7 +8,7 @@ import { FieldWidgetProps } from './types';
|
|
|
8
8
|
*/
|
|
9
9
|
export function DateField({ value, onChange, field, readonly, ...props }: FieldWidgetProps<string>) {
|
|
10
10
|
if (readonly) {
|
|
11
|
-
return <span className="text-sm">{
|
|
11
|
+
return value ? <span className="text-sm">{new Date(value).toLocaleDateString()}</span> : <EmptyValue />;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
// Filter out non-DOM props
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Input } from '@object-ui/components';
|
|
2
|
+
import { Input, EmptyValue } from '@object-ui/components';
|
|
3
3
|
import { FieldWidgetProps } from './types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -8,7 +8,7 @@ import { FieldWidgetProps } from './types';
|
|
|
8
8
|
*/
|
|
9
9
|
export function DateTimeField({ value, onChange, field, readonly, ...props }: FieldWidgetProps<string>) {
|
|
10
10
|
if (readonly) {
|
|
11
|
-
if (!value) return <
|
|
11
|
+
if (!value) return <EmptyValue />;
|
|
12
12
|
const date = new Date(value);
|
|
13
13
|
return (
|
|
14
14
|
<span className="text-sm">
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Input } from '@object-ui/components';
|
|
2
|
+
import { Input, EmptyValue } from '@object-ui/components';
|
|
3
3
|
import { FieldWidgetProps } from './types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -9,7 +9,7 @@ import { FieldWidgetProps } from './types';
|
|
|
9
9
|
export function EmailField({ value, onChange, field, readonly, errorMessage, ...props }: FieldWidgetProps<string>) {
|
|
10
10
|
const config = field || (props as any).schema;
|
|
11
11
|
if (readonly) {
|
|
12
|
-
if (!value) return <
|
|
12
|
+
if (!value) return <EmptyValue />;
|
|
13
13
|
return (
|
|
14
14
|
<a
|
|
15
15
|
href={`mailto:${value}`}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useRef, useState, useCallback } from 'react';
|
|
2
|
-
import { Button } from '@object-ui/components';
|
|
2
|
+
import { Button, EmptyValue } from '@object-ui/components';
|
|
3
3
|
import { Upload, X, File as FileIcon, ImageIcon } from 'lucide-react';
|
|
4
4
|
import { FieldWidgetProps } from './types';
|
|
5
5
|
|
|
@@ -86,7 +86,7 @@ export function FileField({ value, onChange, field, readonly, ...props }: FieldW
|
|
|
86
86
|
}, [accept, processFiles]);
|
|
87
87
|
|
|
88
88
|
if (readonly) {
|
|
89
|
-
if (!value) return <
|
|
89
|
+
if (!value) return <EmptyValue />;
|
|
90
90
|
|
|
91
91
|
const readonlyFiles = Array.isArray(value) ? value : [value];
|
|
92
92
|
return (
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { EmptyValue } from '@object-ui/components';
|
|
2
3
|
import { FieldWidgetProps } from './types';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -9,18 +10,19 @@ export function FormulaField({ value, field, ...props }: FieldWidgetProps<any>)
|
|
|
9
10
|
const formulaField = (field || (props as any).schema) as any;
|
|
10
11
|
const returnType = formulaField?.return_type || 'text';
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
13
|
+
if (value == null) {
|
|
14
|
+
return <EmptyValue className={props.className} />;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let displayValue: string;
|
|
18
|
+
if (returnType === 'number' || returnType === 'currency') {
|
|
19
|
+
displayValue = typeof value === 'number' ? value.toFixed(2) : String(value);
|
|
20
|
+
} else if (returnType === 'boolean') {
|
|
21
|
+
displayValue = value ? 'Yes' : 'No';
|
|
22
|
+
} else if (returnType === 'date') {
|
|
23
|
+
displayValue = new Date(value).toLocaleDateString();
|
|
24
|
+
} else {
|
|
25
|
+
displayValue = String(value);
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
return (
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Input, Button, Label } from '@object-ui/components';
|
|
2
|
+
import { Input, Button, Label, EmptyValue } from '@object-ui/components';
|
|
3
3
|
import { MapPin, Crosshair } from 'lucide-react';
|
|
4
4
|
import { FieldWidgetProps } from './types';
|
|
5
5
|
|
|
@@ -56,7 +56,7 @@ export function GeolocationField({ value, onChange, field, readonly, ...props }:
|
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
const formatLocation = (loc: GeolocationValue): string => {
|
|
59
|
-
if (!loc.latitude || !loc.longitude) return '
|
|
59
|
+
if (!loc.latitude || !loc.longitude) return '';
|
|
60
60
|
return `${loc.latitude.toFixed(6)}, ${loc.longitude.toFixed(6)}`;
|
|
61
61
|
};
|
|
62
62
|
|
|
@@ -67,10 +67,11 @@ export function GeolocationField({ value, onChange, field, readonly, ...props }:
|
|
|
67
67
|
};
|
|
68
68
|
|
|
69
69
|
if (readonly) {
|
|
70
|
+
const formatted = formatLocation(location);
|
|
70
71
|
return (
|
|
71
72
|
<div className="flex items-center gap-2">
|
|
72
73
|
<MapPin className="w-4 h-4 text-muted-foreground" />
|
|
73
|
-
<span className="text-sm">{
|
|
74
|
+
{formatted ? <span className="text-sm">{formatted}</span> : <EmptyValue />}
|
|
74
75
|
{location.latitude && location.longitude && (
|
|
75
76
|
<Button
|
|
76
77
|
type="button"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { FieldWidgetProps } from './types';
|
|
3
|
-
import { cn } from '@object-ui/components';
|
|
3
|
+
import { cn, EmptyValue } from '@object-ui/components';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* GridField - Sub-table/grid data display
|
|
@@ -12,7 +12,7 @@ export function GridField({ value, field, readonly, ...props }: FieldWidgetProps
|
|
|
12
12
|
const columns = gridField?.columns || [];
|
|
13
13
|
|
|
14
14
|
if (!value || !Array.isArray(value)) {
|
|
15
|
-
return <
|
|
15
|
+
return <EmptyValue />;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
if (readonly) {
|
|
@@ -45,7 +45,7 @@ export function GridField({ value, field, readonly, ...props }: FieldWidgetProps
|
|
|
45
45
|
<tr key={rowIdx} className="hover:bg-muted/50 transition-colors">
|
|
46
46
|
{columns.map((col: any, colIdx: number) => (
|
|
47
47
|
<td key={colIdx} className="px-3 py-2 text-foreground">
|
|
48
|
-
{row[col.name] != null ? String(row[col.name]) :
|
|
48
|
+
{row[col.name] != null ? String(row[col.name]) : <EmptyValue />}
|
|
49
49
|
</td>
|
|
50
50
|
))}
|
|
51
51
|
</tr>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useRef } from 'react';
|
|
2
|
-
import { Button } from '@object-ui/components';
|
|
2
|
+
import { Button, EmptyValue } from '@object-ui/components';
|
|
3
3
|
import { Upload, X, Image as ImageIcon } from 'lucide-react';
|
|
4
4
|
import { FieldWidgetProps } from './types';
|
|
5
5
|
|
|
@@ -14,7 +14,7 @@ export function ImageField({ value, onChange, field, readonly, ...props }: Field
|
|
|
14
14
|
const accept = imageField?.accept ? imageField.accept.join(',') : 'image/*';
|
|
15
15
|
|
|
16
16
|
if (readonly) {
|
|
17
|
-
if (!value) return <
|
|
17
|
+
if (!value) return <EmptyValue />;
|
|
18
18
|
|
|
19
19
|
const images = Array.isArray(value) ? value : [value];
|
|
20
20
|
return (
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Input } from '@object-ui/components';
|
|
2
|
+
import { Input, EmptyValue } from '@object-ui/components';
|
|
3
3
|
import { FieldWidgetProps } from './types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -15,7 +15,7 @@ export function LocationField({ value, onChange, field, readonly, ...props }: Fi
|
|
|
15
15
|
: '';
|
|
16
16
|
|
|
17
17
|
if (readonly) {
|
|
18
|
-
return <span className="text-sm">{displayValue ||
|
|
18
|
+
return <span className="text-sm">{displayValue || <EmptyValue />}</span>;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import React, { useState, useEffect, useCallback, useRef, useContext, useMemo } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
Button,
|
|
2
|
+
import { Button,
|
|
4
3
|
Input,
|
|
5
4
|
Badge,
|
|
6
5
|
Popover,
|
|
7
6
|
PopoverTrigger,
|
|
8
|
-
PopoverContent,
|
|
9
|
-
} from '@object-ui/components';
|
|
7
|
+
PopoverContent, EmptyValue } from '@object-ui/components';
|
|
10
8
|
import { Search, X, Loader2, AlertCircle, Plus, TableProperties } from 'lucide-react';
|
|
11
9
|
import { FieldWidgetProps } from './types';
|
|
12
10
|
import type { DataSource, QueryParams, LookupColumnDef } from '@object-ui/types';
|
|
@@ -337,7 +335,7 @@ export function LookupField({ value, onChange, field, readonly, ...props }: Fiel
|
|
|
337
335
|
|
|
338
336
|
if (readonly) {
|
|
339
337
|
if (!selectedOptions.length) {
|
|
340
|
-
return <
|
|
338
|
+
return <EmptyValue />;
|
|
341
339
|
}
|
|
342
340
|
|
|
343
341
|
if (multiple) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Input } from '@object-ui/components';
|
|
2
|
+
import { Input, EmptyValue } from '@object-ui/components';
|
|
3
3
|
import { NumberFieldMetadata } from '@object-ui/types';
|
|
4
4
|
import { FieldWidgetProps } from './types';
|
|
5
5
|
|
|
@@ -9,7 +9,7 @@ import { FieldWidgetProps } from './types';
|
|
|
9
9
|
*/
|
|
10
10
|
export function NumberField({ value, onChange, field, readonly, ...props }: FieldWidgetProps<number>) {
|
|
11
11
|
if (readonly) {
|
|
12
|
-
return <span className="text-sm">{value
|
|
12
|
+
return value == null ? <EmptyValue /> : <span className="text-sm">{value}</span>;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
const numberField = (field || (props as any).schema) as NumberFieldMetadata;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import { Textarea, cn } from '@object-ui/components';
|
|
2
|
+
import { Textarea, cn, EmptyValue } from '@object-ui/components';
|
|
3
3
|
import { FieldWidgetProps } from './types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -42,7 +42,7 @@ export function ObjectField({ value, onChange, field, readonly, ...props }: Fiel
|
|
|
42
42
|
}, [value, jsonString]);
|
|
43
43
|
|
|
44
44
|
if (readonly) {
|
|
45
|
-
if (!value) return <
|
|
45
|
+
if (!value) return <EmptyValue />;
|
|
46
46
|
return (
|
|
47
47
|
<pre className={cn("text-xs bg-gray-50 p-2 rounded border border-gray-200 overflow-auto max-h-40", props.className)}>
|
|
48
48
|
{JSON.stringify(value, null, 2)}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Input, Slider } from '@object-ui/components';
|
|
2
|
+
import { Input, Slider, EmptyValue } from '@object-ui/components';
|
|
3
3
|
import { FieldWidgetProps } from './types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -12,7 +12,7 @@ export function PercentField({ value, onChange, field, readonly, errorMessage, c
|
|
|
12
12
|
const precision = percentField?.precision ?? 2;
|
|
13
13
|
|
|
14
14
|
if (readonly) {
|
|
15
|
-
if (value == null) return <
|
|
15
|
+
if (value == null) return <EmptyValue />;
|
|
16
16
|
return (
|
|
17
17
|
<span className="text-sm font-medium tabular-nums">
|
|
18
18
|
{(value * 100).toFixed(precision)}%
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Input } from '@object-ui/components';
|
|
2
|
+
import { Input, EmptyValue } from '@object-ui/components';
|
|
3
3
|
import { FieldWidgetProps } from './types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -9,7 +9,7 @@ import { FieldWidgetProps } from './types';
|
|
|
9
9
|
export function PhoneField({ value, onChange, field, readonly, errorMessage, ...props }: FieldWidgetProps<string>) {
|
|
10
10
|
const config = field || (props as any).schema;
|
|
11
11
|
if (readonly) {
|
|
12
|
-
if (!value) return <
|
|
12
|
+
if (!value) return <EmptyValue />;
|
|
13
13
|
return (
|
|
14
14
|
<a
|
|
15
15
|
href={`tel:${value}`}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Input, Button } from '@object-ui/components';
|
|
2
|
+
import { Input, Button, EmptyValue } from '@object-ui/components';
|
|
3
3
|
import { QrCode, Copy } from 'lucide-react';
|
|
4
4
|
import { FieldWidgetProps } from './types';
|
|
5
5
|
|
|
@@ -27,7 +27,7 @@ export function QRCodeField({ value, onChange, field, readonly, ...props }: Fiel
|
|
|
27
27
|
if (readonly) {
|
|
28
28
|
return (
|
|
29
29
|
<div className="flex items-center gap-3">
|
|
30
|
-
<span className="text-sm flex-1 truncate">{value ||
|
|
30
|
+
<span className="text-sm flex-1 truncate">{value || <EmptyValue />}</span>
|
|
31
31
|
{value && (
|
|
32
32
|
<Button
|
|
33
33
|
type="button"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Textarea } from '@object-ui/components';
|
|
2
|
+
import { Textarea, EmptyValue } from '@object-ui/components';
|
|
3
3
|
import { FieldWidgetProps } from './types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -13,7 +13,7 @@ export function RichTextField({ value, onChange, field, readonly, errorMessage,
|
|
|
13
13
|
<div
|
|
14
14
|
className="text-sm prose prose-sm max-w-none"
|
|
15
15
|
>
|
|
16
|
-
{value ||
|
|
16
|
+
{value || <EmptyValue />}
|
|
17
17
|
</div>
|
|
18
18
|
);
|
|
19
19
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
Select,
|
|
4
|
-
SelectContent,
|
|
5
|
-
SelectItem,
|
|
6
|
-
SelectTrigger,
|
|
7
|
-
SelectValue
|
|
2
|
+
import {
|
|
3
|
+
Select,
|
|
4
|
+
SelectContent,
|
|
5
|
+
SelectItem,
|
|
6
|
+
SelectTrigger,
|
|
7
|
+
SelectValue,
|
|
8
|
+
EmptyValue,
|
|
8
9
|
} from '@object-ui/components';
|
|
9
10
|
import { SelectFieldMetadata } from '@object-ui/types';
|
|
10
11
|
import { useFieldTranslation } from './useFieldTranslation';
|
|
@@ -21,7 +22,8 @@ export function SelectField({ value, onChange, field, readonly, ...props }: Fiel
|
|
|
21
22
|
|
|
22
23
|
if (readonly) {
|
|
23
24
|
const option = options.find((o) => o.value === value);
|
|
24
|
-
|
|
25
|
+
const display = option?.label || value;
|
|
26
|
+
return display ? <span className="text-sm">{display}</span> : <EmptyValue />;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
return (
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { EmptyValue } from '@object-ui/components';
|
|
2
3
|
import { FieldWidgetProps } from './types';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -9,16 +10,17 @@ export function SummaryField({ value, field, ...props }: FieldWidgetProps<any>)
|
|
|
9
10
|
const summaryField = (field || (props as any).schema) as any;
|
|
10
11
|
const summaryType = summaryField?.summary_type || 'count';
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
if (value == null) {
|
|
14
|
+
return <EmptyValue className={props.className} />;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let displayValue: string;
|
|
18
|
+
if (summaryType === 'count') {
|
|
19
|
+
displayValue = String(value);
|
|
20
|
+
} else if (['sum', 'avg', 'min', 'max'].includes(summaryType)) {
|
|
21
|
+
displayValue = typeof value === 'number' ? value.toFixed(2) : String(value);
|
|
22
|
+
} else {
|
|
23
|
+
displayValue = String(value);
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
return (
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Textarea } from '@object-ui/components';
|
|
2
|
+
import { Textarea, EmptyValue } from '@object-ui/components';
|
|
3
3
|
import { FieldWidgetProps } from './types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -10,7 +10,7 @@ export function TextAreaField({ value, onChange, field, readonly, errorMessage,
|
|
|
10
10
|
if (readonly) {
|
|
11
11
|
return (
|
|
12
12
|
<div className="text-sm whitespace-pre-wrap">
|
|
13
|
-
{value ||
|
|
13
|
+
{value || <EmptyValue />}
|
|
14
14
|
</div>
|
|
15
15
|
);
|
|
16
16
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Input, Textarea } from '@object-ui/components';
|
|
2
|
+
import { Input, Textarea, EmptyValue } from '@object-ui/components';
|
|
3
3
|
import { TextareaFieldMetadata } from '@object-ui/types';
|
|
4
4
|
import { FieldWidgetProps } from './types';
|
|
5
5
|
|
|
@@ -11,7 +11,7 @@ export function TextField({ value, onChange, field, readonly, ...props }: FieldW
|
|
|
11
11
|
const fieldData = field || (props as any).schema;
|
|
12
12
|
|
|
13
13
|
if (readonly) {
|
|
14
|
-
return <span className="text-sm">{value ||
|
|
14
|
+
return <span className="text-sm">{value || <EmptyValue />}</span>;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
// Cast for rows property
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Input } from '@object-ui/components';
|
|
2
|
+
import { Input, EmptyValue } from '@object-ui/components';
|
|
3
3
|
import { FieldWidgetProps } from './types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -8,7 +8,7 @@ import { FieldWidgetProps } from './types';
|
|
|
8
8
|
*/
|
|
9
9
|
export function TimeField({ value, onChange, field, readonly, ...props }: FieldWidgetProps<string>) {
|
|
10
10
|
if (readonly) {
|
|
11
|
-
return <span className="text-sm">{value ||
|
|
11
|
+
return <span className="text-sm">{value || <EmptyValue />}</span>;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
// Filter out non-DOM props
|
package/src/widgets/UrlField.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Input } from '@object-ui/components';
|
|
2
|
+
import { Input, EmptyValue } from '@object-ui/components';
|
|
3
3
|
import { FieldWidgetProps } from './types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -9,7 +9,7 @@ import { FieldWidgetProps } from './types';
|
|
|
9
9
|
export function UrlField({ value, onChange, field, readonly, errorMessage, ...props }: FieldWidgetProps<string>) {
|
|
10
10
|
const config = field || (props as any).schema;
|
|
11
11
|
if (readonly) {
|
|
12
|
-
if (!value) return <
|
|
12
|
+
if (!value) return <EmptyValue />;
|
|
13
13
|
|
|
14
14
|
// Validate URL to prevent javascript: or data: URLs
|
|
15
15
|
const isValidUrl = value.startsWith('http://') || value.startsWith('https://');
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Avatar, AvatarFallback, Badge } from '@object-ui/components';
|
|
2
|
+
import { Avatar, AvatarFallback, Badge, EmptyValue } from '@object-ui/components';
|
|
3
3
|
import { X } from 'lucide-react';
|
|
4
4
|
import { FieldWidgetProps } from './types';
|
|
5
5
|
|
|
@@ -12,7 +12,7 @@ export function UserField({ value, onChange, field, readonly, ...props }: FieldW
|
|
|
12
12
|
const multiple = userField?.multiple || false;
|
|
13
13
|
|
|
14
14
|
if (readonly) {
|
|
15
|
-
if (!value) return <
|
|
15
|
+
if (!value) return <EmptyValue />;
|
|
16
16
|
|
|
17
17
|
const users = Array.isArray(value) ? value : [value];
|
|
18
18
|
return (
|