@object-ui/components 3.1.0 → 3.1.1
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/.turbo/turbo-build.log +11 -11
- package/CHANGELOG.md +9 -0
- package/dist/index.css +1 -1
- package/dist/index.js +9928 -9907
- package/dist/index.umd.cjs +20 -20
- package/dist/src/custom/filter-builder.d.ts +1 -1
- package/dist/src/renderers/action/action-bar.d.ts +2 -0
- package/dist/src/renderers/action/action-button.d.ts +1 -0
- package/package.json +4 -4
- package/src/__tests__/action-bar.test.tsx +34 -0
- package/src/__tests__/filter-builder.test.tsx +409 -0
- package/src/custom/filter-builder.tsx +76 -25
- package/src/renderers/action/action-bar.tsx +25 -6
- package/src/renderers/action/action-button.tsx +17 -6
- package/src/renderers/complex/data-table.tsx +2 -2
|
@@ -36,6 +36,7 @@ import { ComponentRegistry } from '@object-ui/core';
|
|
|
36
36
|
import type { ActionSchema, ActionLocation, ActionComponent } from '@object-ui/types';
|
|
37
37
|
import { useCondition } from '@object-ui/react';
|
|
38
38
|
import { cn } from '../../lib/utils';
|
|
39
|
+
import { useIsMobile } from '../../hooks/use-mobile';
|
|
39
40
|
|
|
40
41
|
export interface ActionBarSchema {
|
|
41
42
|
type: 'action:bar';
|
|
@@ -45,6 +46,8 @@ export interface ActionBarSchema {
|
|
|
45
46
|
location?: ActionLocation;
|
|
46
47
|
/** Maximum visible inline actions before overflow into "More" menu (default: 3) */
|
|
47
48
|
maxVisible?: number;
|
|
49
|
+
/** Maximum visible inline actions on mobile devices (default: 1). Desktop uses maxVisible instead. */
|
|
50
|
+
mobileMaxVisible?: number;
|
|
48
51
|
/** Visibility condition expression */
|
|
49
52
|
visible?: string;
|
|
50
53
|
/** Layout direction */
|
|
@@ -66,22 +69,36 @@ const ActionBarRenderer = forwardRef<HTMLDivElement, { schema: ActionBarSchema;
|
|
|
66
69
|
'data-obj-id': dataObjId,
|
|
67
70
|
'data-obj-type': dataObjType,
|
|
68
71
|
style,
|
|
72
|
+
data,
|
|
69
73
|
...rest
|
|
70
74
|
} = props;
|
|
71
75
|
|
|
72
76
|
const isVisible = useCondition(schema.visible ? `\${${schema.visible}}` : undefined);
|
|
77
|
+
const isMobile = useIsMobile();
|
|
73
78
|
|
|
74
|
-
// Filter actions by location
|
|
79
|
+
// Filter actions by location and deduplicate by name
|
|
75
80
|
const filteredActions = useMemo(() => {
|
|
76
81
|
const actions = schema.actions || [];
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
const located = !schema.location
|
|
83
|
+
? actions
|
|
84
|
+
: actions.filter(
|
|
85
|
+
a => !a.locations || a.locations.length === 0 || a.locations.includes(schema.location!),
|
|
86
|
+
);
|
|
87
|
+
// Deduplicate by action name — keep first occurrence
|
|
88
|
+
const seen = new Set<string>();
|
|
89
|
+
return located.filter(a => {
|
|
90
|
+
if (!a.name) return true;
|
|
91
|
+
if (seen.has(a.name)) return false;
|
|
92
|
+
seen.add(a.name);
|
|
93
|
+
return true;
|
|
94
|
+
});
|
|
81
95
|
}, [schema.actions, schema.location]);
|
|
82
96
|
|
|
83
97
|
// Split into visible inline actions and overflow
|
|
84
|
-
|
|
98
|
+
// On mobile, show fewer actions inline (default: 1)
|
|
99
|
+
const maxVisible = isMobile
|
|
100
|
+
? (schema.mobileMaxVisible ?? 1)
|
|
101
|
+
: (schema.maxVisible ?? 3);
|
|
85
102
|
const { inlineActions, overflowActions } = useMemo(() => {
|
|
86
103
|
if (filteredActions.length <= maxVisible) {
|
|
87
104
|
return { inlineActions: filteredActions, overflowActions: [] as ActionSchema[] };
|
|
@@ -137,9 +154,11 @@ const ActionBarRenderer = forwardRef<HTMLDivElement, { schema: ActionBarSchema;
|
|
|
137
154
|
schema={{
|
|
138
155
|
...action,
|
|
139
156
|
type: componentType,
|
|
157
|
+
actionType: action.type,
|
|
140
158
|
variant: action.variant || schema.variant,
|
|
141
159
|
size: action.size || schema.size,
|
|
142
160
|
}}
|
|
161
|
+
data={data}
|
|
143
162
|
/>
|
|
144
163
|
);
|
|
145
164
|
})}
|
|
@@ -28,7 +28,7 @@ import { Loader2 } from 'lucide-react';
|
|
|
28
28
|
import { resolveIcon } from './resolve-icon';
|
|
29
29
|
|
|
30
30
|
export interface ActionButtonProps {
|
|
31
|
-
schema: ActionSchema & { type: string; className?: string };
|
|
31
|
+
schema: ActionSchema & { type: string; className?: string; actionType?: string };
|
|
32
32
|
className?: string;
|
|
33
33
|
/** Override context for this specific action */
|
|
34
34
|
context?: Record<string, any>;
|
|
@@ -41,15 +41,19 @@ const ActionButtonRenderer = forwardRef<HTMLButtonElement, ActionButtonProps>(
|
|
|
41
41
|
'data-obj-id': dataObjId,
|
|
42
42
|
'data-obj-type': dataObjType,
|
|
43
43
|
style,
|
|
44
|
+
data,
|
|
44
45
|
...rest
|
|
45
46
|
} = props;
|
|
46
47
|
|
|
47
48
|
const { execute } = useAction();
|
|
48
49
|
const [loading, setLoading] = useState(false);
|
|
49
50
|
|
|
50
|
-
//
|
|
51
|
-
const
|
|
52
|
-
|
|
51
|
+
// Record data may be passed from SchemaRenderer (e.g. DetailView passes record data)
|
|
52
|
+
const recordData = data != null && typeof data === 'object' ? data as Record<string, any> : {};
|
|
53
|
+
|
|
54
|
+
// Evaluate visibility and enabled conditions with record data context
|
|
55
|
+
const isVisible = useCondition(schema.visible ? `\${${schema.visible}}` : undefined, recordData);
|
|
56
|
+
const isEnabled = useCondition(schema.enabled ? `\${${schema.enabled}}` : undefined, recordData);
|
|
53
57
|
|
|
54
58
|
// Resolve icon
|
|
55
59
|
const Icon = resolveIcon(schema.icon);
|
|
@@ -63,14 +67,21 @@ const ActionButtonRenderer = forwardRef<HTMLButtonElement, ActionButtonProps>(
|
|
|
63
67
|
setLoading(true);
|
|
64
68
|
|
|
65
69
|
try {
|
|
70
|
+
// Route params correctly:
|
|
71
|
+
// - Array of objects with name+type → ActionParamDef[] → pass as actionParams for collection
|
|
72
|
+
// - Otherwise → pass as actual param values
|
|
73
|
+
const paramsPayload = Array.isArray(schema.params)
|
|
74
|
+
? { actionParams: schema.params as any }
|
|
75
|
+
: { params: schema.params as Record<string, any> | undefined };
|
|
76
|
+
|
|
66
77
|
await execute({
|
|
67
|
-
type: schema.type,
|
|
78
|
+
type: schema.actionType || schema.type,
|
|
68
79
|
name: schema.name,
|
|
69
80
|
target: schema.target,
|
|
70
81
|
execute: schema.execute,
|
|
71
82
|
endpoint: schema.endpoint,
|
|
72
83
|
method: schema.method,
|
|
73
|
-
|
|
84
|
+
...paramsPayload,
|
|
74
85
|
confirmText: schema.confirmText,
|
|
75
86
|
successMessage: schema.successMessage,
|
|
76
87
|
errorMessage: schema.errorMessage,
|
|
@@ -796,7 +796,7 @@ const DataTableRenderer = ({ schema }: { schema: DataTableSchema }) => {
|
|
|
796
796
|
{col.headerIcon && (
|
|
797
797
|
<span className="text-muted-foreground flex-shrink-0">{col.headerIcon}</span>
|
|
798
798
|
)}
|
|
799
|
-
<span className="text-xs font-normal text-muted-foreground">{col.header}</span>
|
|
799
|
+
<span className="text-xs font-normal text-muted-foreground whitespace-nowrap truncate">{col.header}</span>
|
|
800
800
|
{sortable && col.sortable !== false && getSortIcon(col.accessorKey)}
|
|
801
801
|
</div>
|
|
802
802
|
{resizableColumns && col.resizable !== false && (
|
|
@@ -858,7 +858,7 @@ const DataTableRenderer = ({ schema }: { schema: DataTableSchema }) => {
|
|
|
858
858
|
key={rowId}
|
|
859
859
|
data-state={isSelected ? 'selected' : undefined}
|
|
860
860
|
className={cn(
|
|
861
|
-
"bg-background border-b border-border hover:bg-muted/
|
|
861
|
+
"bg-background border-b border-border hover:bg-muted/50 group/row",
|
|
862
862
|
schema.onRowClick && "cursor-pointer",
|
|
863
863
|
rowHasChanges && "bg-amber-50 dark:bg-amber-950/20",
|
|
864
864
|
rowClassName && rowClassName(row, rowIndex)
|