Skip to content

Commit 12d606b

Browse files
committed
Updated layout for selecting widgets
1 parent 4964b08 commit 12d606b

File tree

9 files changed

+231
-109
lines changed

9 files changed

+231
-109
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ navigating through **subcollections** and accessing custom views (such as custom
9393
forms or blog previews). This functionality can also be accessed
9494
programmatically using the `useSideEntityController` hook.
9595

96-
FireCMS includes **over 15 built-in fields** with numerous customization and
96+
FireCMS includes **over 20 built-in fields** with numerous customization and
9797
validation options. The components have been carefully designed for an
9898
outstanding user experience, including advanced features like **references** to
9999
other collections, **markdown**, and **array reordering**.

packages/collection_editor/src/ui/collection_editor/PropertyEditView.tsx

Lines changed: 186 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import equal from "react-fast-compare"
33

44
import { Formex, FormexController, getIn, useCreateFormex } from "@firecms/formex";
55
import {
6-
DEFAULT_FIELD_CONFIGS,
76
ConfirmationDialog,
7+
DEFAULT_FIELD_CONFIGS,
88
getFieldConfig,
99
getFieldId,
1010
isPropertyBuilder,
@@ -17,15 +17,21 @@ import {
1717
} from "@firecms/core";
1818
import {
1919
Button,
20+
Card,
2021
cls,
2122
DeleteIcon,
2223
Dialog,
2324
DialogActions,
2425
DialogContent,
26+
DialogTitle,
27+
fieldBackgroundDisabledMixin,
28+
fieldBackgroundHoverMixin,
29+
fieldBackgroundMixin,
2530
IconButton,
2631
InfoLabel,
27-
Select,
28-
Typography
32+
Tooltip,
33+
Typography,
34+
WarningOffIcon
2935
} from "@firecms/ui";
3036
import { EnumPropertyField } from "./properties/EnumPropertyField";
3137
import { StoragePropertyField } from "./properties/StoragePropertyField";
@@ -42,7 +48,6 @@ import { AdvancedPropertyValidation } from "./properties/advanced/AdvancedProper
4248
import { editableProperty } from "../../utils/entities";
4349
import { KeyValuePropertyField } from "./properties/KeyValuePropertyField";
4450
import { updatePropertyFromWidget } from "./utils/update_property_for_widget";
45-
import { PropertySelectItem } from "./PropertySelectItem";
4651
import { UrlPropertyField } from "./properties/UrlPropertyField";
4752
import { supportedFields } from "./utils/supported_fields";
4853
import { MarkdownPropertyField } from "./properties/MarkdownPropertyField";
@@ -342,12 +347,6 @@ function PropertyEditFormFields({
342347
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
343348
const [selectedFieldConfigId, setSelectedFieldConfigId] = useState<string | undefined>(values?.dataType ? getFieldId(values) : undefined);
344349

345-
const allSupportedFields = Object.entries(supportedFields).concat(Object.entries(propertyConfigs));
346-
347-
const displayedWidgets = inArray
348-
? allSupportedFields.filter(([_, propertyConfig]) => !isPropertyBuilder(propertyConfig.property) && propertyConfig.property?.dataType !== "array")
349-
: allSupportedFields;
350-
351350
const deferredValues = useDeferredValue(values);
352351
const nameFieldRef = useRef<HTMLInputElement>(null);
353352

@@ -489,62 +488,17 @@ function PropertyEditFormFields({
489488

490489
<div className="flex mt-2 justify-between">
491490
<div className={"w-full flex flex-col gap-2"}>
492-
<Select
493-
// className={"w-full"}
494-
error={Boolean(selectedWidgetError)}
495-
value={selectedFieldConfigId ?? ""}
496-
placeholder={"Select a property widget"}
491+
<WidgetSelectView
492+
initialProperty={values}
493+
value={selectedFieldConfigId as PropertyConfigId}
494+
onValueChange={(value) => onWidgetSelectChanged(value as PropertyConfigId)}
497495
open={selectOpen}
498496
onOpenChange={setSelectOpen}
499-
position={"item-aligned"}
500497
disabled={disabled}
501-
renderValue={(value) => {
502-
if (!value) {
503-
return <em>Select a property
504-
widget</em>;
505-
}
506-
const key = value as PropertyConfigId;
507-
const propertyConfig = DEFAULT_FIELD_CONFIGS[key] ?? propertyConfigs[key];
508-
const baseProperty = propertyConfig.property;
509-
const baseFieldConfig = baseProperty && !isPropertyBuilder(baseProperty) ? getFieldConfig(baseProperty, propertyConfigs) : undefined;
510-
const optionDisabled = isPropertyBuilder(baseProperty) || (existing && baseProperty.dataType !== values?.dataType);
511-
const computedFieldConfig = baseFieldConfig ? mergeDeep(baseFieldConfig, propertyConfig) : propertyConfig;
512-
return <div
513-
onClick={(e) => {
514-
if (optionDisabled) {
515-
e.stopPropagation();
516-
e.preventDefault();
517-
}
518-
}}
519-
className={cls(
520-
"flex items-center",
521-
optionDisabled ? "w-full pointer-events-none opacity-50" : "")}>
522-
<div className={"mr-8"}>
523-
<PropertyConfigBadge propertyConfig={computedFieldConfig}/>
524-
</div>
525-
<div className={"flex flex-col items-start text-base text-left"}>
526-
<div>{computedFieldConfig.name}</div>
527-
<Typography variant={"caption"}
528-
color={"disabled"}>
529-
{optionDisabled ? "You can only switch to widgets that use the same data type" : computedFieldConfig.description}
530-
</Typography>
531-
</div>
532-
</div>
533-
}}
534-
onValueChange={(value) => {
535-
onWidgetSelectChanged(value as PropertyConfigId);
536-
}}>
537-
{displayedWidgets.map(([key, propertyConfig]) => {
538-
const baseProperty = propertyConfig.property;
539-
const optionDisabled = existing && !isPropertyBuilder(baseProperty) && baseProperty.dataType !== values?.dataType;
540-
return <PropertySelectItem
541-
key={key}
542-
value={key}
543-
optionDisabled={optionDisabled}
544-
propertyConfig={propertyConfig}
545-
existing={existing}/>;
546-
})}
547-
</Select>
498+
showError={Boolean(selectedWidgetError)}
499+
existing={existing}
500+
propertyConfigs={propertyConfigs}
501+
inArray={inArray}/>
548502

549503
{selectedWidgetError &&
550504
<Typography variant="caption"
@@ -588,10 +542,10 @@ function PropertyEditFormFields({
588542
onCancel={() => setDeleteDialogOpen(false)}
589543
title={<div>Delete this property?</div>}
590544
body={
591-
<div> This will <b>not delete any
592-
data</b>, only modify the
593-
collection.</div>
594-
}/>}
545+
<div> This will <b>not delete any
546+
data</b>, only modify the
547+
collection.</div>
548+
}/>}
595549

596550
</>
597551
);
@@ -621,3 +575,168 @@ function validateName(value: string) {
621575
}
622576
return error;
623577
}
578+
579+
const WIDGET_TYPE_MAP = {
580+
text_field: "Text",
581+
multiline: "Text",
582+
markdown: "Text",
583+
url: "Text",
584+
email: "Text",
585+
switch: "Boolean",
586+
select: "Select",
587+
multi_select: "Select",
588+
number_input: "Number",
589+
number_select: "Select",
590+
multi_number_select: "Select",
591+
file_upload: "File",
592+
multi_file_upload: "File",
593+
reference: "Reference",
594+
multi_references: "Reference",
595+
date_time: "Date",
596+
group: "Group",
597+
key_value: "Key-Value",
598+
repeat: "Repeat",
599+
custom_array: "Array",
600+
block: "Block"
601+
};
602+
603+
function WidgetSelectView({
604+
initialProperty,
605+
value,
606+
onValueChange,
607+
open,
608+
onOpenChange,
609+
disabled,
610+
showError,
611+
existing,
612+
propertyConfigs,
613+
inArray
614+
}: {
615+
initialProperty?: PropertyWithId,
616+
value?: PropertyConfigId,
617+
onValueChange: (value: string) => void,
618+
showError: boolean,
619+
open: any,
620+
onOpenChange: any,
621+
disabled: boolean,
622+
existing: boolean,
623+
propertyConfigs: Record<string, PropertyConfig>,
624+
inArray?: boolean
625+
}) {
626+
627+
const allSupportedFields = Object.entries(supportedFields).concat(Object.entries(propertyConfigs));
628+
629+
const displayedWidgets = inArray
630+
? allSupportedFields.filter(([_, propertyConfig]) => !isPropertyBuilder(propertyConfig.property) && propertyConfig.property?.dataType !== "array")
631+
: allSupportedFields;
632+
633+
const key = value;
634+
const propertyConfig = key ? (DEFAULT_FIELD_CONFIGS[key] ?? propertyConfigs[key]) : undefined;
635+
const baseProperty = propertyConfig?.property;
636+
const baseFieldConfig = baseProperty && !isPropertyBuilder(baseProperty) ? getFieldConfig(baseProperty, propertyConfigs) : undefined;
637+
const computedFieldConfig = baseFieldConfig && propertyConfig ? mergeDeep(baseFieldConfig, propertyConfig) : propertyConfig;
638+
639+
return <>
640+
<div
641+
onClick={() => {
642+
if (!disabled) {
643+
onOpenChange(!open);
644+
}
645+
}}
646+
className={cls(
647+
"select-none rounded-md text-sm p-4",
648+
fieldBackgroundMixin,
649+
disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin,
650+
"relative flex items-center",
651+
)}>
652+
{!value && <em>Select a property widget</em>}
653+
{value && computedFieldConfig && <div
654+
className={cls(
655+
"flex items-center")}>
656+
<div className={"mr-8"}>
657+
<PropertyConfigBadge propertyConfig={computedFieldConfig}/>
658+
</div>
659+
<div className={"flex flex-col items-start text-base text-left"}>
660+
<div>{computedFieldConfig.name}</div>
661+
<Typography variant={"caption"}
662+
color={"secondary"}>
663+
{computedFieldConfig.description}
664+
</Typography>
665+
</div>
666+
</div>}
667+
</div>
668+
<Dialog open={open}
669+
onOpenChange={onOpenChange}
670+
maxWidth={"4xl"}>
671+
<DialogTitle>
672+
Select a property widget
673+
</DialogTitle>
674+
<DialogContent>
675+
<div className={"grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-2 mt-4"}>
676+
{displayedWidgets.map(([key, propertyConfig]) => {
677+
return <WidgetSelectViewItem
678+
key={key}
679+
initialProperty={initialProperty}
680+
onClick={() => {
681+
onValueChange(key);
682+
onOpenChange(false);
683+
}}
684+
propertyConfig={propertyConfig}
685+
existing={existing}/>;
686+
})}
687+
</div>
688+
</DialogContent>
689+
</Dialog>
690+
</>;
691+
}
692+
693+
export interface PropertySelectItemProps {
694+
onClick?: () => void;
695+
initialProperty?: PropertyWithId;
696+
propertyConfig: PropertyConfig;
697+
existing: boolean;
698+
}
699+
700+
export function WidgetSelectViewItem({
701+
onClick,
702+
initialProperty,
703+
// optionDisabled,
704+
propertyConfig,
705+
existing
706+
}: PropertySelectItemProps) {
707+
const baseProperty = propertyConfig.property;
708+
const shouldWarnChangingDataType = existing && !isPropertyBuilder(baseProperty) && baseProperty.dataType !== initialProperty?.dataType;
709+
710+
return <Card
711+
onClick={onClick}
712+
// disabled={optionDisabled}
713+
className={"flex flex-row items-center px-4 py-2"}>
714+
<div
715+
className={cls(
716+
"flex flex-row items-center text-base min-h-[48px]",
717+
// optionDisabled ? "w-full" : ""
718+
)}>
719+
<div className={"mr-8"}>
720+
<PropertyConfigBadge propertyConfig={propertyConfig} disabled={!shouldWarnChangingDataType}/>
721+
</div>
722+
<div>
723+
<div className={"flex flex-row gap-2 items-center"}>
724+
{shouldWarnChangingDataType && <Tooltip
725+
title={"This widget uses a different data type than the initially selected widget. This can cause errors with existing data."}>
726+
<WarningOffIcon size="smallest" className={"w-4"}/>
727+
</Tooltip>}
728+
<Typography
729+
color={shouldWarnChangingDataType ? "secondary" : undefined}>{propertyConfig.name}</Typography>
730+
</div>
731+
<Typography variant={"caption"}
732+
color={"secondary"}
733+
className={"max-w-sm"}>
734+
{propertyConfig.description}
735+
736+
{/*{existing && optionDisabled ? "You can only switch to widgets that use the same data type" : propertyConfig.description}*/}
737+
</Typography>
738+
739+
</div>
740+
</div>
741+
</Card>
742+
}

packages/collection_editor/src/ui/collection_editor/PropertySelectItem.tsx

Lines changed: 0 additions & 32 deletions
This file was deleted.

0 commit comments

Comments
 (0)