@instantdb/components 0.22.89 → 0.22.90-experimental.drewh-ssr.20286580593.1

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@instantdb/components",
3
3
  "private": false,
4
- "version": "0.22.89",
4
+ "version": "0.22.90-experimental.drewh-ssr.20286580593.1",
5
5
  "type": "module",
6
6
  "description": "Instant's UI components",
7
7
  "homepage": "https://github.com/instantdb/instant/tree/main/client/packages/components",
@@ -54,8 +54,8 @@
54
54
  "vitest": "^1.6.0"
55
55
  },
56
56
  "peerDependencies": {
57
- "react": "^18.3.1",
58
- "react-dom": "^18.3.1"
57
+ "react": "^18.3.1 || ^19.0.0",
58
+ "react-dom": "^18.3.1 || ^19.0.0"
59
59
  },
60
60
  "dependencies": {
61
61
  "@babel/parser": "^8.0.0-beta.0",
@@ -74,6 +74,7 @@
74
74
  "@radix-ui/react-switch": "^1.2.6",
75
75
  "@radix-ui/react-toggle-group": "^1.0.4",
76
76
  "@radix-ui/react-tooltip": "^1.1.7",
77
+ "@radix-ui/react-visually-hidden": "^1.2.4",
77
78
  "@tanstack/react-table": "^8.21.3",
78
79
  "clsx": "^2.1.0",
79
80
  "copy-to-clipboard": "^3.3.3",
@@ -92,11 +93,11 @@
92
93
  "swr": "^2.2.4",
93
94
  "tailwind-merge": "^2.2.1",
94
95
  "uuid": "^11.1.0",
95
- "@instantdb/admin": "0.22.89",
96
- "@instantdb/core": "0.22.89",
97
- "@instantdb/react": "0.22.89",
98
- "@instantdb/version": "0.22.89",
99
- "@instantdb/platform": "0.22.89"
96
+ "@instantdb/admin": "0.22.90-experimental.drewh-ssr.20286580593.1",
97
+ "@instantdb/core": "0.22.90-experimental.drewh-ssr.20286580593.1",
98
+ "@instantdb/platform": "0.22.90-experimental.drewh-ssr.20286580593.1",
99
+ "@instantdb/react": "0.22.90-experimental.drewh-ssr.20286580593.1",
100
+ "@instantdb/version": "0.22.90-experimental.drewh-ssr.20286580593.1"
100
101
  },
