@lobb-js/studio 0.42.0 → 0.44.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 (38) hide show
  1. package/dist/components/confirmationDialog/confirmationDialog.svelte +1 -1
  2. package/dist/components/dataTable/dataTable.svelte +49 -17
  3. package/dist/components/dataTable/filter.svelte +26 -23
  4. package/dist/components/dataTable/header.svelte +17 -13
  5. package/dist/components/dataTable/header.svelte.d.ts +1 -0
  6. package/dist/components/dataTable/listViewChildren.svelte +60 -77
  7. package/dist/components/dataTable/listViewChildren.svelte.d.ts +1 -1
  8. package/dist/components/dataTable/table.svelte +20 -61
  9. package/dist/components/dataTable/table.svelte.d.ts +2 -2
  10. package/dist/components/detailView/changeTreeUtils.d.ts +7 -0
  11. package/dist/components/detailView/changeTreeUtils.js +47 -0
  12. package/dist/components/detailView/create/createDetailView.svelte +17 -13
  13. package/dist/components/detailView/detailView.svelte +7 -2
  14. package/dist/components/detailView/detailView.svelte.d.ts +1 -0
  15. package/dist/components/detailView/fieldInput.svelte +10 -9
  16. package/dist/components/detailView/fieldInput.svelte.d.ts +1 -0
  17. package/dist/components/detailView/update/updateDetailView.svelte +22 -18
  18. package/dist/components/drawer.svelte +15 -2
  19. package/dist/components/foreingKeyInput.svelte +32 -60
  20. package/dist/components/foreingKeyInput.svelte.d.ts +1 -1
  21. package/dist/components/polymorphicInput.svelte +42 -66
  22. package/dist/components/polymorphicInput.svelte.d.ts +1 -0
  23. package/package.json +2 -2
  24. package/src/app.css +2 -2
  25. package/src/lib/components/confirmationDialog/confirmationDialog.svelte +1 -1
  26. package/src/lib/components/dataTable/dataTable.svelte +49 -17
  27. package/src/lib/components/dataTable/filter.svelte +26 -23
  28. package/src/lib/components/dataTable/header.svelte +17 -13
  29. package/src/lib/components/dataTable/listViewChildren.svelte +60 -77
  30. package/src/lib/components/dataTable/table.svelte +20 -61
  31. package/src/lib/components/detailView/changeTreeUtils.ts +39 -0
  32. package/src/lib/components/detailView/create/createDetailView.svelte +17 -13
  33. package/src/lib/components/detailView/detailView.svelte +7 -2
  34. package/src/lib/components/detailView/fieldInput.svelte +10 -9
  35. package/src/lib/components/detailView/update/updateDetailView.svelte +22 -18
  36. package/src/lib/components/drawer.svelte +15 -2
  37. package/src/lib/components/foreingKeyInput.svelte +32 -60
  38. package/src/lib/components/polymorphicInput.svelte +42 -66
@@ -4,16 +4,13 @@
4
4
  import DataTable from "./dataTable/dataTable.svelte";
5
5
  import Drawer from "./drawer.svelte";
6
6
  import * as Popover from "./ui/popover/index";
7
- import { getCollectionPrimaryField } from "./dataTable/utils";
8
- import { getStudioContext } from "../context";
9
7
  import { ArrowLeft, Link, ChevronDown, Plus } from "lucide-svelte";
10
8
  import CreateDetailView from "./detailView/create/createDetailView.svelte";
11
9
 
