@asteby/metacore-runtime-react 23.5.1 → 23.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.
- package/CHANGELOG.md +16 -0
- package/dist/action-modal-dispatcher.js +8 -7
- package/dist/custom-stages.d.ts +171 -0
- package/dist/custom-stages.d.ts.map +1 -0
- package/dist/custom-stages.js +535 -0
- package/dist/dialogs/dynamic-record.d.ts.map +1 -1
- package/dist/dialogs/dynamic-record.js +7 -4
- package/dist/dynamic-kanban.d.ts.map +1 -1
- package/dist/dynamic-kanban.js +81 -32
- package/dist/field-grid.d.ts +17 -0
- package/dist/field-grid.d.ts.map +1 -0
- package/dist/field-grid.js +12 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/stage-automations.d.ts +75 -0
- package/dist/stage-automations.d.ts.map +1 -0
- package/dist/stage-automations.js +264 -0
- package/dist/types.d.ts +29 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/action-modal-grid-layout.test.tsx +86 -0
- package/src/__tests__/custom-stages.test.tsx +480 -0
- package/src/__tests__/stage-automations.test.tsx +243 -0
- package/src/action-modal-dispatcher.tsx +29 -30
- package/src/custom-stages.tsx +1113 -0
- package/src/dialogs/dynamic-record.tsx +14 -8
- package/src/dynamic-kanban.tsx +203 -5
- package/src/field-grid.tsx +57 -0
- package/src/index.ts +39 -0
- package/src/stage-automations.tsx +580 -0
- package/src/types.ts +26 -0
|
@@ -52,6 +52,7 @@ import { DynamicSelectField, OptionLead, OptionThumb } from '../dynamic-select-f
|
|
|
52
52
|
import { DynamicRelations } from '../dynamic-relations'
|
|
53
53
|
import { useOptionsResolver, type ResolvedOption } from '../use-options-resolver'
|
|
54
54
|
import { getFieldRef } from '../dynamic-form-schema'
|
|
55
|
+
import { FieldCell } from '../field-grid'
|
|
55
56
|
import { isNilUuid, normalizeNilUuid } from '../nil-uuid'
|
|
56
57
|
import { DynamicIcon, isLucideIconName } from '../dynamic-icon'
|
|
57
58
|
import { humanizeToken } from '../dynamic-columns-helpers'
|
|
@@ -767,18 +768,22 @@ export function DynamicRecordDialog({
|
|
|
767
768
|
<ImageUrlContext.Provider value={getImageUrl}>
|
|
768
769
|
<TimeZoneContext.Provider value={timeZone}>
|
|
769
770
|
<CurrencyContext.Provider value={currency}>
|
|
771
|
+
{/* The grid IS the form element (the footer submit
|
|
772
|
+
button targets it by id). FieldCell gives each
|
|
773
|
+
cell `min-w-0` so a long select/input value can't
|
|
774
|
+
blow the two columns past the dialog width. */}
|
|
770
775
|
<form
|
|
771
776
|
id="dynamic-record-form"
|
|
772
777
|
onSubmit={handleSubmit}
|
|
773
|
-
className="grid grid-cols-1
|
|
778
|
+
className="grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-2"
|
|
774
779
|
>
|
|
775
780
|
{visibleFields.map(field => {
|
|
776
|
-
const isFullWidth =
|
|
781
|
+
const isFullWidth =
|
|
782
|
+
field.type === 'textarea' ||
|
|
783
|
+
field.widget === 'textarea' ||
|
|
784
|
+
field.widget === 'richtext'
|
|
777
785
|
return (
|
|
778
|
-
<
|
|
779
|
-
key={field.key}
|
|
780
|
-
className={isFullWidth ? 'sm:col-span-2' : ''}
|
|
781
|
-
>
|
|
786
|
+
<FieldCell key={field.key} fullWidth={isFullWidth}>
|
|
782
787
|
<FieldRow
|
|
783
788
|
field={field}
|
|
784
789
|
record={record}
|
|
@@ -788,12 +793,12 @@ export function DynamicRecordDialog({
|
|
|
788
793
|
setFormValues((prev: Record<string, any>) => ({ ...prev, [field.key]: val }))
|
|
789
794
|
}
|
|
790
795
|
/>
|
|
791
|
-
</
|
|
796
|
+
</FieldCell>
|
|
792
797
|
)
|
|
793
798
|
})}
|
|
794
799
|
|
|
795
800
|
{record?.external_url && (
|
|
796
|
-
<div className="sm:col-span-2">
|
|
801
|
+
<div className="sm:col-span-2 min-w-0">
|
|
797
802
|
<a
|
|
798
803
|
href={record.external_url}
|
|
799
804
|
target="_blank"
|
|
@@ -807,6 +812,7 @@ export function DynamicRecordDialog({
|
|
|
807
812
|
)}
|
|
808
813
|
</form>
|
|
809
814
|
|
|
815
|
+
|
|
810
816
|
{/* Child records (line items, etc.) for declared relations.
|
|
811
817
|
View = strictly read-only; edit = add/edit/delete. */}
|
|
812
818
|
{!isCreate && record && relations.length > 0 && (
|
package/src/dynamic-kanban.tsx
CHANGED
|
@@ -79,6 +79,24 @@ import {
|
|
|
79
79
|
import { ColumnFilterControl, FilterValueCombobox, type ColumnFilterType } from '@asteby/metacore-ui/data-table'
|
|
80
80
|
import { generateBadgeStyles, optionColor } from '@asteby/metacore-ui/lib'
|
|
81
81
|
import { useApi } from './api-context'
|
|
82
|
+
import {
|
|
83
|
+
useStageAutomations,
|
|
84
|
+
StageAutomationsButton,
|
|
85
|
+
type StageAutomation,
|
|
86
|
+
type NewStageAutomation,
|
|
87
|
+
} from './stage-automations'
|
|
88
|
+
import {
|
|
89
|
+
useCustomStages,
|
|
90
|
+
splitCustomStages,
|
|
91
|
+
mergeLaneStages,
|
|
92
|
+
resolveSmartLanes,
|
|
93
|
+
AddStageColumn,
|
|
94
|
+
CustomStageLaneMenu,
|
|
95
|
+
CustomStageDialog,
|
|
96
|
+
CustomStageDeleteDialog,
|
|
97
|
+
SmartLane,
|
|
98
|
+
type CustomStage,
|
|
99
|
+
} from './custom-stages'
|
|
82
100
|
import { useDynamicFilters } from './use-dynamic-filters'
|
|
83
101
|
import {
|
|
84
102
|
FilterChipsRow,
|
|
@@ -453,6 +471,30 @@ export function DynamicKanban({
|
|
|
453
471
|
const api = useApi()
|
|
454
472
|
const isDark = useIsDarkTheme()
|
|
455
473
|
|
|
474
|
+
// Stage automations (Bitrix-style per-lane rules). Degrades to no-op when
|
|
475
|
+
// the host has no `/stage-automations` endpoint — the ⚡ affordance hides.
|
|
476
|
+
const automations = useStageAutomations(model)
|
|
477
|
+
|
|
478
|
+
// Custom stages (Bitrix-style user-defined columns). Degrades to no-op when
|
|
479
|
+
// the host has no `/custom-stages` endpoint — the "+ Agregar etapa" column
|
|
480
|
+
// and lane menus simply don't render.
|
|
481
|
+
const customStages = useCustomStages(model)
|
|
482
|
+
// Dialog state: create/edit a stage, and the delete confirmation.
|
|
483
|
+
const [stageDialogOpen, setStageDialogOpen] = useState(false)
|
|
484
|
+
const [editingStage, setEditingStage] = useState<CustomStage | null>(null)
|
|
485
|
+
const [deletingStage, setDeletingStage] = useState<CustomStage | null>(null)
|
|
486
|
+
const openCreateStage = useCallback(() => {
|
|
487
|
+
setEditingStage(null)
|
|
488
|
+
setStageDialogOpen(true)
|
|
489
|
+
}, [])
|
|
490
|
+
const openEditStage = useCallback((s: CustomStage) => {
|
|
491
|
+
setEditingStage(s)
|
|
492
|
+
setStageDialogOpen(true)
|
|
493
|
+
}, [])
|
|
494
|
+
const openDeleteStage = useCallback((s: CustomStage) => {
|
|
495
|
+
setDeletingStage(s)
|
|
496
|
+
}, [])
|
|
497
|
+
|
|
456
498
|
const { getMetadata, setMetadata: cacheMetadata } = useMetadataCache()
|
|
457
499
|
const cachedMeta = getMetadata(model)
|
|
458
500
|
|
|
@@ -747,10 +789,34 @@ export function DynamicKanban({
|
|
|
747
789
|
[],
|
|
748
790
|
)
|
|
749
791
|
|
|
750
|
-
const
|
|
792
|
+
const declaredStages = useMemo(
|
|
751
793
|
() => (metadata ? deriveStages(metadata) : []),
|
|
752
794
|
[metadata],
|
|
753
795
|
)
|
|
796
|
+
// The kernel merges custom real stages into `metadata.stages` (custom: true)
|
|
797
|
+
// and serves smart lanes in `metadata.smart_lanes` — the metadata is the
|
|
798
|
+
// painting source (ops #704). The CRUD list (`customStages.stages`) only
|
|
799
|
+
// backs the management dialog: it carries the `id`/filters the metadata
|
|
800
|
+
// omits, so we still split it to build `customByKey` (and to fall back when
|
|
801
|
+
// the metadata hasn't caught up yet).
|
|
802
|
+
const { laneStages: customLaneStages, smartStages: crudSmartStages } =
|
|
803
|
+
useMemo(() => splitCustomStages(customStages.stages), [customStages.stages])
|
|
804
|
+
const { lanes: stages, customByKey } = useMemo(
|
|
805
|
+
() => mergeLaneStages(declaredStages, customLaneStages),
|
|
806
|
+
[declaredStages, customLaneStages],
|
|
807
|
+
)
|
|
808
|
+
// Smart lanes to paint: metadata first, folding in CRUD ids by key.
|
|
809
|
+
const smartStages = useMemo(
|
|
810
|
+
() =>
|
|
811
|
+
resolveSmartLanes(metadata?.smart_lanes, crudSmartStages, model),
|
|
812
|
+
[metadata?.smart_lanes, crudSmartStages, model],
|
|
813
|
+
)
|
|
814
|
+
// Position a newly created stage after every existing lane (real + smart).
|
|
815
|
+
const nextStagePosition = useMemo(() => {
|
|
816
|
+
const positions = customStages.stages.map((s) => s.position ?? 0)
|
|
817
|
+
const base = stages.length + smartStages.length
|
|
818
|
+
return Math.max(base, ...positions, 0) + 1
|
|
819
|
+
}, [customStages.stages, stages.length, smartStages.length])
|
|
754
820
|
const transitions = metadata?.transitions
|
|
755
821
|
|
|
756
822
|
const grouped = useMemo(
|
|
@@ -1075,6 +1141,22 @@ export function DynamicKanban({
|
|
|
1075
1141
|
isDark={isDark}
|
|
1076
1142
|
dimmed={!!activeId && !droppableAllowed}
|
|
1077
1143
|
disabled={!!activeId && !droppableAllowed}
|
|
1144
|
+
model={model}
|
|
1145
|
+
columns={metadata?.columns ?? []}
|
|
1146
|
+
automationsAvailable={
|
|
1147
|
+
automations.available && stage.key !== UNASSIGNED_LANE
|
|
1148
|
+
}
|
|
1149
|
+
automationRules={automations.byStage.get(stage.key) ?? []}
|
|
1150
|
+
onAutomationCreate={automations.create}
|
|
1151
|
+
onAutomationUpdate={automations.update}
|
|
1152
|
+
onAutomationRemove={automations.remove}
|
|
1153
|
+
customStage={
|
|
1154
|
+
customStages.available
|
|
1155
|
+
? customByKey.get(stage.key)
|
|
1156
|
+
: undefined
|
|
1157
|
+
}
|
|
1158
|
+
onEditStage={openEditStage}
|
|
1159
|
+
onDeleteStage={openDeleteStage}
|
|
1078
1160
|
>
|
|
1079
1161
|
{loadingData && cards.length === 0 ? (
|
|
1080
1162
|
<>
|
|
@@ -1104,6 +1186,42 @@ export function DynamicKanban({
|
|
|
1104
1186
|
</KanbanLane>
|
|
1105
1187
|
)
|
|
1106
1188
|
})}
|
|
1189
|
+
|
|
1190
|
+
{/* Smart (virtual) lanes — each runs its own filtered query and
|
|
1191
|
+
renders read-only cards (no drag target). */}
|
|
1192
|
+
{smartStages.map((smart) => (
|
|
1193
|
+
<SmartLane
|
|
1194
|
+
key={`smart-${smart.key}`}
|
|
1195
|
+
stage={smart}
|
|
1196
|
+
model={model}
|
|
1197
|
+
endpoint={endpoint}
|
|
1198
|
+
defaultFilters={defaultFilters}
|
|
1199
|
+
pageSize={pageSize}
|
|
1200
|
+
isDark={isDark}
|
|
1201
|
+
refreshTrigger={refreshTrigger}
|
|
1202
|
+
onEdit={openEditStage}
|
|
1203
|
+
onDelete={openDeleteStage}
|
|
1204
|
+
renderCard={(card) => (
|
|
1205
|
+
<KanbanCard
|
|
1206
|
+
card={card}
|
|
1207
|
+
titleCol={titleCol}
|
|
1208
|
+
fieldCols={fieldCols}
|
|
1209
|
+
actions={rowActions}
|
|
1210
|
+
locale={i18n.language}
|
|
1211
|
+
timeZone={timeZone}
|
|
1212
|
+
currency={currency}
|
|
1213
|
+
onClick={onCardClick}
|
|
1214
|
+
onAction={handleInternalAction}
|
|
1215
|
+
draggable={false}
|
|
1216
|
+
/>
|
|
1217
|
+
)}
|
|
1218
|
+
/>
|
|
1219
|
+
))}
|
|
1220
|
+
|
|
1221
|
+
{/* "+ Agregar etapa" — only when the host wired /custom-stages. */}
|
|
1222
|
+
{customStages.available && (
|
|
1223
|
+
<AddStageColumn onClick={openCreateStage} />
|
|
1224
|
+
)}
|
|
1107
1225
|
</div>
|
|
1108
1226
|
|
|
1109
1227
|
<DragOverlay>
|
|
@@ -1121,6 +1239,35 @@ export function DynamicKanban({
|
|
|
1121
1239
|
|
|
1122
1240
|
{rowActionDialogs}
|
|
1123
1241
|
</DndContext>
|
|
1242
|
+
|
|
1243
|
+
{customStages.available && (
|
|
1244
|
+
<>
|
|
1245
|
+
<CustomStageDialog
|
|
1246
|
+
open={stageDialogOpen}
|
|
1247
|
+
onOpenChange={setStageDialogOpen}
|
|
1248
|
+
model={model}
|
|
1249
|
+
columns={metadata?.columns ?? []}
|
|
1250
|
+
initial={editingStage}
|
|
1251
|
+
nextPosition={nextStagePosition}
|
|
1252
|
+
onCreate={customStages.create}
|
|
1253
|
+
onUpdate={customStages.update}
|
|
1254
|
+
/>
|
|
1255
|
+
<CustomStageDeleteDialog
|
|
1256
|
+
open={!!deletingStage}
|
|
1257
|
+
onOpenChange={(o) => {
|
|
1258
|
+
if (!o) setDeletingStage(null)
|
|
1259
|
+
}}
|
|
1260
|
+
stage={deletingStage}
|
|
1261
|
+
reassignTargets={stages.map((s) => ({
|
|
1262
|
+
key: s.key,
|
|
1263
|
+
label: s.label,
|
|
1264
|
+
}))}
|
|
1265
|
+
onConfirm={(s, reassignTo) =>
|
|
1266
|
+
customStages.remove(s.id, reassignTo)
|
|
1267
|
+
}
|
|
1268
|
+
/>
|
|
1269
|
+
</>
|
|
1270
|
+
)}
|
|
1124
1271
|
</div>
|
|
1125
1272
|
)
|
|
1126
1273
|
}
|
|
@@ -1243,6 +1390,22 @@ interface KanbanLaneProps {
|
|
|
1243
1390
|
isDark: boolean
|
|
1244
1391
|
dimmed: boolean
|
|
1245
1392
|
disabled: boolean
|
|
1393
|
+
/** Model key + columns for the stage-automations editor. */
|
|
1394
|
+
model: string
|
|
1395
|
+
columns: ColumnDefinition[]
|
|
1396
|
+
/** Whether the ⚡ automations affordance should render for this lane. */
|
|
1397
|
+
automationsAvailable: boolean
|
|
1398
|
+
automationRules: StageAutomation[]
|
|
1399
|
+
onAutomationCreate: (draft: NewStageAutomation) => Promise<void>
|
|
1400
|
+
onAutomationUpdate: (
|
|
1401
|
+
id: StageAutomation['id'],
|
|
1402
|
+
patch: Partial<StageAutomation>,
|
|
1403
|
+
) => Promise<void>
|
|
1404
|
+
onAutomationRemove: (id: StageAutomation['id']) => Promise<void>
|
|
1405
|
+
/** When set, this lane is a user-defined custom stage → show ⋮ Editar/Eliminar. */
|
|
1406
|
+
customStage?: CustomStage
|
|
1407
|
+
onEditStage?: (stage: CustomStage) => void
|
|
1408
|
+
onDeleteStage?: (stage: CustomStage) => void
|
|
1246
1409
|
children: React.ReactNode
|
|
1247
1410
|
}
|
|
1248
1411
|
|
|
@@ -1261,6 +1424,16 @@ function KanbanLane({
|
|
|
1261
1424
|
isDark,
|
|
1262
1425
|
dimmed,
|
|
1263
1426
|
disabled,
|
|
1427
|
+
model,
|
|
1428
|
+
columns,
|
|
1429
|
+
automationsAvailable,
|
|
1430
|
+
automationRules,
|
|
1431
|
+
onAutomationCreate,
|
|
1432
|
+
onAutomationUpdate,
|
|
1433
|
+
onAutomationRemove,
|
|
1434
|
+
customStage,
|
|
1435
|
+
onEditStage,
|
|
1436
|
+
onDeleteStage,
|
|
1264
1437
|
children,
|
|
1265
1438
|
}: KanbanLaneProps) {
|
|
1266
1439
|
const { t } = useTranslation()
|
|
@@ -1359,6 +1532,25 @@ function KanbanLane({
|
|
|
1359
1532
|
value={funnelValue}
|
|
1360
1533
|
onChange={onFunnelChange}
|
|
1361
1534
|
/>
|
|
1535
|
+
{automationsAvailable && (
|
|
1536
|
+
<StageAutomationsButton
|
|
1537
|
+
model={model}
|
|
1538
|
+
stageKey={stage.key}
|
|
1539
|
+
stageLabel={stage.label}
|
|
1540
|
+
columns={columns}
|
|
1541
|
+
rules={automationRules}
|
|
1542
|
+
onCreate={onAutomationCreate}
|
|
1543
|
+
onUpdate={onAutomationUpdate}
|
|
1544
|
+
onRemove={onAutomationRemove}
|
|
1545
|
+
/>
|
|
1546
|
+
)}
|
|
1547
|
+
{customStage && onEditStage && onDeleteStage && (
|
|
1548
|
+
<CustomStageLaneMenu
|
|
1549
|
+
stage={customStage}
|
|
1550
|
+
onEdit={onEditStage}
|
|
1551
|
+
onDelete={onDeleteStage}
|
|
1552
|
+
/>
|
|
1553
|
+
)}
|
|
1362
1554
|
</div>
|
|
1363
1555
|
</div>
|
|
1364
1556
|
{searchOpen && (
|
|
@@ -1590,6 +1782,8 @@ interface KanbanCardProps {
|
|
|
1590
1782
|
onClick?: (row: any) => void
|
|
1591
1783
|
/** STRING contract — dispatch the action by its key (see useDynamicRowActions). */
|
|
1592
1784
|
onAction: (actionKey: string, record: any) => void
|
|
1785
|
+
/** When false the card is static (no drag) — used by read-only smart lanes. */
|
|
1786
|
+
draggable?: boolean
|
|
1593
1787
|
}
|
|
1594
1788
|
|
|
1595
1789
|
function KanbanCard({
|
|
@@ -1602,19 +1796,23 @@ function KanbanCard({
|
|
|
1602
1796
|
currency,
|
|
1603
1797
|
onClick,
|
|
1604
1798
|
onAction,
|
|
1799
|
+
draggable = true,
|
|
1605
1800
|
}: KanbanCardProps) {
|
|
1606
1801
|
const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
|
|
1607
1802
|
id: String(card.id),
|
|
1803
|
+
disabled: !draggable,
|
|
1608
1804
|
})
|
|
1609
1805
|
|
|
1610
1806
|
const visibleActions = actions.filter((a) => isRowActionVisible(a, card))
|
|
1611
1807
|
|
|
1612
1808
|
return (
|
|
1613
1809
|
<Card
|
|
1614
|
-
ref={setNodeRef}
|
|
1615
|
-
{...attributes}
|
|
1616
|
-
{...listeners}
|
|
1617
|
-
className=
|
|
1810
|
+
ref={draggable ? setNodeRef : undefined}
|
|
1811
|
+
{...(draggable ? attributes : {})}
|
|
1812
|
+
{...(draggable ? listeners : {})}
|
|
1813
|
+
className={`w-full min-w-0 border-border/70 shadow-sm ${
|
|
1814
|
+
draggable ? 'cursor-grab active:cursor-grabbing' : 'cursor-default'
|
|
1815
|
+
}`}
|
|
1618
1816
|
style={{ opacity: isDragging ? 0.4 : 1 }}
|
|
1619
1817
|
onClick={() => onClick?.(card)}
|
|
1620
1818
|
data-card-id={String(card.id)}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// FieldGrid — the shared, responsive form layout for the SDK's declarative
|
|
2
|
+
// modals (the automatic CRUD create/edit dialog and the placement:create /
|
|
3
|
+
// generic action modal). One source of truth so both read identically: short
|
|
4
|
+
// scalar fields (inputs, selects, dates) flow in two columns when the width
|
|
5
|
+
// allows, textareas / line-items / long-form widgets span the full row, and
|
|
6
|
+
// everything collapses to a single column on phones.
|
|
7
|
+
//
|
|
8
|
+
// The load-bearing detail is `min-w-0` on every cell: CSS grid tracks default
|
|
9
|
+
// to `minmax(auto, 1fr)`, so a child with a wide intrinsic minimum (a Select
|
|
10
|
+
// with a long option, an Input with a long value) pushes its column past the
|
|
11
|
+
// container and the whole dialog grows a horizontal scrollbar. `min-w-0` lets
|
|
12
|
+
// the cell shrink below its content and keeps the modal within its width.
|
|
13
|
+
import type { ReactNode } from 'react'
|
|
14
|
+
import { Label } from '@asteby/metacore-ui/primitives'
|
|
15
|
+
import { cn } from '@asteby/metacore-ui/lib'
|
|
16
|
+
|
|
17
|
+
export function FieldGrid({ children, className }: { children: ReactNode; className?: string }) {
|
|
18
|
+
return (
|
|
19
|
+
<div className={cn('grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-2', className)}>
|
|
20
|
+
{children}
|
|
21
|
+
</div>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function FieldCell({
|
|
26
|
+
children,
|
|
27
|
+
fullWidth,
|
|
28
|
+
className,
|
|
29
|
+
}: {
|
|
30
|
+
children: ReactNode
|
|
31
|
+
/** Span both columns — textareas, line-items grids, embedded relations. */
|
|
32
|
+
fullWidth?: boolean
|
|
33
|
+
className?: string
|
|
34
|
+
}) {
|
|
35
|
+
return (
|
|
36
|
+
<div className={cn('flex min-w-0 flex-col gap-1.5', fullWidth && 'sm:col-span-2', className)}>
|
|
37
|
+
{children}
|
|
38
|
+
</div>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function FieldLabel({
|
|
43
|
+
htmlFor,
|
|
44
|
+
required,
|
|
45
|
+
children,
|
|
46
|
+
}: {
|
|
47
|
+
htmlFor?: string
|
|
48
|
+
required?: boolean
|
|
49
|
+
children: ReactNode
|
|
50
|
+
}) {
|
|
51
|
+
return (
|
|
52
|
+
<Label htmlFor={htmlFor} className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
|
53
|
+
{children}
|
|
54
|
+
{required && <span className="ml-0.5 text-destructive">*</span>}
|
|
55
|
+
</Label>
|
|
56
|
+
)
|
|
57
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -16,6 +16,44 @@ export {
|
|
|
16
16
|
selectCardColumns,
|
|
17
17
|
UNASSIGNED_LANE,
|
|
18
18
|
} from './dynamic-kanban'
|
|
19
|
+
export {
|
|
20
|
+
useStageAutomations,
|
|
21
|
+
StageAutomationsButton,
|
|
22
|
+
isTagColumn,
|
|
23
|
+
automationFieldOptions,
|
|
24
|
+
groupAutomationsByStage,
|
|
25
|
+
activeAutomationCount,
|
|
26
|
+
type StageAutomation,
|
|
27
|
+
type StageAutomationAction,
|
|
28
|
+
type StageAutomationActionType,
|
|
29
|
+
type NewStageAutomation,
|
|
30
|
+
type UseStageAutomationsResult,
|
|
31
|
+
type StageAutomationsButtonProps,
|
|
32
|
+
} from './stage-automations'
|
|
33
|
+
export {
|
|
34
|
+
useCustomStages,
|
|
35
|
+
splitCustomStages,
|
|
36
|
+
mergeLaneStages,
|
|
37
|
+
resolveSmartLanes,
|
|
38
|
+
smartLaneParams,
|
|
39
|
+
customStageFilterFields,
|
|
40
|
+
isCustomStageDraftValid,
|
|
41
|
+
slugifyStageKey,
|
|
42
|
+
emptyCustomStageFilter,
|
|
43
|
+
AddStageColumn,
|
|
44
|
+
CustomStageLaneMenu,
|
|
45
|
+
CustomStageDialog,
|
|
46
|
+
CustomStageDeleteDialog,
|
|
47
|
+
SmartLane,
|
|
48
|
+
CUSTOM_STAGE_COLORS,
|
|
49
|
+
CUSTOM_STAGE_FILTER_OPS,
|
|
50
|
+
type CustomStage,
|
|
51
|
+
type NewCustomStage,
|
|
52
|
+
type CustomStageType,
|
|
53
|
+
type CustomStageFilter,
|
|
54
|
+
type CustomStageFilterOp,
|
|
55
|
+
type UseCustomStagesResult,
|
|
56
|
+
} from './custom-stages'
|
|
19
57
|
export {
|
|
20
58
|
DynamicView,
|
|
21
59
|
resolveViewRenderer,
|
|
@@ -29,6 +67,7 @@ export {
|
|
|
29
67
|
type UseDynamicFiltersResult,
|
|
30
68
|
} from './use-dynamic-filters'
|
|
31
69
|
export * from './dynamic-form'
|
|
70
|
+
export { FieldGrid, FieldCell, FieldLabel } from './field-grid'
|
|
32
71
|
export {
|
|
33
72
|
ActionModalDispatcher,
|
|
34
73
|
type ActionModalProps,
|