101
102
  "scripts": {
102
103
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -55,10 +55,10 @@ export const ExplorerLayout = ({
55
55
  props.className,
56
56
  )}
57
57
  >
58
- <Dialog {...recentlyDeletedNsDialog}>
58
+ <Dialog title="Recently Deleted Namespaces" {...recentlyDeletedNsDialog}>
59
59
  <RecentlyDeletedNamespaces appId={props.appId} db={db} />
60
60
  </Dialog>
61
- <Dialog {...newNsDialog}>
61
+ <Dialog title="New Namespace" {...newNsDialog}>
62
62
  <NewNamespaceDialog
63
63
  db={db}
64
64
  onClose={(p) => {
@@ -91,7 +91,7 @@ const fillPropsWithDefaults = (
91
91
  // In uncontrolled mode, use the internal state
92
92
  explorerState: controlled ? (input.explorerState ?? null) : _explorerState,
93
93
  setExplorerState: input.setExplorerState || setExplorerState,
94
- useShadowDOM: input.useShadowDOM || false,
94
+ useShadowDOM: input.useShadowDOM === undefined ? true : input.useShadowDOM,
95
95
  };
96
96
  };
97
97
 
@@ -120,12 +120,20 @@ export const Explorer = (_props: WithOptional<ExplorerProps>) => {
120
120
  const [_explorerState, _setExplorerState] = useState<ExplorerNav | null>(
121
121
  null,
122
122
  );
123
+
123
124
  const props: WithDefaults<ExplorerProps> = fillPropsWithDefaults(
124
125
  _props,
125
126
  _explorerState,
126
127
  _setExplorerState,
127
128
  );
128
129
 
130
+ if (!props.adminToken) {
131
+ throw new Error('adminToken is required for explorer');
132
+ }
133
+ if (!props.appId) {
134
+ throw new Error('appId is required for explorer');
135
+ }
136
+
129
137
  // inside the component avoid setting explorer state directly
130
138
  // if change could be useful for history
131
139
  const { explorerState, setExplorerState } = props;
@@ -8,6 +8,10 @@ import {
8
8
  useSensor,
9
9
  useSensors,
10
10
  } from '@dnd-kit/core';
11
+ import { markdownTable } from 'markdown-table';
12
+
13
+ import { mkConfig, generateCsv, download } from 'export-to-csv';
14
+
11
15
  import { restrictToHorizontalAxis } from '@dnd-kit/modifiers';
12
16
  import {
13
17
  arrayMove,
@@ -77,6 +81,7 @@ import { ViewSettings } from './view-settings';
77
81
  import { isObject } from 'lodash';
78
82
  import { EditNamespaceDialog } from './edit-namespace-dialog';
79
83
  import { EditRowDialog } from './edit-row-dialog';
84
+ import copy from 'copy-to-clipboard';
80
85
 
81
86
  const fallbackItems: any[] = [];
82
87
 
@@ -88,6 +93,147 @@ export type TableColMeta = {
88
93
  copyable?: boolean;
89
94
  };
90
95
 
96
+ function exportToCSV(
97
+ rows: any[],
98
+ columns: ColumnDef<any>[],
99
+ namespace: string,
100
+ downloadFile: boolean = false,
101
+ ) {
102
+ if (rows.length === 0) return;
103
+
104
+ const visibleColumns = columns.filter(
105
+ (col) =>
106
+ col.id !== 'select-col' &&
107
+ col.header !== undefined &&
108
+ !(col.meta as TableColMeta | undefined)?.isLink,
109
+ );
110
+
111
+ const data = rows.map((row) => {
112
+ const rowData: Record<string, any> = {};
113
+ visibleColumns.forEach((col: any) => {
114
+ const value = row[col.header];
115
+ // Handle different data types
116
+ if (value === null || value === undefined) {
117
+ rowData[col.header] = '';
118
+ } else if (typeof value === 'object') {
119
+ rowData[col.header] = JSON.stringify(value);
120
+ } else {
121
+ rowData[col.header] = value;
122
+ }
123
+ });
124
+ return rowData;
125
+ });
126
+
127
+ const csvConfig = mkConfig({
128
+ fieldSeparator: ',',
129
+ filename: `${namespace}_export`,
130
+ decimalSeparator: '.',
131
+ useKeysAsHeaders: true,
132
+ });
133
+
134
+ const csv = generateCsv(csvConfig)(data);
135
+
136
+ if (downloadFile) {
137
+ download(csvConfig)(csv);
138
+ successToast('CSV file downloaded');
139
+ } else {
140
+ copy(csv.toString());
141
+ successToast('CSV copied to clipboard');
142
+ }
143
+ }
144
+
145
+ function exportToMarkdown(
146
+ rows: any[],
147
+ columns: any[],
148
+ namespace: string,
149
+ downloadFile: boolean = false,
150
+ ) {
151
+ if (rows.length === 0) return;
152
+
153
+ const visibleColumns = columns.filter(
154
+ (col) =>
155
+ col.id !== 'select-col' &&
156
+ col.header !== undefined &&
157
+ !(col.meta as TableColMeta | undefined)?.isLink,
158
+ );
159
+
160
+ const headers = visibleColumns.map((col: any) => col.header as string);
161
+
162
+ const data = rows.map((row) => {
163
+ return visibleColumns.map((col: any) => {
164
+ const value = row[col.header];
165
+ if (value === null || value === undefined) {
166
+ return ' ';
167
+ } else if (typeof value === 'object') {
168
+ return JSON.stringify(value);
169
+ } else {
170
+ return String(value);
171
+ }
172
+ });
173
+ });
174
+
175
+ const markdown = markdownTable([headers, ...data]);
176
+
177
+ if (downloadFile) {
178
+ const blob = new Blob([markdown], { type: 'text/markdown' });
179
+ const url = URL.createObjectURL(blob);
180
+ const a = document.createElement('a');
181
+ a.href = url;
182
+ a.download = `${namespace}_export.md`;
183
+ document.body.appendChild(a);
184
+ a.click();
185
+ document.body.removeChild(a);
186
+ URL.revokeObjectURL(url);
187
+ successToast('Markdown file downloaded');
188
+ } else {
189
+ copy(markdown);
190
+ successToast('Markdown copied to clipboard');
191
+ }
192
+ }
193
+
194
+ function exportToJSON(
195
+ rows: any[],
196
+ columns: any[],
197
+ namespace: string,
198
+ downloadFile: boolean = false,
199
+ ) {
200
+ if (rows.length === 0) return;
201
+
202
+ const visibleColumns = columns.filter(
203
+ (col) =>
204
+ col.id !== 'select-col' &&
205
+ col.header !== undefined &&
206
+ !(col.meta as TableColMeta | undefined)?.isLink,
207
+ );
208
+
209
+ const data = rows.map((row) => {
210
+ const rowData: Record<string, any> = {};
211
+ visibleColumns.forEach((col: any) => {
212
+ const value = row[col.header];
213
+ rowData[col.header] = value;
214
+ });
215
+ return rowData;
216
+ });
217
+
218
+ const json = JSON.stringify(data, null, 2);
219
+
220
+ if (downloadFile) {
221
+ const blob = new Blob([json], { type: 'application/json' });
222
+ const url = URL.createObjectURL(blob);
223
+ const a = document.createElement('a');
224
+ a.href = url;
225
+ a.download = `${namespace}_export.json`;
226
+ document.body.appendChild(a);
227
+ a.click();
228
+ document.body.removeChild(a);
229
+ URL.revokeObjectURL(url);
230
+ successToast('JSON file downloaded');
231
+ } else {
232
+ copy(json);
233
+ successToast('JSON copied to clipboard');
234
+ }
235
+ }
236
+
91
237
  export const InnerExplorer: React.FC<{
92
238
  db: InstantReactAbstractDatabase<any, any>;
93
239
  namespaces: SchemaNamespace[];
@@ -684,6 +830,7 @@ export const InnerExplorer: React.FC<{
684
830
  return (
685
831
  <>
686
832
  <Dialog
833
+ title="Delete Rows"
687
834
  open={deleteDataConfirmationOpen}
688
835
  onClose={() => setDeleteDataConfirmationOpen(false)}
689
836
  >
@@ -754,6 +901,7 @@ export const InnerExplorer: React.FC<{
754
901
  ) : null}
755
902
  </Dialog>
756
903
  <Dialog
904
+ title="Edit Row"
757
905
  open={addItemDialogOpen}
758
906
  onClose={() => setAddItemDialogOpen(false)}
759
907
  >
@@ -767,6 +915,7 @@ export const InnerExplorer: React.FC<{
767
915
  ) : null}
768
916
  </Dialog>
769
917
  <Dialog
918
+ title="Edit Row"
770
919
  open={!!selectedEditableItem}
771
920
  onClose={() => setEditableRowId(null)}
772
921
  >
@@ -780,6 +929,7 @@ export const InnerExplorer: React.FC<{
780
929
  ) : null}
781
930
  </Dialog>
782
931
  <Dialog
932
+ title="Edit Namespace"
783
933
  stopFocusPropagation={true}
784
934
  open={Boolean(editNs)}
785
935
  onClose={() => setEditNs(null)}
@@ -1086,12 +1236,12 @@ export const InnerExplorer: React.FC<{
1086
1236
  allItems,
1087
1237
  checkedIds,
1088
1238
  );
1089
- // exportToCSV(
1090
- // selectedRows,
1091
- // columns,
1092
- // selectedNamespace.name,
1093
- // isShiftPressed,
1094
- // );
1239
+ exportToCSV(
1240
+ selectedRows,
1241
+ columns,
1242
+ selectedNamespace.name,
1243
+ isShiftPressed,
1244
+ );
1095
1245
  setDropdownOpen(false);
1096
1246
  }}
1097
1247
  className="flex items-center gap-2"
@@ -1106,12 +1256,12 @@ export const InnerExplorer: React.FC<{
1106
1256
  allItems,
1107
1257
  checkedIds,
1108
1258
  );
1109
- // exportToMarkdown(
1110
- // selectedRows,
1111
- // columns,
1112
- // selectedNamespace.name,
1113
- // isShiftPressed,
1114
- // );
1259
+ exportToMarkdown(
1260
+ selectedRows,
1261
+ columns,
1262
+ selectedNamespace.name,
1263
+ isShiftPressed,
1264
+ );
1115
1265
  setDropdownOpen(false);
1116
1266
  }}
