@firecms/core 3.0.0-canary.178 → 3.0.0-canary.179
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/dist/components/PropertyIdCopyTooltip.d.ts +1 -1
- package/dist/index.es.js +427 -420
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +426 -419
- package/dist/index.umd.js.map +1 -1
- package/dist/types/properties.d.ts +5 -0
- package/package.json +5 -5
- package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +4 -2
- package/src/components/PropertyIdCopyTooltip.tsx +2 -3
- package/src/core/EntityEditView.tsx +39 -29
- package/src/form/PropertyFieldBinding.tsx +11 -9
- package/src/form/components/LabelWithIcon.tsx +6 -3
- package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +1 -1
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +1 -1
- package/src/form/field_bindings/MapFieldBinding.tsx +9 -10
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +1 -1
- package/src/form/field_bindings/MultiSelectFieldBinding.tsx +1 -1
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +2 -2
- package/src/form/field_bindings/ReferenceFieldBinding.tsx +1 -1
- package/src/form/field_bindings/RepeatFieldBinding.tsx +2 -1
- package/src/form/field_bindings/SelectFieldBinding.tsx +1 -1
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +1 -1
- package/src/form/field_bindings/SwitchFieldBinding.tsx +1 -0
- package/src/form/field_bindings/TextFieldBinding.tsx +1 -0
- package/src/types/properties.ts +6 -0
- package/src/util/join_collections.ts +3 -0
- package/src/util/parent_references_from_path.ts +3 -3
|
@@ -103,6 +103,11 @@ export interface BaseProperty<T extends CMSType, CustomProps = any> {
|
|
|
103
103
|
* save the new config. The saved config will then become the source of truth.
|
|
104
104
|
*/
|
|
105
105
|
editable?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* A number between 0 and 100 that indicates the width of the field in the form view.
|
|
108
|
+
* It defaults to 100, but you can set it to 50 to have two fields in the same row.
|
|
109
|
+
*/
|
|
110
|
+
widthPercentage?: number;
|
|
106
111
|
}
|
|
107
112
|
/**
|
|
108
113
|
* @group Entity properties
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firecms/core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.0.0-canary.
|
|
4
|
+
"version": "3.0.0-canary.179",
|
|
5
5
|
"description": "Awesome Firebase/Firestore-based headless open-source CMS",
|
|
6
6
|
"funding": {
|
|
7
7
|
"url": "https://github.com/sponsors/firecmsco"
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"./package.json": "./package.json"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@firecms/editor": "^3.0.0-canary.
|
|
54
|
-
"@firecms/formex": "^3.0.0-canary.
|
|
55
|
-
"@firecms/ui": "^3.0.0-canary.
|
|
53
|
+
"@firecms/editor": "^3.0.0-canary.179",
|
|
54
|
+
"@firecms/formex": "^3.0.0-canary.179",
|
|
55
|
+
"@firecms/ui": "^3.0.0-canary.179",
|
|
56
56
|
"@hello-pangea/dnd": "^17.0.0",
|
|
57
57
|
"@radix-ui/react-portal": "^1.1.3",
|
|
58
58
|
"clsx": "^2.1.1",
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
"dist",
|
|
105
105
|
"src"
|
|
106
106
|
],
|
|
107
|
-
"gitHead": "
|
|
107
|
+
"gitHead": "39125fe4c1a15167d203baee64f2bbd5376eb766",
|
|
108
108
|
"publishConfig": {
|
|
109
109
|
"access": "public"
|
|
110
110
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
|
-
import { canCreateEntity, canDeleteEntity
|
|
3
|
+
import { canCreateEntity, canDeleteEntity } from "../../util";
|
|
4
4
|
import { useAuthController, useCustomizationController, useFireCMSContext, useLargeLayout } from "../../hooks";
|
|
5
5
|
import { CollectionActionsProps, EntityCollection, EntityTableController, SelectionController } from "../../types";
|
|
6
6
|
import { AddIcon, Button, DeleteIcon, IconButton, Tooltip } from "@firecms/ui";
|
|
@@ -124,7 +124,9 @@ export function EntityCollectionViewActions<M extends Record<string, any>>({
|
|
|
124
124
|
|
|
125
125
|
return (
|
|
126
126
|
<>
|
|
127
|
-
|
|
127
|
+
<ErrorBoundary>
|
|
128
|
+
{actions}
|
|
129
|
+
</ErrorBoundary>
|
|
128
130
|
{multipleDeleteButton}
|
|
129
131
|
{addButton}
|
|
130
132
|
</>
|
|
@@ -4,7 +4,7 @@ import { useCallback, useState } from "react";
|
|
|
4
4
|
export function PropertyIdCopyTooltip({
|
|
5
5
|
propertyKey,
|
|
6
6
|
className,
|
|
7
|
-
children
|
|
7
|
+
children,
|
|
8
8
|
}: {
|
|
9
9
|
propertyKey: string,
|
|
10
10
|
className?: string,
|
|
@@ -13,11 +13,10 @@ export function PropertyIdCopyTooltip({
|
|
|
13
13
|
return <Tooltip title={<PropertyIdCopyTooltipContent propertyKey={propertyKey}/>}
|
|
14
14
|
delayDuration={800}
|
|
15
15
|
side={"top"}
|
|
16
|
-
asChild={false}
|
|
17
16
|
align={"start"}
|
|
18
17
|
sideOffset={8}
|
|
19
18
|
className={className}>
|
|
20
|
-
|
|
19
|
+
{children}
|
|
21
20
|
</Tooltip>
|
|
22
21
|
|
|
23
22
|
}
|
|
@@ -127,6 +127,7 @@ export function EntityEditView<M extends Record<string, any>, USER extends User>
|
|
|
127
127
|
entityId,
|
|
128
128
|
...props
|
|
129
129
|
}: EntityEditViewProps<M>) {
|
|
130
|
+
|
|
130
131
|
const {
|
|
131
132
|
entity,
|
|
132
133
|
dataLoading,
|
|
@@ -173,7 +174,7 @@ function FormLayout({
|
|
|
173
174
|
return <div
|
|
174
175
|
role="tabpanel"
|
|
175
176
|
id={id}
|
|
176
|
-
className={cls("relative flex flex-row max-w-4xl lg:max-w-3xl xl:max-w-
|
|
177
|
+
className={cls("relative flex flex-row max-w-4xl lg:max-w-3xl xl:max-w-4xl 2xl:max-w-6xl w-full h-fit", className)}>
|
|
177
178
|
|
|
178
179
|
<div className={cls("flex flex-col w-full pt-12 pb-16 px-4 sm:px-8 md:px-10")}>
|
|
179
180
|
|
|
@@ -225,8 +226,6 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
225
226
|
console.warn(`The collection ${collection.path} has customId and formAutoSave enabled. This is not supported and formAutoSave will be ignored`);
|
|
226
227
|
}
|
|
227
228
|
|
|
228
|
-
const actionsAtTheBottom = !largeLayout || layout === "side_panel";
|
|
229
|
-
|
|
230
229
|
const [saving, setSaving] = useState(false);
|
|
231
230
|
/**
|
|
232
231
|
* These are the values that are being saved. They are debounced.
|
|
@@ -316,7 +315,6 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
316
315
|
const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0;
|
|
317
316
|
|
|
318
317
|
const [usedEntity, setUsedEntity] = useState<Entity<M> | undefined>(entity);
|
|
319
|
-
const [readOnly, setReadOnly] = useState<boolean | undefined>(undefined);
|
|
320
318
|
|
|
321
319
|
const baseDataSourceValuesRef = useRef<Partial<EntityValues<M>> | null>(cachedDirtyValues ?? getDataSourceEntityValues(initialResolvedCollection, status, usedEntity));
|
|
322
320
|
|
|
@@ -325,16 +323,17 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
325
323
|
setUsedEntity(entity);
|
|
326
324
|
}, [entity]);
|
|
327
325
|
|
|
328
|
-
|
|
329
|
-
if (status === "new") {
|
|
330
|
-
|
|
326
|
+
const canEdit = useMemo(() => {
|
|
327
|
+
if (status === "new" || status === "copy") {
|
|
328
|
+
return true;
|
|
331
329
|
} else {
|
|
332
|
-
|
|
333
|
-
if (usedEntity)
|
|
334
|
-
setReadOnly(!editEnabled);
|
|
330
|
+
return usedEntity ? canEditEntity(collection, authController, path, usedEntity ?? null) : false;
|
|
335
331
|
}
|
|
336
332
|
}, [authController, usedEntity, status]);
|
|
337
333
|
|
|
334
|
+
const readOnly = !canEdit;
|
|
335
|
+
console.log("readOnly", readOnly);
|
|
336
|
+
|
|
338
337
|
const onPreSaveHookError = useCallback((e: Error) => {
|
|
339
338
|
setSaving(false);
|
|
340
339
|
snackbarController.open({
|
|
@@ -573,7 +572,8 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
573
572
|
: [];
|
|
574
573
|
|
|
575
574
|
const selectedEntityView = resolvedEntityViews.find(e => e.key === selectedTab);
|
|
576
|
-
const shouldShowEntityActions = selectedTab === MAIN_TAB_VALUE || selectedEntityView?.includeActions;
|
|
575
|
+
const shouldShowEntityActions = !readOnly && (selectedTab === MAIN_TAB_VALUE || selectedEntityView?.includeActions);
|
|
576
|
+
const actionsAtTheBottom = !largeLayout || layout === "side_panel" || shouldShowEntityActions === "bottom";
|
|
577
577
|
|
|
578
578
|
const secondaryForms: React.ReactNode[] | undefined = customViews && resolvedEntityViews
|
|
579
579
|
.filter(e => e.includeActions)
|
|
@@ -618,7 +618,7 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
618
618
|
|
|
619
619
|
return <div
|
|
620
620
|
className={cls(defaultBorderMixin,
|
|
621
|
-
"relative flex-
|
|
621
|
+
"relative flex-1 w-full h-full overflow-auto",
|
|
622
622
|
{
|
|
623
623
|
"hidden": selectedTab !== customView.key
|
|
624
624
|
})}
|
|
@@ -646,7 +646,7 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
646
646
|
if (selectedTab !== subcollectionId) return null;
|
|
647
647
|
return (
|
|
648
648
|
<div
|
|
649
|
-
className={"relative flex-
|
|
649
|
+
className={"relative flex-1 h-full overflow-auto w-full"}
|
|
650
650
|
key={`subcol_${subcollectionId}`}
|
|
651
651
|
role="tabpanel">
|
|
652
652
|
|
|
@@ -827,9 +827,12 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
827
827
|
}
|
|
828
828
|
}, [formex.isSubmitting, autoSave, underlyingChanges, entity, formex.values, formex.touched, formex.setFieldValue]);
|
|
829
829
|
|
|
830
|
+
const formFieldKeys = getFormFieldKeys(resolvedCollection);
|
|
831
|
+
const resolvedProperties = formFieldKeys.map(key => resolvedCollection.properties[key]);
|
|
832
|
+
|
|
830
833
|
const formFields = (
|
|
831
|
-
|
|
832
|
-
{
|
|
834
|
+
<div className={"flex flex-wrap gap-x-4 w-full space-y-8"}>
|
|
835
|
+
{formFieldKeys
|
|
833
836
|
.map((key) => {
|
|
834
837
|
|
|
835
838
|
const property = resolvedCollection.properties[key];
|
|
@@ -843,6 +846,7 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
843
846
|
const disabled = (!autoSave && formex.isSubmitting) || isReadOnly(property) || Boolean(property.disabled);
|
|
844
847
|
const hidden = isHidden(property);
|
|
845
848
|
if (hidden) return null;
|
|
849
|
+
const widthPercentage = property.widthPercentage ?? 100;
|
|
846
850
|
const cmsFormFieldProps: PropertyFieldBindingProps<any, M> = {
|
|
847
851
|
propertyKey: key,
|
|
848
852
|
disabled,
|
|
@@ -857,6 +861,8 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
857
861
|
|
|
858
862
|
return (
|
|
859
863
|
<div id={`form_field_${key}`}
|
|
864
|
+
className={"relative"}
|
|
865
|
+
style={{ width: widthPercentage === 100 ? "100%" : `calc(${widthPercentage}% - 8px)` }}
|
|
860
866
|
key={`field_${resolvedCollection.name}_${key}`}>
|
|
861
867
|
<ErrorBoundary>
|
|
862
868
|
<PropertyFieldBinding {...cmsFormFieldProps}/>
|
|
@@ -874,21 +880,22 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
874
880
|
|
|
875
881
|
const child = Builder
|
|
876
882
|
? <Builder entity={entity} context={context}/>
|
|
877
|
-
:
|
|
883
|
+
: <div className={"w-full"}>
|
|
878
884
|
{additionalField.value?.({
|
|
879
885
|
entity,
|
|
880
886
|
context
|
|
881
887
|
})?.toString()}
|
|
882
|
-
|
|
888
|
+
</div>;
|
|
889
|
+
|
|
883
890
|
return (
|
|
884
|
-
<div key={`additional_${key}`}>
|
|
891
|
+
<div key={`additional_${key}`} className={"w-full"}>
|
|
885
892
|
<LabelWithIconAndTooltip
|
|
886
893
|
propertyKey={key}
|
|
887
894
|
icon={<NotesIcon size={"small"}/>}
|
|
888
895
|
title={additionalField.name}
|
|
889
896
|
className={"text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>
|
|
890
897
|
<div
|
|
891
|
-
className={cls(paperMixin, "min-h-14 p-4 md:p-6 overflow-x-scroll no-scrollbar")}>
|
|
898
|
+
className={cls(paperMixin, "w-full min-h-14 p-4 md:p-6 overflow-x-scroll no-scrollbar")}>
|
|
892
899
|
|
|
893
900
|
<ErrorBoundary>
|
|
894
901
|
{child}
|
|
@@ -904,7 +911,7 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
904
911
|
})
|
|
905
912
|
.filter(Boolean)}
|
|
906
913
|
|
|
907
|
-
|
|
914
|
+
</div>);
|
|
908
915
|
|
|
909
916
|
const disabled = formex.isSubmitting || (!modified && status === "existing");
|
|
910
917
|
const formRef = React.useRef<HTMLDivElement>(null);
|
|
@@ -961,7 +968,7 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
961
968
|
</Typography>
|
|
962
969
|
<Alert color={"base"} className={"w-full"} size={"small"}>
|
|
963
970
|
<code
|
|
964
|
-
className={"text-xs select-all text-text-secondary dark:text-text-secondary-dark"}>{path}/{entityId}</code>
|
|
971
|
+
className={"text-xs select-all text-text-secondary dark:text-text-secondary-dark"}>{entity?.path ?? path}/{entityId}</code>
|
|
965
972
|
</Alert>
|
|
966
973
|
</div>
|
|
967
974
|
|
|
@@ -991,18 +998,18 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
991
998
|
</ErrorBoundary>
|
|
992
999
|
)
|
|
993
1000
|
: (
|
|
994
|
-
|
|
1001
|
+
<div className={"flex flex-col"}>
|
|
995
1002
|
<Typography
|
|
996
1003
|
className={"mt-16 mb-8 mx-8"}
|
|
997
1004
|
variant={"h4"}>{collection.singularName ?? collection.name}
|
|
998
1005
|
</Typography>
|
|
999
1006
|
<EntityView
|
|
1000
|
-
className={"px-
|
|
1007
|
+
className={"px-8"}
|
|
1001
1008
|
entity={usedEntity as Entity<M>}
|
|
1002
1009
|
path={path}
|
|
1003
1010
|
collection={collection}/>
|
|
1004
1011
|
|
|
1005
|
-
|
|
1012
|
+
</div>
|
|
1006
1013
|
));
|
|
1007
1014
|
|
|
1008
1015
|
const subcollectionTabs = subcollections && subcollections.map(
|
|
@@ -1032,6 +1039,7 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
1032
1039
|
}, [entityId, onIdChange]);
|
|
1033
1040
|
|
|
1034
1041
|
const shouldShowTopBar = Boolean(barActions) || hasAdditionalViews;
|
|
1042
|
+
const shouldIncludeForm = selectedTab === MAIN_TAB_VALUE || selectedEntityView?.includeActions;
|
|
1035
1043
|
|
|
1036
1044
|
let result = <div className="relative flex flex-col h-full w-full bg-white dark:bg-surface-900">
|
|
1037
1045
|
|
|
@@ -1079,7 +1087,8 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
1079
1087
|
return onDiscard();
|
|
1080
1088
|
}}
|
|
1081
1089
|
noValidate
|
|
1082
|
-
className={"flex-1 flex flex-row w-full overflow-y-auto justify-center"
|
|
1090
|
+
className={cls("flex-1 flex flex-row w-full overflow-y-auto justify-center",
|
|
1091
|
+
shouldIncludeForm ? "" : "hidden")}>
|
|
1083
1092
|
|
|
1084
1093
|
<FormLayout
|
|
1085
1094
|
className={!mainViewVisible ? "hidden" : ""}
|
|
@@ -1205,6 +1214,7 @@ function buildBottomActions<M extends object>({
|
|
|
1205
1214
|
layout
|
|
1206
1215
|
}: ActionsViewProps<M>) {
|
|
1207
1216
|
|
|
1217
|
+
const canClose = layout === "side_panel";
|
|
1208
1218
|
return <DialogActions position={"absolute"}>
|
|
1209
1219
|
|
|
1210
1220
|
{savingError &&
|
|
@@ -1246,7 +1256,7 @@ function buildBottomActions<M extends object>({
|
|
|
1246
1256
|
</Button>
|
|
1247
1257
|
|
|
1248
1258
|
<Button
|
|
1249
|
-
variant="text"
|
|
1259
|
+
variant={canClose ? "text" : "filled"}
|
|
1250
1260
|
color="primary"
|
|
1251
1261
|
type="submit"
|
|
1252
1262
|
disabled={disabled || isSubmitting}
|
|
@@ -1258,7 +1268,7 @@ function buildBottomActions<M extends object>({
|
|
|
1258
1268
|
{status === "new" && "Create"}
|
|
1259
1269
|
</Button>
|
|
1260
1270
|
|
|
1261
|
-
<LoadingButton
|
|
1271
|
+
{canClose && <LoadingButton
|
|
1262
1272
|
variant="filled"
|
|
1263
1273
|
color="primary"
|
|
1264
1274
|
type="submit"
|
|
@@ -1270,7 +1280,7 @@ function buildBottomActions<M extends object>({
|
|
|
1270
1280
|
{status === "existing" && "Save and close"}
|
|
1271
1281
|
{status === "copy" && "Create copy and close"}
|
|
1272
1282
|
{status === "new" && "Create and close"}
|
|
1273
|
-
</LoadingButton>
|
|
1283
|
+
</LoadingButton>}
|
|
1274
1284
|
|
|
1275
1285
|
</DialogActions>;
|
|
1276
1286
|
}
|
|
@@ -1290,7 +1300,7 @@ function buildSideActions<M extends object>({
|
|
|
1290
1300
|
}: ActionsViewProps<M>) {
|
|
1291
1301
|
|
|
1292
1302
|
return <div
|
|
1293
|
-
className={cls("overflow-auto h-full flex flex-col gap-2 w-80
|
|
1303
|
+
className={cls("overflow-auto h-full flex flex-col gap-2 w-80 2xl:w-96 px-4 py-16 sticky top-0 border-l", defaultBorderMixin)}>
|
|
1294
1304
|
|
|
1295
1305
|
<LoadingButton
|
|
1296
1306
|
fullWidth={true}
|
|
@@ -5,13 +5,13 @@ import { Field, FieldProps as FormexFieldProps, getIn } from "@firecms/formex";
|
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
7
|
CMSType,
|
|
8
|
-
EntityCollection,
|
|
9
8
|
FieldProps,
|
|
10
9
|
FireCMSPlugin,
|
|
11
10
|
PluginFieldBuilderParams,
|
|
12
11
|
Property,
|
|
13
12
|
PropertyFieldBindingProps,
|
|
14
13
|
PropertyOrBuilder,
|
|
14
|
+
ResolvedEntityCollection,
|
|
15
15
|
ResolvedProperty
|
|
16
16
|
} from "../types";
|
|
17
17
|
import { ReadOnlyFieldBinding } from "./field_bindings/ReadOnlyFieldBinding";
|
|
@@ -147,7 +147,9 @@ function PropertyFieldBindingInternal<T extends CMSType = CMSType, M extends Rec
|
|
|
147
147
|
console.warn(`No field component found for property ${propertyKey}`);
|
|
148
148
|
console.warn("Property:", property);
|
|
149
149
|
return (
|
|
150
|
-
<div
|
|
150
|
+
<div className={"w-full"}>
|
|
151
|
+
{`Currently the field ${resolvedProperty.dataType} is not supported`}
|
|
152
|
+
</div>
|
|
151
153
|
);
|
|
152
154
|
}
|
|
153
155
|
|
|
@@ -199,11 +201,11 @@ function FieldInternal<T extends CMSType, CustomProps, M extends Record<string,
|
|
|
199
201
|
},
|
|
200
202
|
formexFieldProps
|
|
201
203
|
}:
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
204
|
+
{
|
|
205
|
+
Component: ComponentType<FieldProps<T, any, M>>,
|
|
206
|
+
componentProps: ResolvedPropertyFieldBindingProps<T, M>,
|
|
207
|
+
formexFieldProps: FormexFieldProps<T, any>
|
|
208
|
+
}) {
|
|
207
209
|
|
|
208
210
|
const { plugins } = useCustomizationController();
|
|
209
211
|
|
|
@@ -296,7 +298,7 @@ const shouldPropertyReRender = (property: PropertyOrBuilder | ResolvedProperty,
|
|
|
296
298
|
|
|
297
299
|
interface UseWrappedComponentParams<T extends CMSType = CMSType, M extends Record<string, any> = any> {
|
|
298
300
|
path?: string,
|
|
299
|
-
collection?:
|
|
301
|
+
collection?: ResolvedEntityCollection<M>,
|
|
300
302
|
propertyKey: string,
|
|
301
303
|
property: ResolvedProperty<T>,
|
|
302
304
|
Component: ComponentType<FieldProps<T, any, M>>,
|
|
@@ -327,7 +329,7 @@ function useWrappedComponent<T extends CMSType = CMSType, M extends Record<strin
|
|
|
327
329
|
Field: Component,
|
|
328
330
|
plugin,
|
|
329
331
|
path,
|
|
330
|
-
collection
|
|
332
|
+
collection,
|
|
331
333
|
};
|
|
332
334
|
const enabled = plugin.form?.fieldBuilderEnabled?.(params);
|
|
333
335
|
if (enabled === undefined || enabled)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { forwardRef } from "react";
|
|
2
|
+
import { cls } from "@firecms/ui";
|
|
2
3
|
|
|
3
4
|
interface LabelWithIconProps {
|
|
4
5
|
icon: React.ReactNode;
|
|
@@ -23,9 +24,11 @@ export const LabelWithIcon = forwardRef<HTMLDivElement, LabelWithIconProps>(
|
|
|
23
24
|
return (
|
|
24
25
|
<div
|
|
25
26
|
ref={ref}
|
|
26
|
-
className={
|
|
27
|
+
className={cls("inline-flex items-center my-0.5",
|
|
28
|
+
small ? "gap-1" : "gap-2",
|
|
29
|
+
className)}
|
|
27
30
|
>
|
|
28
|
-
|
|
31
|
+
{icon}
|
|
29
32
|
<span
|
|
30
33
|
className={`text-start font-medium text-${small ? "base" : "sm"} origin-top-left transform ${
|
|
31
34
|
small ? "translate-x-2 scale-75" : ""
|
|
@@ -33,7 +36,7 @@ export const LabelWithIcon = forwardRef<HTMLDivElement, LabelWithIconProps>(
|
|
|
33
36
|
>
|
|
34
37
|
{(title ?? "") + (required ? " *" : "")}
|
|
35
38
|
</span>
|
|
36
|
-
|
|
39
|
+
</div>
|
|
37
40
|
);
|
|
38
41
|
}
|
|
39
42
|
);
|
|
@@ -54,7 +54,7 @@ export function ArrayCustomShapedFieldBinding<T extends Array<any>>({
|
|
|
54
54
|
icon={getIconForProperty(property, "small")}
|
|
55
55
|
required={property.validation?.required}
|
|
56
56
|
title={property.name}
|
|
57
|
-
className={"flex-grow text-text-secondary dark:text-text-secondary-dark"}/>
|
|
57
|
+
className={"h-8 flex-grow text-text-secondary dark:text-text-secondary-dark"}/>
|
|
58
58
|
{Array.isArray(value) && <Typography variant={"caption"} className={"px-4"}>({value.length})</Typography>}
|
|
59
59
|
</>);
|
|
60
60
|
|
|
@@ -106,7 +106,7 @@ export function ArrayOfReferencesFieldBinding({
|
|
|
106
106
|
icon={getIconForProperty(property, "small")}
|
|
107
107
|
required={property.validation?.required}
|
|
108
108
|
title={property.name}
|
|
109
|
-
className={"flex flex-grow text-text-secondary dark:text-text-secondary-dark"}/>
|
|
109
|
+
className={"h-8 flex flex-grow text-text-secondary dark:text-text-secondary-dark"}/>
|
|
110
110
|
{Array.isArray(value) && <Typography variant={"caption"} className={"px-4"}>({value.length})</Typography>}
|
|
111
111
|
</>);
|
|
112
112
|
|
|
@@ -95,15 +95,6 @@ export function MapFieldBinding({
|
|
|
95
95
|
</>
|
|
96
96
|
;
|
|
97
97
|
|
|
98
|
-
const title = (
|
|
99
|
-
<LabelWithIconAndTooltip
|
|
100
|
-
propertyKey={propertyKey}
|
|
101
|
-
icon={getIconForProperty(property, "small")}
|
|
102
|
-
required={property.validation?.required}
|
|
103
|
-
title={property.name}
|
|
104
|
-
className={"text-text-secondary dark:text-text-secondary-dark"}/>
|
|
105
|
-
);
|
|
106
|
-
|
|
107
98
|
return (
|
|
108
99
|
<ErrorBoundary>
|
|
109
100
|
|
|
@@ -113,8 +104,16 @@ export function MapFieldBinding({
|
|
|
113
104
|
expanded
|
|
114
105
|
});
|
|
115
106
|
}}
|
|
107
|
+
className={property.widthPercentage !== undefined ? "mt-8" : undefined}
|
|
116
108
|
innerClassName={"px-2 md:px-4 pb-2 md:pb-4 pt-1 md:pt-2 bg-white dark:bg-surface-900"}
|
|
117
|
-
title={
|
|
109
|
+
title={<LabelWithIconAndTooltip
|
|
110
|
+
propertyKey={propertyKey}
|
|
111
|
+
icon={getIconForProperty(property, "small")}
|
|
112
|
+
required={property.validation?.required}
|
|
113
|
+
title={property.name}
|
|
114
|
+
className={"text-text-secondary dark:text-text-secondary-dark"}/>}>
|
|
115
|
+
{mapFormView}
|
|
116
|
+
</ExpandablePanel>}
|
|
118
117
|
|
|
119
118
|
{minimalistView && mapFormView}
|
|
120
119
|
|
|
@@ -135,7 +135,7 @@ export function MarkdownEditorFieldBinding({
|
|
|
135
135
|
icon={getIconForProperty(property, "small")}
|
|
136
136
|
required={property.validation?.required}
|
|
137
137
|
title={property.name}
|
|
138
|
-
className={"text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>
|
|
138
|
+
className={"h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>
|
|
139
139
|
<div className={cls("rounded-md", fieldBackgroundMixin, fieldBackgroundHoverMixin)}>
|
|
140
140
|
{editor}
|
|
141
141
|
</div>
|
|
@@ -92,7 +92,7 @@ export function MultiSelectFieldBinding({
|
|
|
92
92
|
icon={getIconForProperty(property, "small")}
|
|
93
93
|
required={property.validation?.required}
|
|
94
94
|
title={property.name}
|
|
95
|
-
className={"text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>}
|
|
95
|
+
className={"h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>}
|
|
96
96
|
onValueChange={(updatedValue: string[]) => {
|
|
97
97
|
let newValue: EnumType[] | null;
|
|
98
98
|
if (of && (of as ResolvedProperty)?.dataType === "number") {
|
|
@@ -39,11 +39,11 @@ export function ReadOnlyFieldBinding({
|
|
|
39
39
|
icon={getIconForProperty(property, "small")}
|
|
40
40
|
required={property.validation?.required}
|
|
41
41
|
title={property.name}
|
|
42
|
-
className={"text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>
|
|
42
|
+
className={"h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
<div
|
|
46
|
-
className={cls(paperMixin, "min-h-14 p-4 md:p-6 overflow-x-scroll no-scrollbar")}>
|
|
46
|
+
className={cls(paperMixin, "w-full min-h-14 p-4 md:p-6 overflow-x-scroll no-scrollbar")}>
|
|
47
47
|
|
|
48
48
|
<ErrorBoundary>
|
|
49
49
|
<PropertyPreview propertyKey={propertyKey}
|
|
@@ -90,7 +90,7 @@ function ReferenceFieldBindingInternal({
|
|
|
90
90
|
icon={getIconForProperty(property, "small")}
|
|
91
91
|
required={property.validation?.required}
|
|
92
92
|
title={property.name}
|
|
93
|
-
className={"text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>}
|
|
93
|
+
className={"h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>}
|
|
94
94
|
|
|
95
95
|
{!collection && <ErrorView
|
|
96
96
|
error={"The specified collection does not exist. Check console"}/>}
|
|
@@ -90,6 +90,7 @@ export function RepeatFieldBinding<T extends Array<any>>({
|
|
|
90
90
|
includeAddButton={!property.disabled}
|
|
91
91
|
newDefaultEntry={getDefaultValueFor(property.of)}
|
|
92
92
|
onValueChange={(value) => setFieldValue(propertyKey, value)}
|
|
93
|
+
className={property.widthPercentage !== undefined ? "mt-8" : undefined}
|
|
93
94
|
/>;
|
|
94
95
|
|
|
95
96
|
const title = (<>
|
|
@@ -98,7 +99,7 @@ export function RepeatFieldBinding<T extends Array<any>>({
|
|
|
98
99
|
icon={getIconForProperty(property, "small")}
|
|
99
100
|
required={property.validation?.required}
|
|
100
101
|
title={property.name}
|
|
101
|
-
className={"flex flex-grow text-text-secondary dark:text-text-secondary-dark"}/>
|
|
102
|
+
className={"h-8 flex flex-grow text-text-secondary dark:text-text-secondary-dark"}/>
|
|
102
103
|
{Array.isArray(value) && <Typography variant={"caption"} className={"px-4"}>({value.length})</Typography>}
|
|
103
104
|
</>);
|
|
104
105
|
|
|
@@ -62,7 +62,7 @@ export function SelectFieldBinding<T extends EnumType>({
|
|
|
62
62
|
icon={getIconForProperty(property, "small")}
|
|
63
63
|
required={property.validation?.required}
|
|
64
64
|
title={property.name}
|
|
65
|
-
className={"text-text-secondary dark:text-text-secondary-dark ml-3.5"}
|
|
65
|
+
className={"h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5 my-0"}
|
|
66
66
|
/>
|
|
67
67
|
</PropertyIdCopyTooltip>}
|
|
68
68
|
endAdornment={
|
|
@@ -103,7 +103,7 @@ export function StorageUploadFieldBinding({
|
|
|
103
103
|
icon={getIconForProperty(property, "small")}
|
|
104
104
|
required={property.validation?.required}
|
|
105
105
|
title={property.name}
|
|
106
|
-
className={"
|
|
106
|
+
className={"h-8text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>}
|
|
107
107
|
|
|
108
108
|
<StorageUpload
|
|
109
109
|
value={internalValue}
|
|
@@ -43,6 +43,7 @@ export const SwitchFieldBinding = function SwitchFieldBinding({
|
|
|
43
43
|
value={value}
|
|
44
44
|
onValueChange={(v) => setValue(v)}
|
|
45
45
|
error={showError}
|
|
46
|
+
className={property.widthPercentage !== undefined ? "mt-8" : undefined}
|
|
46
47
|
label={<LabelWithIcon
|
|
47
48
|
icon={getIconForProperty(property, "small")}
|
|
48
49
|
required={property.validation?.required}
|
|
@@ -75,6 +75,7 @@ export function TextFieldBinding<T extends string | number>({
|
|
|
75
75
|
value={value}
|
|
76
76
|
onChange={onChange}
|
|
77
77
|
autoFocus={autoFocus}
|
|
78
|
+
className={property.widthPercentage !== undefined ? "mt-8" : undefined}
|
|
78
79
|
label={<LabelWithIcon
|
|
79
80
|
icon={getIconForProperty(property, "small")}
|
|
80
81
|
required={property.validation?.required}
|
package/src/types/properties.ts
CHANGED
|
@@ -156,6 +156,12 @@ export interface BaseProperty<T extends CMSType, CustomProps = any> {
|
|
|
156
156
|
* save the new config. The saved config will then become the source of truth.
|
|
157
157
|
*/
|
|
158
158
|
editable?: boolean;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* A number between 0 and 100 that indicates the width of the field in the form view.
|
|
162
|
+
* It defaults to 100, but you can set it to 50 to have two fields in the same row.
|
|
163
|
+
*/
|
|
164
|
+
widthPercentage?: number;
|
|
159
165
|
}
|
|
160
166
|
|
|
161
167
|
/**
|
|
@@ -22,11 +22,11 @@ export function getParentReferencesFromPath(props: {
|
|
|
22
22
|
|
|
23
23
|
const collection: EntityCollection<any> | undefined = collections && collections.find((entry) => entry.id === subpathCombination || entry.path === subpathCombination);
|
|
24
24
|
|
|
25
|
+
// If we find a collection, we add the reference and continue
|
|
25
26
|
if (collection) {
|
|
26
|
-
const path = collection.path;
|
|
27
27
|
const collectionPath = currentFullPath && currentFullPath.length > 0
|
|
28
|
-
? (currentFullPath + "/" + path)
|
|
29
|
-
: path;
|
|
28
|
+
? (currentFullPath + "/" + collection.path)
|
|
29
|
+
: collection.path;
|
|
30
30
|
|
|
31
31
|
const restOfThePath = removeInitialAndTrailingSlashes(removeInitialAndTrailingSlashes(path).replace(subpathCombination, ""));
|
|
32
32
|
const nextSegments = restOfThePath.length > 0 ? restOfThePath.split("/") : [];
|