@object-ui/components 0.5.0 → 3.0.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 (119) hide show
  1. package/.turbo/turbo-build.log +12 -25
  2. package/CHANGELOG.md +32 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.js +23987 -22576
  5. package/dist/index.umd.cjs +30 -30
  6. package/dist/src/custom/action-param-dialog.d.ts +21 -0
  7. package/dist/src/custom/index.d.ts +4 -0
  8. package/dist/src/custom/navigation-overlay.d.ts +50 -0
  9. package/dist/src/custom/view-skeleton.d.ts +37 -0
  10. package/dist/src/custom/view-states.d.ts +33 -0
  11. package/dist/src/index.d.ts +1 -0
  12. package/dist/src/renderers/action/action-button.d.ts +11 -0
  13. package/dist/src/renderers/action/action-group.d.ts +25 -0
  14. package/dist/src/renderers/action/action-icon.d.ts +10 -0
  15. package/dist/src/renderers/action/action-menu.d.ts +19 -0
  16. package/dist/src/renderers/action/index.d.ts +0 -0
  17. package/dist/src/renderers/action/resolve-icon.d.ts +6 -0
  18. package/package.json +20 -19
  19. package/src/__tests__/PageRendererRegions.test.tsx +664 -55
  20. package/src/__tests__/__snapshots__/snapshot-critical.test.tsx.snap +811 -0
  21. package/src/__tests__/__snapshots__/snapshot.test.tsx.snap +327 -0
  22. package/src/__tests__/accessibility.test.tsx +137 -0
  23. package/src/__tests__/api-consistency.test.tsx +596 -0
  24. package/src/__tests__/color-contrast.test.tsx +212 -0
  25. package/src/__tests__/compliance.test.tsx +72 -0
  26. package/src/__tests__/edge-cases.test.tsx +285 -0
  27. package/src/__tests__/navigation-overlay.test.tsx +273 -0
  28. package/src/__tests__/snapshot-critical.test.tsx +317 -0
  29. package/src/__tests__/snapshot.test.tsx +205 -0
  30. package/src/__tests__/view-compliance.test.tsx +153 -0
  31. package/src/__tests__/wcag-audit.test.tsx +493 -0
  32. package/src/custom/action-param-dialog.tsx +264 -0
  33. package/src/custom/index.ts +4 -0
  34. package/src/custom/navigation-overlay.tsx +296 -0
  35. package/src/custom/view-skeleton.tsx +243 -0
  36. package/src/custom/view-states.tsx +153 -0
  37. package/src/index.ts +1 -0
  38. package/src/renderers/action/action-button.tsx +147 -0
  39. package/src/renderers/action/action-group.tsx +270 -0
  40. package/src/renderers/action/action-icon.tsx +150 -0
  41. package/src/renderers/action/action-menu.tsx +203 -0
  42. package/src/renderers/action/index.ts +18 -0
  43. package/src/renderers/action/resolve-icon.ts +35 -0
  44. package/src/renderers/complex/__tests__/data-table-batch-editing.test.tsx +275 -0
  45. package/src/renderers/complex/__tests__/data-table-cell-renderer.test.tsx +120 -0
  46. package/src/renderers/complex/__tests__/data-table-editing.test.tsx +221 -0
  47. package/src/renderers/complex/data-table.tsx +269 -33
  48. package/src/renderers/complex/resizable.tsx +20 -17
  49. package/src/renderers/data-display/list.tsx +1 -1
  50. package/src/renderers/data-display/table.tsx +1 -1
  51. package/src/renderers/data-display/tree-view.tsx +2 -1
  52. package/src/renderers/form/form.tsx +33 -10
  53. package/src/renderers/index.ts +1 -0
  54. package/src/renderers/layout/aspect-ratio.tsx +1 -1
  55. package/src/renderers/layout/page.tsx +416 -52
  56. package/src/renderers/navigation/sidebar.tsx +6 -0
  57. package/src/renderers/placeholders.tsx +2 -2
  58. package/src/stories/MockedData.stories.tsx +87 -37
  59. package/src/stories-json/Accessibility.mdx +297 -0
  60. package/src/stories-json/EdgeCases.stories.tsx +160 -0
  61. package/src/stories-json/GettingStarted.mdx +89 -0
  62. package/src/stories-json/Introduction.mdx +127 -0
  63. package/src/stories-json/accordion.stories.tsx +1 -1
  64. package/src/stories-json/aggrid.stories.tsx +1 -1
  65. package/src/stories-json/alert.stories.tsx +1 -1
  66. package/src/stories-json/aspect-ratio.stories.tsx +1 -1
  67. package/src/stories-json/avatar.stories.tsx +1 -1
  68. package/src/stories-json/badge.stories.tsx +1 -1
  69. package/src/stories-json/breadcrumb.stories.tsx +1 -1
  70. package/src/stories-json/button-group.stories.tsx +1 -1
  71. package/src/stories-json/button.stories.tsx +1 -1
  72. package/src/stories-json/calendar.stories.tsx +1 -1
  73. package/src/stories-json/card.stories.tsx +1 -1
  74. package/src/stories-json/carousel.stories.tsx +1 -1
  75. package/src/stories-json/charts.stories.tsx +1 -1
  76. package/src/stories-json/chatbot.stories.tsx +1 -1
  77. package/src/stories-json/code-editor.stories.tsx +1 -1
  78. package/src/stories-json/collapsible.stories.tsx +1 -1
  79. package/src/stories-json/controls.stories.tsx +1 -1
  80. package/src/stories-json/crm-live-data.stories.tsx +154 -0
  81. package/src/stories-json/data-table.stories.tsx +80 -4
  82. package/src/stories-json/data_display_extras.stories.tsx +1 -1
  83. package/src/stories-json/date-picker.stories.tsx +1 -1
  84. package/src/stories-json/detail-view.stories.tsx +1 -1
  85. package/src/stories-json/dialog.stories.tsx +1 -1
  86. package/src/stories-json/feedback_extras.stories.tsx +1 -1
  87. package/src/stories-json/feedback_others.stories.tsx +1 -1
  88. package/src/stories-json/form-variants.stories.tsx +210 -0
  89. package/src/stories-json/form_advanced.stories.tsx +1 -1
  90. package/src/stories-json/form_extras.stories.tsx +1 -1
  91. package/src/stories-json/grid.stories.tsx +1 -1
  92. package/src/stories-json/icon.stories.tsx +1 -1
  93. package/src/stories-json/input.stories.tsx +1 -1
  94. package/src/stories-json/kanban.stories.tsx +1 -1
  95. package/src/stories-json/layout_extended.stories.tsx +1 -1
  96. package/src/stories-json/layout_flex.stories.tsx +1 -1
  97. package/src/stories-json/list-view.stories.tsx +1 -1
  98. package/src/stories-json/markdown.stories.tsx +1 -1
  99. package/src/stories-json/menus.stories.tsx +1 -1
  100. package/src/stories-json/metric-card.stories.tsx +1 -1
  101. package/src/stories-json/navigation-menu.stories.tsx +1 -1
  102. package/src/stories-json/object-aggrid-advanced.stories.tsx +389 -0
  103. package/src/stories-json/object-aggrid.stories.tsx +1 -1
  104. package/src/stories-json/object-form.stories.tsx +1 -1
  105. package/src/stories-json/object-gantt.stories.tsx +1 -1
  106. package/src/stories-json/object-grid.stories.tsx +159 -1
  107. package/src/stories-json/object-map.stories.tsx +1 -1
  108. package/src/stories-json/object-view.stories.tsx +1 -1
  109. package/src/stories-json/overlay_extras.stories.tsx +1 -1
  110. package/src/stories-json/overlay_others.stories.tsx +1 -1
  111. package/src/stories-json/resizable.stories.tsx +1 -1
  112. package/src/stories-json/select.stories.tsx +1 -1
  113. package/src/stories-json/separator.stories.tsx +1 -1
  114. package/src/stories-json/statistic.stories.tsx +1 -1
  115. package/src/stories-json/tabs.stories.tsx +1 -1
  116. package/src/stories-json/timeline.stories.tsx +1 -1
  117. package/src/stories-json/typography.stories.tsx +1 -1
  118. package/src/ui/slider.tsx +6 -2
  119. package/src/stories/Introduction.mdx +0 -34