12
- const { ctx, lobb } = getStudioContext();
13
-
14
10
  interface Props {
15
11
  collectionField: string;
16
12
  idField: string;
13
+ virtualField: string;
17
14
  targetCollections: string[];
18
15
  entry: Record<string, any>;
19
16
  destructive?: boolean;
@@ -22,64 +19,68 @@
22
19
  let {
23
20
  collectionField,
24
21
  idField,
22
+ virtualField,
25
23
  targetCollections,
26
24
  entry = $bindable(),
27
25
  destructive,
28
26
  }: Props = $props();
29
27
 
30
28
  const selectedCollection = $derived(entry[collectionField] ?? null);
31
- const selectedId = $derived(entry[idField] ?? null);
29
+ const selectedId = $derived(entry[idField] ?? null);
30
+ const virtualVal = $derived(entry[virtualField] ?? null);
31
+
32
+ let initialId = $state<number | null | undefined>(undefined);
33
+ let initialCollection = $state<string | null | undefined>(undefined);
34
+ onMount(() => {
35
+ initialId = entry[idField] ?? null;
36
+ initialCollection = entry[collectionField] ?? null;
37
+ });
38
+
39
+ const isPendingCreate = $derived(virtualVal && typeof virtualVal === 'object' && virtualVal.create);
40
+ const isChanged = $derived(
41
+ initialId !== undefined &&
42
+ !isPendingCreate &&
43
+ (selectedId !== initialId || selectedCollection !== initialCollection)
44
+ );
45
+
46
+ const bgClass = $derived(
47
+ isPendingCreate ? '!bg-green-500/5 border-green-500/40' :
48
+ isChanged ? '!bg-orange-500/5' :
49
+ ''
50
+ );
32
51
 
33
- let displayName = $state<string | null>(null);
34
52
  let collectionPopoverOpen = $state(false);
35
53
  let recordDrawerOpen = $state(false);
36
54
  let createDrawerOpen = $state(false);
37
55
 
38
- onMount(async () => {
39
- if (selectedCollection == null || selectedId == null) return;
40
- try {
41
- const res = await lobb.findOne(selectedCollection, selectedId);
42
- const record = await res.json();
43
- const primaryFieldName = getCollectionPrimaryField(ctx, selectedCollection);
44
- displayName = primaryFieldName ? String(record[primaryFieldName]) : null;
45
- } catch {
46
- displayName = null;
47
- }
48
- });
49
-
50
- $effect(() => {
51
- if (entry[idField] == null) displayName = null;
52
- });
53
-
54
56
  function onCollectionChange(col: string) {
55
57
  collectionPopoverOpen = false;
56
58
  if (entry[collectionField] !== col) {
57
- entry = { ...entry, [collectionField]: col, [idField]: null };
59
+ entry = { ...entry, [collectionField]: col, [idField]: null, [virtualField]: undefined };
58
60
  }
59
61
  }
60
62
 
61
63
  function onIdChange(e: Event) {
62
64
  const raw = (e.target as HTMLInputElement).value;
63
65
  const id = raw === "" ? null : Number(raw);
64
- entry = { ...entry, [idField]: id };
66
+ entry = { ...entry, [idField]: id, [virtualField]: undefined };
65
67
  }
66
68
 
67
69
  function onRecordSelect(record: any) {
68
- const primaryFieldName = getCollectionPrimaryField(ctx, selectedCollection!);
69
- entry = { ...entry, [idField]: record.id };
70
- displayName = primaryFieldName ? String(record[primaryFieldName]) : null;
70
+ entry = { ...entry, [idField]: record.id, [virtualField]: undefined };
71
71
  recordDrawerOpen = false;
72
72
  }
73
73
 
74
74
  async function onPolyCreated(record: any) {
75
- const primaryFieldName = getCollectionPrimaryField(ctx, selectedCollection!);
76
- entry = { ...entry, [idField]: record.id };
77
- displayName = primaryFieldName ? String(record[primaryFieldName]) : null;
75
+ if (!record.id) {
76
+ entry = { ...entry, [virtualField]: { collection: selectedCollection, create: record }, [collectionField]: selectedCollection, [idField]: null };
77
+ } else {
78
+ entry = { ...entry, [idField]: record.id, [virtualField]: undefined };
79
+ }
78
80
  }
79
81
  </script>
80
82
 
81
- <div class="flex h-9 w-full items-center gap-1.5 rounded-md border pl-1.5 pr-9 text-xs bg-muted {destructive ? 'border-destructive bg-destructive/10' : ''}">
82
- <!-- Collection picker -->
83
+ <div class="flex h-9 w-full items-center gap-1.5 rounded-md border pl-1.5 pr-9 text-xs bg-muted {bgClass} {destructive ? '!bg-destructive/10 border-destructive' : ''}">
83
84
  <Popover.Root bind:open={collectionPopoverOpen}>
84
85
  <Popover.Trigger>
85
86
  {#snippet child({ props })}
@@ -107,39 +108,22 @@
107
108
  </Popover.Content>
108
109
  </Popover.Root>
109
110
 
110
- <!-- Transparent id input -->
111
111
  <input
112
- placeholder="NULL"
112
+ placeholder={isPendingCreate ? "AUTO GENERATED" : "NULL"}
113
113
  type="number"
114
114
  class="min-w-0 flex-1 bg-transparent outline-none text-xs placeholder:text-muted-foreground"
115
115
  value={selectedId ?? ""}
116
116
  oninput={onIdChange}
117
117
  />
118
118
 
119
- <!-- Primary field badge -->
120
- {#if displayName}
121
- <div class="flex shrink-0 items-center bg-background rounded-full border h-6 px-3 shadow-sm">
122
- {displayName}
123
- </div>
124
- {/if}
125
-
126
- <!-- Create / Select record buttons -->
127
119
  {#if selectedCollection}
128
- <Button
129
- class="h-6 shrink-0 px-2 font-normal text-xs"
130
- variant="outline"
131
- onclick={() => (createDrawerOpen = true)}
132
- >
120
+ <Button class="h-6 shrink-0 px-2 font-normal text-xs" variant="outline" onclick={() => (createDrawerOpen = true)}>
133
121
  <Plus size="13" />
134
122
  Create
135
123
  </Button>
136
- <Button
137
- class="h-6 shrink-0 px-2 font-normal text-xs"
138
- variant="outline"
139
- onclick={() => (recordDrawerOpen = true)}
140
- >
124
+ <Button class="h-6 shrink-0 px-2 font-normal text-xs" variant="outline" onclick={() => (recordDrawerOpen = true)}>
141
125
  <Link size="13" />
142
- Select
126
+ Link
143
127
  </Button>
144
128
  {/if}
145
129
  </div>
@@ -147,26 +131,17 @@
147
131
  {#if recordDrawerOpen}
148
132
  <Drawer onHide={async () => { recordDrawerOpen = false }}>
149
133
  <div class="flex h-12 items-center gap-4 border-b px-4">
150
- <Button
151
- variant="outline"
152
- onclick={() => (recordDrawerOpen = false)}
153
- class="h-8 w-8 rounded-full text-xs font-normal"
154
- Icon={ArrowLeft}
155
- />
134
+ <Button variant="outline" onclick={() => (recordDrawerOpen = false)} class="h-8 w-8 rounded-full text-xs font-normal" Icon={ArrowLeft} />
156
135
  <div class="flex items-center gap-2">
157
136
  <div class="text-sm">Select record from</div>
158
- <span class="rounded-md border bg-muted px-2 py-0.5 text-sm">
159
- {selectedCollection}
160
- </span>
137
+ <span class="rounded-md border bg-muted px-2 py-0.5 text-sm">{selectedCollection}</span>
161
138
  </div>
162
139
  </div>
163
140
  <div class="flex-1 overflow-y-auto bg-muted">
164
141
  <DataTable
165
142
  collectionName={selectedCollection!}
166
- tableProps={{
167
- showCheckboxes: false,
168
- select: { onSelect: onRecordSelect },
169
- }}
143
+ filter={selectedId != null ? { id: { $ne: selectedId } } : undefined}
144
+ tableProps={{ showCheckboxes: false, select: { onSelect: onRecordSelect } }}
170
145
  />
171
146
  </div>
172
147
  </Drawer>
@@ -176,6 +151,7 @@
176
151
  <CreateDetailView
177
152
  collectionName={selectedCollection}
178
153
  onCreated={onPolyCreated}
154
+ onChanges={() => {}}
179
155
  onCancel={async () => { createDrawerOpen = false; }}
180
156
  />
181
157
  {/if}
@@ -1,6 +1,7 @@
1
1
  interface Props {
2
2
  collectionField: string;
3
3
  idField: string;
4
+ virtualField: string;
4
5
  targetCollections: string[];
5
6
  entry: Record<string, any>;
6
7
  destructive?: boolean;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lobb-js/studio",
3
3
  "license": "UNLICENSED",
4
- "version": "0.42.0",
4
+ "version": "0.44.0",
5
5
  "type": "module",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -45,7 +45,7 @@
45
45
  "postpublish": "./scripts/postpublish.sh"
46
46
  },
47
47
  "devDependencies": {
48
- "@lobb-js/core": "^0.37.1",
48
+ "@lobb-js/core": "^0.38.0",
49
49
  "@chromatic-com/storybook": "^4.1.2",
50
50
  "@playwright/test": "^1.60.0",
51
51
  "@storybook/addon-a11y": "^10.0.1",
package/src/app.css CHANGED
@@ -24,7 +24,7 @@
24
24
  --secondary: oklch(0.97 0.001 138);
25
25
  --secondary-foreground: oklch(0.20 0.012 138);
26
26
 
27
- --muted: oklch(0.97 0.001 138);
27
+ --muted: oklch(0.98 0.001 138);
28
28
  --muted-foreground: oklch(0.55 0.012 138);
29
29
 
30
30
  --accent: oklch(0.95 0.001 138);
@@ -52,7 +52,7 @@
52
52
  --secondary: oklch(0.24 0.012 138);
53
53
  --secondary-foreground: oklch(0.94 0.012 138);
54
54
 
55
- --muted: oklch(0.24 0.012 138);
55
+ --muted: oklch(0.28 0.012 138);
56
56
  --muted-foreground: oklch(0.65 0.012 138);
57
57
 
58
58
  --accent: oklch(0.27 0.012 138);
@@ -17,7 +17,7 @@
17
17
  <AlertDialog.Content>
18
18
  <AlertDialog.Header>
19
19
  <AlertDialog.Title>{title}</AlertDialog.Title>
20
- <AlertDialog.Description class="whitespace-pre-line">
20
+ <AlertDialog.Description class="whitespace-pre-wrap font-mono text-xs">
21
21
  {description}
22
22
  </AlertDialog.Description>
23
23
  </AlertDialog.Header>
@@ -14,8 +14,10 @@
14
14
  import Table, { type TableProps } from "./table.svelte";
15
15
  import { getCollectionColumns, getCollectionParamsFields } from "./utils";
16
16
  import CanAccess from "../canAccess.svelte";
17
- import { Pencil, Trash, Unlink, RotateCcw } from "lucide-svelte";
17
+ import { Pencil, Trash, Unlink, RotateCcw, Network } from "lucide-svelte";
18
18
  import ListViewChildren from "./listViewChildren.svelte";
19
+ import Drawer from "../drawer.svelte";
20
+ import { ArrowLeft } from "lucide-svelte";
19
21
  import FieldCell from "./fieldCell.svelte";
20
22
  import Skeleton from "../ui/skeleton/skeleton.svelte";
21
23
  import Button from "../ui/button/button.svelte";
@@ -73,6 +75,7 @@
73
75
  }: Props = $props();
74
76
 
75
77
  const isRecordingMode = onChanges !== undefined;
78
+ const isSelectMode = $derived(tableProps?.select != null);
76
79
  let localChanges = $state<ChildrenChanges>(
77
80
  untrack(() => changes) ?? { created: [], updated: [], deleted: [], linked: [], unlinked: [] }
78
81
  );
@@ -135,17 +138,17 @@
135
138
  const state = entry._recordingState;
136
139
  const border = cellIndex === 0 ? {
137
140
  deleted: 'border-l-2 border-l-red-500',
138
- unlinked: 'border-l-2 border-l-orange-500',
141
+ unlinked: 'border-l-2 border-l-slate-500',
139
142
  created: 'border-l-2 border-l-green-500',
140
143
  linked: 'border-l-2 border-l-blue-500',
141
- updated: 'border-l-2 border-l-amber-500',
144
+ updated: 'border-l-2 border-l-orange-500',
142
145
  }[state as string] ?? '' : '';
143
146
  const bg: Record<string, string> = {
144
- deleted: '!bg-red-500/5 opacity-50',
145
- unlinked: '!bg-orange-500/5 opacity-50',
147
+ deleted: '!bg-red-500/5',
148
+ unlinked: '!bg-slate-500/5',
146
149
  created: '!bg-green-500/5',
147
150
  linked: '!bg-blue-500/5',
148
- updated: '!bg-amber-500/5',
151
+ updated: '!bg-orange-500/5',
149
152
  };
150
153
  return `${bg[state as string] ?? ''} ${border}`.trim();
151
154
  }
@@ -220,6 +223,10 @@
220
223
  (ctx.meta.collections[collectionName]?.children ?? [])
221
224
  .some((c: any) => c.type === "fk" || c.type === "m2m" || c.type === "polymorphic")
222
225
  );
226
+ // Select-mode drawer has no checkbox, edit, delete, or unlink — the
227
+ // left-sticky tools column would render empty, so drop it entirely.
228
+ const showLeftTools = $derived(!isSelectMode);
229
+ let childrenDrawerEntry = $state<Record<string, any> | null>(null);
223
230
 
224
231
  // requests the data from the server when the params is changed
225
232
  $effect(() => {
@@ -404,6 +411,7 @@
404
411
  bind:selectedRecords
405
412
  {showImport}
406
413
  {showFilter}
414
+ showCreate={!isSelectMode}
407
415
  {loading}
408
416
  {parentContext}
409
417
  onLink={isRecordingMode ? handleLink : undefined}
@@ -453,7 +461,6 @@
453
461
  <Table
454
462
  {data}
455
463
  {columns}
456
- showCollapsible={doesCollectionHasChildren}
457
464
  selectByColumn="id"
458
465
  showLastRowBorder={true}
459
466
  showLastColumnBorder={true}
@@ -461,11 +468,25 @@
461
468
  bind:selectedRecords
462
469
  bind:tableWidth={dataTableWidth}
463
470
  {...tableProps}
471
+ {showLeftTools}
464
472
  rowActions={hasRowActions ? rowActionsSnippet : undefined}
465
473
  onCellClass={isRecordingMode ? onCellClass : undefined}>
474
+ {#snippet preTools(entry)}
475
+ {#if doesCollectionHasChildren}
476
+ <Button
477
+ class="h-6 w-6 text-muted-foreground hover:bg-transparent"
478
+ variant="ghost"
479
+ size="icon"
480
+ onclick={() => { childrenDrawerEntry = entry; }}
481
+ Icon={Network}
482
+ title="Show children"
483
+ aria-label={`Show children of record ${entry.id}`}
484
+ ></Button>
485
+ {/if}
486
+ {/snippet}
466
487
  {#snippet tools(entry)}
467
488
  {#if entry._recordingState !== 'deleted' && entry._recordingState !== 'unlinked'}
468
- {#if showEdit}
489
+ {#if showEdit && !isSelectMode}
469
490
  {@const isPending = entry._recordingState === 'created'}
470
491
  <CanAccess collection={collectionName} action="update">
471
492
  <UpdateDetailViewButton
@@ -524,15 +545,6 @@
524
545
  refresh={() => { params = { ...params }; }}
525
546
  />
526
547
  {/snippet}
527
- {#snippet collapsible(entry)}
528
- <ListViewChildren
529
- {collectionName}
530
- recordId={entry.id}
531
- width={dataTableWidth > dataTableContainerWidth
532
- ? dataTableContainerWidth
533
- : dataTableWidth}
534
- />
535
- {/snippet}
536
548
  </Table>
537
549
  {/if}
538
550
  </div>
@@ -546,3 +558,23 @@
546
558
  />
547
559
  {/if}
548
560
  </div>
561
+
562
+ {#if childrenDrawerEntry}
563
+ <Drawer position="bottom" onHide={async () => { childrenDrawerEntry = null; }}>
564
+ <div class="flex h-12 items-center gap-4 border-b px-4 shrink-0">
565
+ <Button
566
+ variant="outline"
567
+ onclick={() => { childrenDrawerEntry = null; }}
568
+ class="h-8 w-8 rounded-full text-xs font-normal"
569
+ Icon={ArrowLeft}
570
+ ></Button>
571
+ <div class="flex items-center gap-2 text-sm">
572
+ <span>Children of</span>
573
+ <span class="rounded-md border bg-muted px-2 py-0.5">{collectionName} #{childrenDrawerEntry.id}</span>
574
+ </div>
575
+ </div>
576
+ <div class="flex-1 overflow-y-auto">
577
+ <ListViewChildren {collectionName} recordId={String(childrenDrawerEntry.id)} />
578
+ </div>
579
+ </Drawer>
580
+ {/if}
@@ -20,11 +20,13 @@
20
20
  // • anything else → explicit $and/$or arrays.
21
21
 
22
22
  import * as Select from "../ui/select/index.js";
23
+ import * as Popover from "../ui/popover/index.js";
23
24
  import Button from "../ui/button/button.svelte";
24
- import { Plus, X, Boxes, Settings2 } from "lucide-svelte";
25
+ import { ChevronDown, Plus, X, Boxes, Settings2 } from "lucide-svelte";
25
26
  import { getStudioContext } from "../../context";
26
27
  import { getFieldIcon } from "./utils";
27
28
  import { getFieldRelationTarget } from "../../relations";
29
+ import FieldPicker from "./fieldPicker.svelte";
28
30
 
29
31
  const { ctx } = getStudioContext();
30
32
 
@@ -43,6 +45,9 @@
43
45
  isEmpty = $bindable(true),
44
46
  }: Props = $props();
45
47
 
48
+ // Per-rule open state for the field-picker popovers.
49
+ let fieldPickerOpen = $state<Record<string, boolean>>({});
50
+
46
51
  type OperatorDef = {
47
52
  value: string;
48
53
  label: string;
@@ -471,30 +476,28 @@
471
476
  {@const currentOp = ops.find((o) => o.value === rule.operator) ?? ops[0]}
472
477
  {@const kind = getFieldKind(rule.field)}
473
478
  {@const FieldIcon = getFieldIcon(ctx, rule.field, collectionName)}
474
- <!-- Field picker -->
475
- <Select.Root
476
- type="single"
477
- value={rule.field}
478
- onValueChange={(v) => v && patchRuleInPlace(rule.id, { field: v })}
479
- >
480
- <Select.Trigger class="bg-muted h-7 w-36 text-xs">
481
- <div class="inline-flex items-center gap-1.5">
479
+ <!-- Field picker — typeahead popover, same widget Sort uses -->
480
+ <Popover.Root bind:open={fieldPickerOpen[rule.id]}>
481
+ <Popover.Trigger
482
+ class="inline-flex h-7 w-36 items-center justify-between gap-1.5 rounded-md border bg-muted px-2 text-xs"
483
+ >
484
+ <div class="inline-flex items-center gap-1.5 truncate">
482
485
  <FieldIcon size="13" />
483
- {rule.field}
486
+ <span class="truncate">{rule.field}</span>
484
487
  </div>
485
- </Select.Trigger>
486
- <Select.Content>
487
- {#each allFieldNames as fname}
488
- {@const OptionIcon = getFieldIcon(ctx, fname, collectionName)}
489
- <Select.Item value={fname}>
490
- <div class="inline-flex items-center gap-1.5">
491
- <OptionIcon size="13" />
492
- {fname}
493
- </div>
494
- </Select.Item>
495
- {/each}
496
- </Select.Content>
497
- </Select.Root>
488
+ <ChevronDown size="13" class="text-muted-foreground shrink-0" />
489
+ </Popover.Trigger>
490
+ <Popover.Content class="w-64 p-2">
491
+ <FieldPicker
492
+ {collectionName}
493
+ placeholder="Pick a field…"
494
+ onPick={(fname: string) => {
495
+ patchRuleInPlace(rule.id, { field: fname });
496
+ fieldPickerOpen[rule.id] = false;
497
+ }}
498
+ />
499
+ </Popover.Content>
500
+ </Popover.Root>
498
501
 
499
502
  <!-- Operator picker -->
500
503
  <Select.Root
@@ -26,6 +26,7 @@
26
26
  onCreate?: (changes: Changes) => void;
27
27
  showImport?: boolean;
28
28
  showFilter?: boolean;
29
+ showCreate?: boolean;
29
30
  loading?: boolean;
30
31
  left?: Snippet<[]>;
31
32
  excludeIds?: (string | number)[];
@@ -40,6 +41,7 @@
40
41
  onCreate,
41
42
  showImport = true,
42
43
  showFilter = true,
44
+ showCreate = true,
43
45
  loading = false,
44
46
  left,
45
47
  excludeIds = [],
@@ -154,7 +156,7 @@
154
156
  {headerIsSmall ? "" : "Refresh"}
155
157
  {/if}
156
158
  </Button>
157
- {#if showImport}
159
+ {#if showImport && showCreate}
158
160
  <CanAccess collection={collectionName} action="create">
159
161
  <ImportButton
160
162
  {collectionName}
@@ -183,17 +185,19 @@
183
185
  {headerIsSmall ? "" : "Link"}
184
186
  </SelectRecord>
185
187
  {/if}
186
- <CanAccess collection={collectionName} action="create">
187
- <CreateDetailViewButton
188
- {collectionName}
189
- variant="default"
190
- size="sm"
191
- Icon={Plus}
192
- onChanges={onCreate ? handleCreate : undefined}
193
- onSuccessfullSave={onCreate ? undefined : handleCreate}
194
- >
195
- {headerIsSmall ? "" : "Create"}
196
- </CreateDetailViewButton>
197
- </CanAccess>
188
+ {#if showCreate}
189
+ <CanAccess collection={collectionName} action="create">
190
+ <CreateDetailViewButton
191
+ {collectionName}
192
+ variant="default"
193
+ size="sm"
194
+ Icon={Plus}
195
+ onChanges={onCreate ? handleCreate : undefined}
196
+ onSuccessfullSave={onCreate ? undefined : handleCreate}
197
+ >
198
+ {headerIsSmall ? "" : "Create"}
199
+ </CreateDetailViewButton>
200
+ </CanAccess>
201
+ {/if}
198
202
  </div>
199
203
  </div>