@firecms/core 3.0.0-alpha.24 → 3.0.0-alpha.26

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.
@@ -92,9 +92,9 @@ export type FireCMSPlugin<PROPS = any, FORM_PROPS = any> = {
92
92
  HeaderAction?: React.ComponentType<{
93
93
  property: ResolvedProperty;
94
94
  propertyKey: string;
95
- onHover: boolean;
96
95
  fullPath: string;
97
96
  parentPathSegments: string[];
97
+ onHover: boolean;
98
98
  }>;
99
99
  /**
100
100
  * If you add this callback to your plugin, an add button will be added to the collection table.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firecms/core",
3
- "version": "3.0.0-alpha.24",
3
+ "version": "3.0.0-alpha.26",
4
4
  "description": "Awesome Firebase/Firestore-based headless open-source CMS",
5
5
  "funding": {
6
6
  "url": "https://github.com/sponsors/firecmsco"
@@ -133,7 +133,7 @@
133
133
  "dist",
134
134
  "src"
135
135
  ],
136
- "gitHead": "efeb689af20f913978c975c8ab3f91718e43935b",
136
+ "gitHead": "6cf98e00ea1eee087526615c741eeeedfaf400b7",
137
137
  "publishConfig": {
138
138
  "access": "public"
139
139
  }
@@ -89,7 +89,7 @@ export function TextField<T extends string | number>({
89
89
  ? <TextareaAutosize
90
90
  {...inputProps as any}
91
91
  ref={inputRef}
92
- placeholder={!focused && !hasValue ? undefined : placeholder}
92
+ placeholder={focused || hasValue || !label ? placeholder : undefined}
93
93
  autoFocus={autoFocus}
94
94
  rows={rows}
95
95
  // onFocus={() => setFocused(true)}
@@ -121,7 +121,7 @@ export function TextField<T extends string | number>({
121
121
  disabled && "border border-transparent outline-none opacity-50 dark:opacity-50 text-gray-800 dark:text-gray-200",
122
122
  inputClassName
123
123
  )}
124
- placeholder={!focused && !hasValue ? undefined : placeholder}
124
+ placeholder={focused || hasValue || !label ? placeholder : undefined}
125
125
  autoFocus={autoFocus}
126
126
  onFocus={() => setFocused(true)}
127
127
  onBlur={() => setFocused(false)}
@@ -5,6 +5,7 @@ import {
5
5
  AdditionalFieldDelegate,
6
6
  CollectionSize,
7
7
  Entity,
8
+ EntityCollection,
8
9
  FilterValues,
9
10
  FireCMSContext,
10
11
  ResolvedProperty,
@@ -102,13 +103,14 @@ export const EntityCollectionTable = React.memo<EntityCollectionTableProps<any>>
102
103
  pageSize = 50,
103
104
  paginationEnabled,
104
105
  checkFilterCombination,
105
- setPopupCell,
106
+ setPopupCell
106
107
  },
107
108
  filterable = true,
108
109
  sortable = true,
109
110
  endAdornment,
110
- additionalHeaderWidget,
111
- AddColumnComponent
111
+ AddColumnComponent,
112
+ AdditionalHeaderWidget,
113
+ additionalIDHeaderWidget
112
114
  }: EntityCollectionTableProps<M>) {
113
115
 
114
116
  const largeLayout = useLargeLayout();
@@ -270,7 +272,7 @@ export const EntityCollectionTable = React.memo<EntityCollectionTableProps<any>>
270
272
 
271
273
  }, [additionalFieldsMap, size, selectedEntityIds]);
272
274
 
273
- const allColumns: VirtualTableColumn[] = useMemo(() => {
275
+ const collectionColumns: VirtualTableColumn[] = useMemo(() => {
274
276
  const columnsResult: VirtualTableColumn[] = Object.entries<ResolvedProperty>(properties)
275
277
  .flatMap(([key, property]) => getColumnKeysForProperty(property, key))
276
278
  .map(({
@@ -294,11 +296,9 @@ export const EntityCollectionTable = React.memo<EntityCollectionTableProps<any>>
294
296
  resolvedProperty: property,
295
297
  disabled
296
298
  },
297
- additionalHeaderWidget: (onHover) => additionalHeaderWidget?.({
298
- property,
299
- propertyKey: key,
300
- onHover
301
- })
299
+ AdditionalHeaderWidget: ({ onHover }) => AdditionalHeaderWidget
300
+ ? <AdditionalHeaderWidget property={property} propertyKey={key} onHover={onHover}/>
301
+ : undefined
302
302
  };
303
303
  });
304
304
 
@@ -322,16 +322,18 @@ export const EntityCollectionTable = React.memo<EntityCollectionTableProps<any>>
322
322
  title: "ID",
323
323
  resizable: false,
324
324
  frozen: largeLayout,
325
- headerAlign: "center"
325
+ headerAlign: "center",
326
+ align: "center",
327
+ AdditionalHeaderWidget: () => additionalIDHeaderWidget
326
328
  }), [largeLayout])
327
329
 
328
330
  const columns: VirtualTableColumn[] = useMemo(() => [
329
331
  idColumn,
330
332
  ...displayedColumnIds
331
333
  .map((p) => {
332
- return allColumns.find(c => c.key === p.key);
334
+ return collectionColumns.find(c => c.key === p.key);
333
335
  }).filter(Boolean) as VirtualTableColumn[]
334
- ], [allColumns, displayedColumnIds, idColumn]);
336
+ ], [collectionColumns, displayedColumnIds, idColumn]);
335
337
 
336
338
  const cellRenderer = useCallback((props: CellRendererParams<any>) => {
337
339
  const column = props.column;
@@ -373,7 +375,7 @@ export const EntityCollectionTable = React.memo<EntityCollectionTableProps<any>>
373
375
  onValueChange,
374
376
  size,
375
377
  selectedCell,
376
- selectedEntityIds,
378
+ selectedEntityIds
377
379
  }}
378
380
  >
379
381
 
@@ -503,3 +505,4 @@ function filterableProperty(property: ResolvedProperty, partOfArray = false): bo
503
505
  }
504
506
  return ["string", "number", "boolean", "date", "reference", "array"].includes(property.dataType);
505
507
  }
508
+
@@ -119,7 +119,7 @@ export type EntityCollectionTableProps<M extends Record<string, any>,
119
119
 
120
120
  endAdornment?: React.ReactNode;
121
121
 
122
- additionalHeaderWidget?: (props: {
122
+ AdditionalHeaderWidget?: (props: {
123
123
  property: ResolvedProperty,
124
124
  propertyKey: string,
125
125
  onHover: boolean,
@@ -127,6 +127,8 @@ export type EntityCollectionTableProps<M extends Record<string, any>,
127
127
 
128
128
  AddColumnComponent?: React.ComponentType;
129
129
 
130
+ additionalIDHeaderWidget?: React.ReactNode;
131
+
130
132
  }
131
133
 
132
134
  export type GetPropertyForProps<M extends Record<string, any>> = {
@@ -46,12 +46,12 @@ import {
46
46
  import { useUserConfigurationPersistence } from "../../../hooks/useUserConfigurationPersistence";
47
47
  import { EntityCollectionViewActions } from "./EntityCollectionViewActions";
48
48
  import { useEntityCollectionTableController } from "../EntityCollectionTable/useEntityCollectionTableController";
49
- import { Button, cn, Typography } from "../../../components";
49
+ import { Button, cn, IconButton, TextField, Tooltip, Typography } from "../../../components";
50
50
  import { Popover } from "../../../components/Popover";
51
51
  import { Skeleton } from "../../../components/Skeleton";
52
52
  import { setIn } from "formik";
53
53
  import { getSubcollectionColumnId } from "../EntityCollectionTable/internal/common";
54
- import { KeyboardTabIcon } from "../../../icons";
54
+ import { KeyboardTabIcon, SearchIcon } from "../../../icons";
55
55
  import { useColumnIds } from "./useColumnsIds";
56
56
  import { PopupFormField } from "../EntityCollectionTable/internal/popup_field/PopupFormField";
57
57
  import { GetPropertyForProps } from "../EntityCollectionTable/EntityCollectionTableProps";
@@ -517,10 +517,10 @@ export const EntityCollectionView = React.memo(
517
517
  .map((plugin, i) => {
518
518
  const HeaderAction = plugin.collectionView!.HeaderAction!;
519
519
  return <HeaderAction
520
+ onHover={onHover}
520
521
  key={`plugin_header_action_${i}`}
521
522
  propertyKey={propertyKey}
522
523
  property={property}
523
- onHover={onHover}
524
524
  fullPath={fullPath}
525
525
  parentPathSegments={parentPathSegments ?? []}/>;
526
526
  })}
@@ -571,8 +571,11 @@ export const EntityCollectionView = React.memo(
571
571
  />}
572
572
  hoverRow={hoverRow}
573
573
  inlineEditing={checkInlineEditing()}
574
- additionalHeaderWidget={buildAdditionalHeaderWidget}
574
+ AdditionalHeaderWidget={buildAdditionalHeaderWidget}
575
575
  AddColumnComponent={addColumnComponentInternal}
576
+ additionalIDHeaderWidget={<EntityIdHeaderWidget
577
+ path={fullPath}
578
+ collection={collection}/>}
576
579
  />
577
580
 
578
581
  <PopupFormField
@@ -686,3 +689,54 @@ function buildPropertyWidthOverwrite(key: string, width: number): PartialEntityC
686
689
  }
687
690
  return { properties: { [key]: { columnWidth: width } } } as PartialEntityCollection;
688
691
  }
692
+
693
+ function EntityIdHeaderWidget({ collection, path }: {
694
+ collection: EntityCollection,
695
+ path: string
696
+ }) {
697
+ const [openPopup, setOpenPopup] = React.useState(false);
698
+ const [searchString, setSearchString] = React.useState("");
699
+ const sideEntityController = useSideEntityController();
700
+ return (
701
+ <Tooltip title={!openPopup ? "Find by ID" : undefined}>
702
+ <Popover
703
+ open={openPopup}
704
+ onOpenChange={setOpenPopup}
705
+ trigger={
706
+ <IconButton size={"small"}>
707
+ <SearchIcon size={"small"}/>
708
+ </IconButton>
709
+ }
710
+ >
711
+ <form noValidate={true}
712
+ onSubmit={(e) => {
713
+ e.preventDefault();
714
+ if (!searchString) return;
715
+ setOpenPopup(false);
716
+ return sideEntityController.open({
717
+ entityId: searchString,
718
+ path,
719
+ collection,
720
+ updateUrl: true
721
+ });
722
+ }}
723
+ className={"text-gray-900 dark:text-white w-96 max-w-full"}>
724
+
725
+ <div className="flex p-4 w-full gap-4">
726
+ <TextField
727
+ placeholder={"Find entity by ID"}
728
+ size={"small"}
729
+ onChange={(e) => setSearchString(e.target.value)}
730
+ value={searchString}
731
+ className={"flex-grow"}/>
732
+ <Button variant={"outlined"}
733
+ disabled={!searchString}
734
+ type={"submit"}
735
+ >Go</Button>
736
+ </div>
737
+ </form>
738
+ </Popover>
739
+
740
+ </Tooltip>
741
+ );
742
+ }
@@ -5,7 +5,7 @@ import { VirtualTableColumn, VirtualTableSort, VirtualTableWhereFilterOp } from
5
5
  import { ErrorBoundary } from "../ErrorBoundary";
6
6
  import { Badge, Button, cn, IconButton, Popover } from "../../../components";
7
7
  import { defaultBorderMixin } from "../../../styles";
8
- import { ArrowUpwardIcon, ExpandMoreIcon, FilterIcon, FilterListIcon } from "../../../icons";
8
+ import { ArrowUpwardIcon, FilterListIcon } from "../../../icons";
9
9
 
10
10
  interface FilterFormProps<T> {
11
11
  column: VirtualTableColumn<T>;
@@ -37,7 +37,7 @@ type VirtualTableHeaderProps<M extends Record<string, any>> = {
37
37
  onFilterUpdate: (column: VirtualTableColumn, filterForProperty?: [VirtualTableWhereFilterOp, any]) => void;
38
38
  onClickResizeColumn?: (columnIndex: number, column: VirtualTableColumn) => void;
39
39
  createFilterField?: (props: FilterFormFieldProps<any>) => React.ReactNode;
40
- additionalHeaderWidget?: (onHover: boolean) => React.ReactNode;
40
+ AdditionalHeaderWidget?: (props: { onHover: boolean }) => React.ReactNode;
41
41
  };
42
42
 
43
43
  export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<any>>(
@@ -52,7 +52,7 @@ export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<any>>(
52
52
  column,
53
53
  onClickResizeColumn,
54
54
  createFilterField,
55
- additionalHeaderWidget
55
+ AdditionalHeaderWidget
56
56
  }: VirtualTableHeaderProps<M>) {
57
57
 
58
58
  const [onHover, setOnHover] = useState(false);
@@ -112,10 +112,12 @@ export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<any>>(
112
112
 
113
113
  </div>
114
114
  </div>
115
+ <>
115
116
 
116
- {column.sortable && (sort || hovered || openFilter) &&
117
- <>
118
- {additionalHeaderWidget && additionalHeaderWidget(hovered || openFilter)}
117
+ {AdditionalHeaderWidget &&
118
+ <AdditionalHeaderWidget onHover={onHover || openFilter}/>}
119
+
120
+ {column.sortable && (sort || hovered || openFilter) &&
119
121
  <Badge color="secondary"
120
122
  invisible={!sort}>
121
123
  <IconButton
@@ -133,8 +135,8 @@ export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<any>>(
133
135
  <ArrowUpwardIcon className={"rotate-180"}/>}
134
136
  </IconButton>
135
137
  </Badge>
136
- </>
137
- }
138
+ }
139
+ </>
138
140
 
139
141
  {column.filter && createFilterField && <div>
140
142
  <Badge color="secondary"
@@ -220,11 +222,7 @@ function FilterForm<M>({
220
222
 
221
223
  if (!filterField) return null;
222
224
  return (
223
- <div className={
224
- cn(
225
- "text-gray-900 dark:text-white",
226
- )
227
- }>
225
+ <div className={"text-gray-900 dark:text-white"}>
228
226
  <div
229
227
  className={cn(defaultBorderMixin, "py-4 px-6 text-xs font-semibold uppercase border-b")}>
230
228
  {column.title ?? id}
@@ -121,7 +121,7 @@ export const VirtualTableHeaderRow = ({
121
121
  onClickResizeColumn={onClickResizeColumn}
122
122
  column={column}
123
123
  createFilterField={createFilterField}
124
- additionalHeaderWidget={column.additionalHeaderWidget}/>
124
+ AdditionalHeaderWidget={column.AdditionalHeaderWidget}/>
125
125
  </ErrorBoundary>;
126
126
  })}
127
127
 
@@ -220,7 +220,7 @@ export interface VirtualTableColumn<CustomProps extends any = any> {
220
220
  /**
221
221
  * Additional children to be rendered in the header when hovering
222
222
  */
223
- additionalHeaderWidget?: (onHover:boolean) => React.ReactNode;
223
+ AdditionalHeaderWidget?: (props: { onHover: boolean }) => React.ReactNode;
224
224
  }
225
225
 
226
226
  /**
@@ -112,9 +112,9 @@ export type FireCMSPlugin<PROPS = any, FORM_PROPS = any> = {
112
112
  HeaderAction?: React.ComponentType<{
113
113
  property: ResolvedProperty,
114
114
  propertyKey: string,
115
- onHover: boolean,
116
115
  fullPath: string,
117
116
  parentPathSegments: string[],
117
+ onHover: boolean,
118
118
  }>;
119
119
 
120
120
  /**