@@ -0,0 +1,243 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
9
+ import * as React from "react"
10
+ import { cn } from "../lib/utils"
11
+ import { Skeleton } from "../ui/skeleton"
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Shared types
15
+ // ---------------------------------------------------------------------------
16
+
17
+ export interface ViewSkeletonProps extends React.ComponentProps<"div"> {
18
+ /** Number of rows/items to render */
19
+ rows?: number
20
+ }
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // GridSkeleton – table rows with header and column cells
24
+ // ---------------------------------------------------------------------------
25
+
26
+ export interface GridSkeletonProps extends ViewSkeletonProps {
27
+ /** Number of columns to render */
28
+ columns?: number
29
+ }
30
+
31
+ function GridSkeleton({
32
+ rows = 5,
33
+ columns = 4,
34
+ className,
35
+ ...props
36
+ }: GridSkeletonProps) {
37
+ return (
38
+ <div
39
+ data-slot="grid-skeleton"
40
+ className={cn("w-full space-y-2", className)}
41
+ {...props}
42
+ >
43
+ {/* Header row */}
44
+ <div className="flex gap-4 px-4 py-2">
45
+ {Array.from({ length: columns }).map((_, col) => (
46
+ <Skeleton key={col} className="h-4 flex-1 rounded" />
47
+ ))}
48
+ </div>
49
+
50
+ {/* Data rows */}
51
+ {Array.from({ length: rows }).map((_, row) => (
52
+ <div key={row} className="flex gap-4 rounded-md border px-4 py-3">
53
+ {Array.from({ length: columns }).map((_, col) => (
54
+ <Skeleton
55
+ key={col}
56
+ className={cn("h-4 flex-1 rounded", col === 0 && "max-w-[40%]")}
57
+ />
58
+ ))}
59
+ </div>
60
+ ))}
61
+ </div>
62
+ )
63
+ }
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // KanbanSkeleton – columns with placeholder cards
67
+ // ---------------------------------------------------------------------------
68
+
69
+ export interface KanbanSkeletonProps extends ViewSkeletonProps {
70
+ /** Number of kanban columns to render */
71
+ columns?: number
72
+ /** Number of cards per column */
73
+ cardsPerColumn?: number
74
+ }
75
+
76
+ function KanbanSkeleton({
77
+ columns = 3,
78
+ cardsPerColumn = 3,
79
+ className,
80
+ ...props
81
+ }: KanbanSkeletonProps) {
82
+ return (
83
+ <div
84
+ data-slot="kanban-skeleton"
85
+ className={cn("flex gap-4 overflow-x-auto", className)}
86
+ {...props}
87
+ >
88
+ {Array.from({ length: columns }).map((_, col) => (
89
+ <div
90
+ key={col}
91
+ className="flex w-72 shrink-0 flex-col gap-3 rounded-lg border bg-muted/30 p-3"
92
+ >
93
+ {/* Column header */}
94
+ <Skeleton className="h-5 w-24 rounded" />
95
+
96
+ {/* Cards */}
97
+ {Array.from({ length: cardsPerColumn }).map((_, card) => (
98
+ <div key={card} className="space-y-2 rounded-md border bg-background p-3">
99
+ <Skeleton className="h-4 w-3/4 rounded" />
100
+ <Skeleton className="h-3 w-1/2 rounded" />
101
+ </div>
102
+ ))}
103
+ </div>
104
+ ))}
105
+ </div>
106
+ )
107
+ }
108
+
109
+ // ---------------------------------------------------------------------------
110
+ // FormSkeleton – labeled form fields
111
+ // ---------------------------------------------------------------------------
112
+
113
+ function FormSkeleton({
114
+ rows = 4,
115
+ className,
116
+ ...props
117
+ }: ViewSkeletonProps) {
118
+ return (
119
+ <div
120
+ data-slot="form-skeleton"
121
+ className={cn("w-full max-w-lg space-y-6", className)}
122
+ {...props}
123
+ >
124
+ {Array.from({ length: rows }).map((_, i) => (
125
+ <div key={i} className="space-y-2">
126
+ {/* Label */}
127
+ <Skeleton className="h-4 w-28 rounded" />
128
+ {/* Input */}
129
+ <Skeleton className="h-9 w-full rounded-md" />
130
+ </div>
131
+ ))}
132
+
133
+ {/* Submit button */}
134
+ <Skeleton className="h-9 w-24 rounded-md" />
135
+ </div>
136
+ )
137
+ }
138
+
139
+ // ---------------------------------------------------------------------------
140
+ // ListSkeleton – stacked list items
141
+ // ---------------------------------------------------------------------------
142
+
143
+ function ListSkeleton({
144
+ rows = 5,
145
+ className,
146
+ ...props
147
+ }: ViewSkeletonProps) {
148
+ return (
149
+ <div
150
+ data-slot="list-skeleton"
151
+ className={cn("w-full space-y-3", className)}
152
+ {...props}
153
+ >
154
+ {Array.from({ length: rows }).map((_, i) => (
155
+ <div
156
+ key={i}
157
+ className="flex items-center gap-3 rounded-md border px-4 py-3"
158
+ >
159
+ <Skeleton className="size-8 shrink-0 rounded-full" />
160
+ <div className="flex-1 space-y-2">
161
+ <Skeleton className="h-4 w-3/5 rounded" />
162
+ <Skeleton className="h-3 w-2/5 rounded" />
163
+ </div>
164
+ </div>
165
+ ))}
166
+ </div>
167
+ )
168
+ }
169
+
170
+ // ---------------------------------------------------------------------------
171
+ // ChartSkeleton – chart area placeholder
172
+ // ---------------------------------------------------------------------------
173
+
174
+ function ChartSkeleton({
175
+ className,
176
+ ...props
177
+ }: Omit<ViewSkeletonProps, "rows">) {
178
+ return (
179
+ <div
180
+ data-slot="chart-skeleton"
181
+ className={cn("w-full space-y-4", className)}
182
+ {...props}
183
+ >
184
+ {/* Chart title */}
185
+ <Skeleton className="h-5 w-40 rounded" />
186
+
187
+ {/* Chart area with bar placeholders */}
188
+ <div className="flex h-48 items-end gap-2 rounded-md border p-4">
189
+ {(["h-2/5", "h-3/5", "h-1/3", "h-4/5", "h-1/2", "h-3/4", "h-2/5"] as const).map((heightClass, i) => (
190
+ <Skeleton
191
+ key={i}
192
+ className={cn("flex-1 rounded-t", heightClass)}
193
+ />
194
+ ))}
195
+ </div>
196
+
197
+ {/* Legend */}
198
+ <div className="flex gap-4">
199
+ <Skeleton className="h-3 w-16 rounded" />
200
+ <Skeleton className="h-3 w-16 rounded" />
201
+ <Skeleton className="h-3 w-16 rounded" />
202
+ </div>
203
+ </div>
204
+ )
205
+ }
206
+
207
+ // ---------------------------------------------------------------------------
208
+ // ViewSkeleton – convenience wrapper that dispatches by variant
209
+ // ---------------------------------------------------------------------------
210
+
211
+ export type ViewSkeletonVariant = "grid" | "kanban" | "form" | "list" | "chart"
212
+
213
+ export interface ViewSkeletonDispatchProps extends ViewSkeletonProps {
214
+ variant: ViewSkeletonVariant
215
+ /** Number of columns (grid / kanban) */
216
+ columns?: number
217
+ /** Cards per column (kanban only) */
218
+ cardsPerColumn?: number
219
+ }
220
+
221
+ function ViewSkeleton({ variant, ...props }: ViewSkeletonDispatchProps) {
222
+ switch (variant) {
223
+ case "grid":
224
+ return <GridSkeleton {...props} />
225
+ case "kanban":
226
+ return <KanbanSkeleton {...props} />
227
+ case "form":
228
+ return <FormSkeleton {...props} />
229
+ case "list":
230
+ return <ListSkeleton {...props} />
231
+ case "chart":
232
+ return <ChartSkeleton {...props} />
233
+ }
234
+ }
235
+
236
+ export {
237
+ ViewSkeleton,
238
+ GridSkeleton,
239
+ KanbanSkeleton,
240
+ FormSkeleton,
241
+ ListSkeleton,
242
+ ChartSkeleton,
243
+ }
@@ -0,0 +1,153 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
9
+ import * as React from "react"
10
+ import { Loader2, InboxIcon, AlertCircle } from "lucide-react"
11
+
12
+ import { cn } from "../lib/utils"
13
+ import { Button } from "../ui/button"
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // DataLoadingState
17
+ // ---------------------------------------------------------------------------
18
+
19
+ interface DataLoadingStateProps extends React.ComponentProps<"div"> {
20
+ /** Message displayed below the spinner */
21
+ message?: string
22
+ }
23
+
24
+ function DataLoadingState({
25
+ className,
26
+ message = "Loading…",
27
+ ...props
28
+ }: DataLoadingStateProps) {
29
+ return (
30
+ <div
31
+ role="status"
32
+ aria-label={message}
33
+ data-slot="data-loading-state"
34
+ className={cn(
35
+ "flex flex-col items-center justify-center gap-3 p-6 text-center",
36
+ className
37
+ )}
38
+ {...props}
39
+ >
40
+ <Loader2 className="size-6 animate-spin text-muted-foreground" />
41
+ {message && (
42
+ <p className="text-sm text-muted-foreground">{message}</p>
43
+ )}
44
+ </div>
45
+ )
46
+ }
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // DataEmptyState
50
+ // ---------------------------------------------------------------------------
51
+
52
+ interface DataEmptyStateProps extends React.ComponentProps<"div"> {
53
+ /** Icon rendered above the title */
54
+ icon?: React.ReactNode
55
+ title?: string
56
+ description?: string
57
+ /** Optional action rendered below the description */
58
+ action?: React.ReactNode
59
+ }
60
+
61
+ function DataEmptyState({
62
+ className,
63
+ icon,
64
+ title = "No data",
65
+ description,
66
+ action,
67
+ children,
68
+ ...props
69
+ }: DataEmptyStateProps) {
70
+ return (
71
+ <div
72
+ data-slot="data-empty-state"
73
+ className={cn(
74
+ "flex flex-col items-center justify-center gap-3 p-6 text-center",
75
+ className
76
+ )}
77
+ {...props}
78
+ >
79
+ <div className="flex size-10 items-center justify-center rounded-lg bg-muted">
80
+ {icon ?? <InboxIcon className="size-5 text-muted-foreground" />}
81
+ </div>
82
+ {title && (
83
+ <h3 className="text-sm font-medium">{title}</h3>
84
+ )}
85
+ {description && (
86
+ <p className="max-w-sm text-sm text-muted-foreground">{description}</p>
87
+ )}
88
+ {action}
89
+ {children}
90
+ </div>
91
+ )
92
+ }
93
+
94
+ // ---------------------------------------------------------------------------
95
+ // DataErrorState
96
+ // ---------------------------------------------------------------------------
97
+
98
+ interface DataErrorStateProps extends React.ComponentProps<"div"> {
99
+ title?: string
100
+ /** Error message or description */
101
+ message?: string
102
+ /** Callback invoked when the retry button is clicked */
103
+ onRetry?: () => void
104
+ /** Label for the retry button */
105
+ retryLabel?: string
106
+ }
107
+
108
+ function DataErrorState({
109
+ className,
110
+ title = "Something went wrong",
111
+ message,
112
+ onRetry,
113
+ retryLabel = "Retry",
114
+ children,
115
+ ...props
116
+ }: DataErrorStateProps) {
117
+ return (
118
+ <div
119
+ role="alert"
120
+ data-slot="data-error-state"
121
+ className={cn(
122
+ "flex flex-col items-center justify-center gap-3 p-6 text-center",
123
+ className
124
+ )}
125
+ {...props}
126
+ >
127
+ <div className="flex size-10 items-center justify-center rounded-lg bg-destructive/10">
128
+ <AlertCircle className="size-5 text-destructive" />
129
+ </div>
130
+ {title && (
131
+ <h3 className="text-sm font-medium">{title}</h3>
132
+ )}
133
+ {message && (
134
+ <p className="max-w-sm text-sm text-muted-foreground">{message}</p>
135
+ )}
136
+ {onRetry && (
137
+ <Button variant="outline" size="sm" onClick={onRetry}>
138
+ {retryLabel}
139
+ </Button>
140
+ )}
141
+ {children}
142
+ </div>
143
+ )
144
+ }
145
+
146
+ export {
147
+ DataLoadingState,
148
+ DataEmptyState,
149
+ DataErrorState,
150
+ type DataLoadingStateProps,
151
+ type DataEmptyStateProps,
152
+ type DataErrorStateProps,
153
+ }
package/src/index.ts CHANGED
@@ -14,6 +14,7 @@ import './renderers';
14
14
  // Export utils