1117
1267
  className="flex items-center gap-2"
@@ -1128,12 +1278,12 @@ export const InnerExplorer: React.FC<{
1128
1278
  allItems,
1129
1279
  checkedIds,
1130
1280
  );
1131
- // exportToJSON(
1132
- // selectedRows,
1133
- // columns,
1134
- // selectedNamespace.name,
1135
- // isShiftPressed,
1136
- // );
1281
+ exportToJSON(
1282
+ selectedRows,
1283
+ columns,
1284
+ selectedNamespace.name,
1285
+ isShiftPressed,
1286
+ );
1137
1287
  setDropdownOpen(false);
1138
1288
  }}
1139
1289
  className="flex items-center gap-2"
@@ -25,6 +25,7 @@ export const ViewSettings = ({
25
25
  onClick={() => setDialogOpen(true)}
26
26
  />
27
27
  <Dialog
28
+ title="Explorer View Settings"
28
29
  hideCloseButton={true}
29
30
  onClose={() => setDialogOpen(false)}
30
31
  open={dialogOpen}
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
  import { Toaster, toast } from 'sonner';
3
+ import { VisuallyHidden } from '@radix-ui/react-visually-hidden';
3
4
  import { Editor, Monaco, OnMount } from '@monaco-editor/react';
4
5
  import type { ClassValue } from 'clsx';
5
6
  import clsx from 'clsx';
@@ -635,6 +636,7 @@ function DialogContent({
635
636
  className,
636
637
  children,
637
638
  showCloseButton = false,
639
+ title,
638
640
  ...props
639
641
  }: React.ComponentProps<typeof DialogPrimitive.Content> & {
640
642
  showCloseButton?: boolean;
@@ -654,6 +656,9 @@ function DialogContent({
654
656
  )}
655
657
  {...props}
656
658
  >
659
+ <VisuallyHidden>
660
+ <DialogTitle>{title}</DialogTitle>
661
+ </VisuallyHidden>
657
662
  {children}
658
663
  {showCloseButton && (
659
664
  <DialogPrimitive.Close
@@ -677,11 +682,13 @@ export function Dialog({
677
682
  children,
678
683
  onClose,
679
684
  className,
685
+ title,
680
686
  stopFocusPropagation = false,
681
687
  hideCloseButton = false,
682
688
  }: {
683
689
  open: boolean;
684
690
  children: React.ReactNode;
691
+ title: string;
685
692
  onClose: () => void;
686
693
  className?: string;
687
694
  stopFocusPropagation?: boolean;
@@ -697,6 +704,7 @@ export function Dialog({
697
704
  open={open}
698
705
  >
699
706
  <DialogContent
707
+ title={title}
700
708
  onFocusCapture={(e) => {
701
709
  if (stopFocusPropagation) {
702
710
  e.stopPropagation();
@@ -1533,6 +1541,7 @@ export function Fence({
1533
1541
  import * as SwitchPrimitive from '@radix-ui/react-switch';
1534
1542
  import { rosePineDawnTheme } from './rosePineDawnTheme';
1535
1543
  import { useShadowRoot, useShadowDarkMode } from './StyleMe';
1544
+ import { DialogTitle } from '@radix-ui/react-dialog';
1536
1545
  function Switch({
1537
1546
  className,
1538
1547
  ...props