15
15
  export { cn } from './lib/utils';
16
16
  export { renderChildren } from './lib/utils';
17
+ export { cva } from 'class-variance-authority';
17
18
 
18
19
  // Export placeholder registration
19
20
  export { registerPlaceholders } from './renderers/placeholders';
@@ -0,0 +1,147 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
9
+ /**
10
+ * action:button — Smart action button driven by ActionSchema.
11
+ *
12
+ * Renders a Shadcn Button wired to the ActionRunner. Supports:
13
+ * - All 5 spec action types (script, url, modal, flow, api)
14
+ * - Conditional visibility & enabled state
15
+ * - Loading indicator during async execution
16
+ * - Icon rendering via Lucide
17
+ * - Variant / size / className overrides from schema
18
+ */
19
+
20
+ import React, { forwardRef, useCallback, useState } from 'react';
21
+ import { ComponentRegistry } from '@object-ui/core';
22
+ import type { ActionSchema } from '@object-ui/types';
23
+ import { useAction } from '@object-ui/react';
24
+ import { useCondition } from '@object-ui/react';
25
+ import { Button } from '../../ui';
26
+ import { cn } from '../../lib/utils';
27
+ import { Loader2 } from 'lucide-react';
28
+ import { resolveIcon } from './resolve-icon';
29
+
30
+ export interface ActionButtonProps {
31
+ schema: ActionSchema & { type: string; className?: string };
32
+ className?: string;
33
+ /** Override context for this specific action */
34
+ context?: Record<string, any>;
35
+ [key: string]: any;
36
+ }
37
+
38
+ const ActionButtonRenderer = forwardRef<HTMLButtonElement, ActionButtonProps>(
39
+ ({ schema, className, context: localContext, ...props }, ref) => {
40
+ const {
41
+ 'data-obj-id': dataObjId,
42
+ 'data-obj-type': dataObjType,
43
+ style,
44
+ ...rest
45
+ } = props;
46
+
47
+ const { execute } = useAction();
48
+ const [loading, setLoading] = useState(false);
49
+
50
+ // Evaluate visibility and enabled conditions
51
+ const isVisible = useCondition(schema.visible ? `\${${schema.visible}}` : undefined);
52
+ const isEnabled = useCondition(schema.enabled ? `\${${schema.enabled}}` : undefined);
53
+
54
+ // Resolve icon
55
+ const Icon = resolveIcon(schema.icon);
56
+
57
+ // Map schema variant to Shadcn button variant
58
+ const variant = schema.variant === 'primary' ? 'default' : (schema.variant || 'default');
59
+ const size = schema.size === 'md' ? 'default' : (schema.size || 'default');
60
+
61
+ const handleClick = useCallback(async () => {
62
+ if (loading) return;
63
+ setLoading(true);
64
+
65
+ try {
66
+ await execute({
67
+ type: schema.type,
68
+ name: schema.name,
69
+ target: schema.target,
70
+ execute: schema.execute,
71
+ endpoint: schema.endpoint,
72
+ method: schema.method,
73
+ params: schema.params as Record<string, any> | undefined,
74
+ confirmText: schema.confirmText,
75
+ successMessage: schema.successMessage,
76
+ errorMessage: schema.errorMessage,
77
+ refreshAfter: schema.refreshAfter,
78
+ toast: schema.toast,
79
+ ...localContext,
80
+ });
81
+ } finally {
82
+ setLoading(false);
83
+ }
84
+ }, [schema, execute, loading, localContext]);
85
+
86
+ if (schema.visible && !isVisible) return null;
87
+
88
+ return (
89
+ <Button
90
+ ref={ref}
91
+ type="button"
92
+ variant={variant as any}
93
+ size={size as any}
94
+ className={cn(schema.className, className)}
95
+ disabled={(schema.enabled ? !isEnabled : false) || loading}
96
+ onClick={handleClick}
97
+ {...rest}
98
+ {...{ 'data-obj-id': dataObjId, 'data-obj-type': dataObjType, style }}
99
+ >
100
+ {loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
101
+ {!loading && Icon && <Icon className={cn('h-4 w-4', schema.label && 'mr-2')} />}
102
+ {schema.label}
103
+ </Button>
104
+ );
105
+ },
106
+ );
107
+
108
+ ActionButtonRenderer.displayName = 'ActionButtonRenderer';
109
+
110
+ ComponentRegistry.register('action:button', ActionButtonRenderer, {
111
+ namespace: 'action',
112
+ label: 'Action Button',
113
+ inputs: [
114
+ { name: 'name', type: 'string', label: 'Action Name' },
115
+ { name: 'label', type: 'string', label: 'Label', defaultValue: 'Action' },
116
+ { name: 'icon', type: 'string', label: 'Icon' },
117
+ {
118
+ name: 'type',
119
+ type: 'enum',
120
+ label: 'Action Type',
121
+ enum: ['script', 'url', 'modal', 'flow', 'api'],
122
+ defaultValue: 'script',
123
+ },
124
+ { name: 'target', type: 'string', label: 'Target' },
125
+ {
126
+ name: 'variant',
127
+ type: 'enum',
128
+ label: 'Variant',
129
+ enum: ['default', 'primary', 'secondary', 'destructive', 'outline', 'ghost'],
130
+ defaultValue: 'default',
131
+ },
132
+ {
133
+ name: 'size',
134
+ type: 'enum',
135
+ label: 'Size',
136
+ enum: ['sm', 'md', 'lg'],
137
+ defaultValue: 'md',
138
+ },
139
+ { name: 'className', type: 'string', label: 'CSS Class', advanced: true },
140
+ ],
141
+ defaultProps: {
142
+ label: 'Action',
143
+ type: 'script',
144
+ variant: 'default',
145
+ size: 'md',
146
+ },
147
+ });