@rebasepro/studio 0.0.1-canary.eae7889 → 0.1.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 (66) hide show
  1. package/dist/{ApiExplorer-gMJt5JrS.js → ApiExplorer-DHVmWYfK.js} +13 -13
  2. package/dist/ApiExplorer-DHVmWYfK.js.map +1 -0
  3. package/dist/AuthSimulationSelector-CM488Eei.js +106 -0
  4. package/dist/AuthSimulationSelector-CM488Eei.js.map +1 -0
  5. package/dist/{JSEditor-D8nVp3Lp.js → JSEditor-CSHA0t_O.js} +2 -2
  6. package/dist/{JSEditor-D8nVp3Lp.js.map → JSEditor-CSHA0t_O.js.map} +1 -1
  7. package/dist/{RLSEditor-DBH09u9v.js → RLSEditor-BzDjqo6w.js} +46 -5
  8. package/dist/RLSEditor-BzDjqo6w.js.map +1 -0
  9. package/dist/{SQLEditor-CkVx9vgr.js → SQLEditor-Cr9Kg_Qg.js} +63 -75
  10. package/dist/SQLEditor-Cr9Kg_Qg.js.map +1 -0
  11. package/dist/{SchemaVisualizer-BgD5Zb77.js → SchemaVisualizer-BGpmzyXT.js} +5 -5
  12. package/dist/SchemaVisualizer-BGpmzyXT.js.map +1 -0
  13. package/dist/StorageView-BYoslzBR.js +870 -0
  14. package/dist/StorageView-BYoslzBR.js.map +1 -0
  15. package/dist/core/src/components/BootstrapAdminBanner.d.ts +4 -0
  16. package/dist/core/src/components/LoginView/LoginView.d.ts +22 -0
  17. package/dist/core/src/components/common/useDataTableController.d.ts +3 -3
  18. package/dist/core/src/components/index.d.ts +1 -0
  19. package/dist/core/src/hooks/data/useRelationSelector.d.ts +2 -2
  20. package/dist/core/src/hooks/index.d.ts +1 -0
  21. package/dist/core/src/hooks/useCollapsedGroups.d.ts +16 -1
  22. package/dist/core/src/hooks/useResolvedComponent.d.ts +47 -0
  23. package/dist/index.es.js +79 -71
  24. package/dist/index.es.js.map +1 -1
  25. package/dist/index.umd.js +821 -834
  26. package/dist/index.umd.js.map +1 -1
  27. package/dist/types/src/controllers/auth.d.ts +8 -2
  28. package/dist/types/src/controllers/client.d.ts +13 -0
  29. package/dist/types/src/controllers/collection_registry.d.ts +2 -1
  30. package/dist/types/src/controllers/data_driver.d.ts +36 -1
  31. package/dist/types/src/controllers/navigation.d.ts +18 -6
  32. package/dist/types/src/controllers/registry.d.ts +9 -1
  33. package/dist/types/src/controllers/side_entity_controller.d.ts +7 -0
  34. package/dist/types/src/rebase_context.d.ts +17 -0
  35. package/dist/types/src/types/backend_hooks.d.ts +187 -0
  36. package/dist/types/src/types/collections.d.ts +31 -11
  37. package/dist/types/src/types/component_ref.d.ts +47 -0
  38. package/dist/types/src/types/cron.d.ts +1 -1
  39. package/dist/types/src/types/entity_views.d.ts +6 -7
  40. package/dist/types/src/types/formex.d.ts +40 -0
  41. package/dist/types/src/types/index.d.ts +3 -0
  42. package/dist/types/src/types/plugins.d.ts +6 -3
  43. package/dist/types/src/types/properties.d.ts +72 -88
  44. package/dist/types/src/types/slots.d.ts +20 -10
  45. package/dist/types/src/types/translations.d.ts +6 -0
  46. package/dist/ui/src/components/FileUpload.d.ts +1 -1
  47. package/dist/ui/src/components/SearchBar.d.ts +5 -1
  48. package/dist/ui/src/styles.d.ts +2 -2
  49. package/package.json +10 -10
  50. package/src/components/ApiExplorer/ApiExplorer.tsx +7 -5
  51. package/src/components/ApiExplorer/TryItPanel.tsx +29 -53
  52. package/src/components/AuthSimulationSelector.tsx +13 -17
  53. package/src/components/RLSEditor/RLSEditor.tsx +82 -3
  54. package/src/components/SQLEditor/SQLEditor.tsx +16 -18
  55. package/src/components/SQLEditor/SchemaBrowser.tsx +6 -8
  56. package/src/components/SchemaVisualizer/SchemaVisualizer.tsx +20 -22
  57. package/src/components/StorageView/StorageView.tsx +719 -304
  58. package/src/components/StudioHomePage.tsx +4 -1
  59. package/dist/ApiExplorer-gMJt5JrS.js.map +0 -1
  60. package/dist/AuthSimulationSelector-BF4rkRGp.js +0 -118
  61. package/dist/AuthSimulationSelector-BF4rkRGp.js.map +0 -1
  62. package/dist/RLSEditor-DBH09u9v.js.map +0 -1
  63. package/dist/SQLEditor-CkVx9vgr.js.map +0 -1
  64. package/dist/SchemaVisualizer-BgD5Zb77.js.map +0 -1
  65. package/dist/StorageView-CTqGFhY9.js +0 -907
  66. package/dist/StorageView-CTqGFhY9.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import React, { useState, useCallback, useMemo, useEffect } from "react";
2
- import { Typography, Button, cls, defaultBorderMixin, iconSize } from "@rebasepro/ui";
2
+ import { Typography, Button, TextField, IconButton, cls, defaultBorderMixin, iconSize } from "@rebasepro/ui";
3
3
  import { LoaderIcon, SendIcon, PlusIcon, XIcon } from "lucide-react";
4
4
  import { useRebaseContext, UserSelectPopover, SelectableUser } from "@rebasepro/core";
5
5
  import { AuthSimulationSelector } from "../AuthSimulationSelector";
@@ -233,19 +233,15 @@ time: elapsed });
233
233
  >
234
234
  Request Body
235
235
  </Typography>
236
- <textarea
236
+ <TextField
237
+ multiline
238
+ minRows={10}
237
239
  value={body}
238
240
  onChange={(e) => { setBody(e.target.value); setValidationError(null); }}
239
- rows={10}
240
241
  spellCheck={false}
241
- className={cls(
242
- "w-full font-mono text-xs p-3 rounded-lg resize-y",
243
- "bg-surface-50 dark:bg-surface-900",
244
- validationError ? "border-red-500 focus:ring-red-500/30" : cls("border focus:ring-primary/30 dark:focus:ring-primary-dark/30", defaultBorderMixin),
245
- "text-text-primary dark:text-text-primary-dark",
246
- "focus:outline-none focus:ring-2",
247
- "transition-all"
248
- )}
242
+ error={!!validationError}
243
+ className="w-full"
244
+ inputClassName="font-mono text-xs p-3 resize-y"
249
245
  />
250
246
  {validationError && (
251
247
  <Typography variant="caption" className="text-red-500 mt-1 block text-xs">
@@ -352,21 +348,13 @@ function ParamSection({
352
348
  <code className="text-xs font-mono font-semibold">{p.name}</code>
353
349
  {p.required && <span className="text-red-500 ml-0.5 text-xs">*</span>}
354
350
  </label>
355
- <input
356
- type="text"
351
+ <TextField
352
+ size="small"
357
353
  placeholder={p.description ?? p.name}
358
354
  value={values[p.name] ?? ""}
359
355
  onChange={(e) => onChange(p.name, e.target.value)}
360
- className={cls(
361
- "flex-1 px-3 py-1.5 text-xs rounded-lg font-mono",
362
- "bg-surface-50 dark:bg-surface-900",
363
- "border",
364
- defaultBorderMixin,
365
- "text-text-primary dark:text-text-primary-dark",
366
- "placeholder:text-text-secondary/50",
367
- "focus:outline-none focus:ring-2 focus:ring-primary/30",
368
- "transition-all"
369
- )}
356
+ className="flex-1"
357
+ inputClassName="font-mono text-xs"
370
358
  />
371
359
  </div>
372
360
  ))}
@@ -397,55 +385,43 @@ function CustomKeyValueSection({
397
385
  >
398
386
  {title}
399
387
  </Typography>
400
- <button
388
+ <Button
389
+ variant="text"
390
+ size="small"
391
+ color="primary"
401
392
  onClick={onAdd}
402
- className="text-xs text-primary dark:text-primary-dark hover:underline flex items-center gap-1 font-medium"
393
+ className="text-xs p-0 min-h-0"
403
394
  >
404
395
  <PlusIcon size={iconSize.small} className="mr-1" /> Add Header
405
- </button>
396
+ </Button>
406
397
  </div>
407
398
  <div className="space-y-2">
408
399
  {values.map((v, i) => (
409
400
  <div key={i} className="flex items-center gap-2">
410
- <input
411
- type="text"
401
+ <TextField
402
+ size="small"
412
403
  placeholder="Header name"
413
404
  value={v.key}
414
405
  onChange={(e) => onChange(i, e.target.value, v.value)}
415
- className={cls(
416
- "w-1/3 px-3 py-1.5 text-xs rounded-lg font-mono",
417
- "bg-surface-50 dark:bg-surface-900",
418
- "border",
419
- defaultBorderMixin,
420
- "text-text-primary dark:text-text-primary-dark",
421
- "placeholder:text-text-secondary/50",
422
- "focus:outline-none focus:ring-2 focus:ring-primary/30",
423
- "transition-all"
424
- )}
406
+ className="w-1/3"
407
+ inputClassName="font-mono text-xs"
425
408
  />
426
- <input
427
- type="text"
409
+ <TextField
410
+ size="small"
428
411
  placeholder="Value"
429
412
  value={v.value}
430
413
  onChange={(e) => onChange(i, v.key, e.target.value)}
431
- className={cls(
432
- "flex-1 px-3 py-1.5 text-xs rounded-lg font-mono",
433
- "bg-surface-50 dark:bg-surface-900",
434
- "border",
435
- defaultBorderMixin,
436
- "text-text-primary dark:text-text-primary-dark",
437
- "placeholder:text-text-secondary/50",
438
- "focus:outline-none focus:ring-2 focus:ring-primary/30",
439
- "transition-all"
440
- )}
414
+ className="flex-1"
415
+ inputClassName="font-mono text-xs"
441
416
  />
442
- <button
417
+ <IconButton
418
+ size="small"
443
419
  onClick={() => onRemove(i)}
444
- className="p-1.5 text-text-secondary hover:text-red-500 rounded transition-colors flex items-center justify-center shrink-0"
420
+ className="text-text-secondary hover:text-red-500 shrink-0"
445
421
  title="Remove"
446
422
  >
447
423
  <XIcon size={iconSize.small} />
448
- </button>
424
+ </IconButton>
449
425
  </div>
450
426
  ))}
451
427
  {values.length === 0 && (
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { Typography, cls, iconSize } from "@rebasepro/ui";
2
+ import { Typography, cls, iconSize, Button } from "@rebasepro/ui";
3
3
  import { KeyRoundIcon } from "lucide-react";
4
4
  import { UserSelectPopover, SelectableUser } from "@rebasepro/core";
5
5
 
@@ -31,28 +31,24 @@ export function AuthSimulationSelector({
31
31
  >
32
32
  Auth:
33
33
  </Typography>
34
- <button
34
+ <Button
35
+ size="small"
36
+ variant={authMode === "jwt" ? "outlined" : "outlined"}
37
+ color={authMode === "jwt" ? "primary" : "neutral"}
35
38
  onClick={() => setAuthMode("jwt")}
36
- className={cls(
37
- "px-3 py-1 text-xs rounded-full border transition-all",
38
- authMode === "jwt"
39
- ? "bg-primary/15 text-primary dark:text-primary-dark border-primary/30 font-medium"
40
- : "border-surface-300 dark:border-surface-600 text-text-secondary dark:text-text-secondary-dark hover:border-primary/30"
41
- )}
39
+ className="rounded-full !px-3 !py-1 !text-xs"
42
40
  >
43
41
  JWT Token
44
- </button>
45
- <button
42
+ </Button>
43
+ <Button
44
+ size="small"
45
+ variant={authMode === "none" ? "outlined" : "outlined"}
46
+ color={authMode === "none" ? "error" : "neutral"}
46
47
  onClick={() => setAuthMode("none")}
47
- className={cls(
48
- "px-3 py-1 text-xs rounded-full border transition-all",
49
- authMode === "none"
50
- ? "bg-red-500/15 text-red-600 dark:text-red-400 border-red-500/30 font-medium"
51
- : "border-surface-300 dark:border-surface-600 text-text-secondary dark:text-text-secondary-dark hover:border-red-500/30"
52
- )}
48
+ className="rounded-full !px-3 !py-1 !text-xs"
53
49
  >
54
50
  No Auth
55
- </button>
51
+ </Button>
56
52
 
57
53
  {authMode === "jwt" && (
58
54
  <>
@@ -7,6 +7,18 @@ import { useRebaseContext, useSnackbarController, ErrorView, useTranslation } fr
7
7
  import { isPostgresCollection } from "@rebasepro/types";
8
8
  import { PolicyEditor } from "./PolicyEditor";
9
9
 
10
+ /**
11
+ * Validates and double-quotes a SQL identifier to prevent injection.
12
+ * Only allows safe Postgres identifiers (letters, digits, underscores).
13
+ * Throws if the identifier contains unsafe characters.
14
+ */
15
+ function sanitizeSqlIdentifier(name: string): string {
16
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
17
+ throw new Error(`Invalid SQL identifier: "${name}". Only letters, digits, and underscores are allowed.`);
18
+ }
19
+ return `"${name}"`;
20
+ }
21
+
10
22
  export interface PostgresPolicy {
11
23
  policyname: string;
12
24
  tablename: string;
@@ -402,6 +414,35 @@ totalPolicies };
402
414
  <Typography variant="body2" className="font-mono text-[13px] font-medium">{rlsStats.totalPolicies}</Typography>
403
415
  </div>
404
416
  </div>
417
+
418
+ {/* Security health indicators */}
419
+ {rlsStats.total - rlsStats.enabled > 0 && (
420
+ <div className={cls("p-2.5 rounded border border-yellow-200 dark:border-yellow-900/50 bg-yellow-50 dark:bg-yellow-900/20 flex items-start gap-2", defaultBorderMixin)}>
421
+ <AlertTriangleIcon size={14} className="text-yellow-600 dark:text-yellow-500 mt-0.5 shrink-0"/>
422
+ <div>
423
+ <Typography variant="caption" className="text-yellow-800 dark:text-yellow-400 text-[11px] font-semibold block">
424
+ {rlsStats.total - rlsStats.enabled} table{rlsStats.total - rlsStats.enabled > 1 ? "s" : ""} without RLS
425
+ </Typography>
426
+ <Typography variant="caption" className="text-yellow-700 dark:text-yellow-600 text-[10px] block mt-0.5">
427
+ These tables have no row-level access control. If auth enforcement is disabled, data may be publicly accessible.
428
+ </Typography>
429
+ </div>
430
+ </div>
431
+ )}
432
+
433
+ {rlsStats.enabled > 0 && rlsStats.enabled - rlsStats.withPolicies > 0 && (
434
+ <div className={cls("p-2.5 rounded border border-blue-200 dark:border-blue-900/50 bg-blue-50 dark:bg-blue-900/20 flex items-start gap-2", defaultBorderMixin)}>
435
+ <ShieldIcon size={14} className="text-blue-600 dark:text-blue-400 mt-0.5 shrink-0"/>
436
+ <div>
437
+ <Typography variant="caption" className="text-blue-800 dark:text-blue-300 text-[11px] font-semibold block">
438
+ {rlsStats.enabled - rlsStats.withPolicies} table{rlsStats.enabled - rlsStats.withPolicies > 1 ? "s" : ""} with RLS but no policies
439
+ </Typography>
440
+ <Typography variant="caption" className="text-blue-700 dark:text-blue-500 text-[10px] block mt-0.5">
441
+ RLS is enabled but no permissive policies exist. All access is denied by default (Postgres deny-all).
442
+ </Typography>
443
+ </div>
444
+ </div>
445
+ )}
405
446
  </div>
406
447
  </div>
407
448
  )}
@@ -437,7 +478,7 @@ totalPolicies };
437
478
  const action = activeTableData.rlsEnabled ? "DISABLE" : "ENABLE";
438
479
  if (!confirm(`Are you sure you want to ${action.toLowerCase()} Row Level Security on "${table}"?`)) return;
439
480
  try {
440
- await databaseAdmin!.executeSql!(`ALTER TABLE "${table}" ${action} ROW LEVEL SECURITY`);
481
+ await databaseAdmin!.executeSql!(`ALTER TABLE ${sanitizeSqlIdentifier(table)} ${action} ROW LEVEL SECURITY`);
441
482
  snackbarController.open({ type: "success",
442
483
  message: `RLS ${action.toLowerCase()}d on ${table}` });
443
484
  fetchRLSData();
@@ -476,7 +517,11 @@ message: e instanceof Error ? e.message : String(e) });
476
517
  </div>
477
518
  </div>
478
519
 
479
- {error ? (
520
+ {isLoading && !activeTableData ? (
521
+ <div className="flex-grow flex items-center justify-center h-full">
522
+ <CircularProgress size="small"/>
523
+ </div>
524
+ ) : error ? (
480
525
  <div className="p-6 h-full flex items-center justify-center">
481
526
  <ErrorView title={t("studio_rls_error")} error={error} onRetry={fetchRLSData}/>
482
527
  </div>
@@ -660,7 +705,7 @@ message: e instanceof Error ? e.message : String(e) });
660
705
  const table = activeTableData!.tableName;
661
706
  if (!confirm(`Drop policy "${policy.policyname}" from table "${table}"?`)) return;
662
707
  try {
663
- await databaseAdmin!.executeSql!(`DROP POLICY "${policy.policyname}" ON "${table}"`);
708
+ await databaseAdmin!.executeSql!(`DROP POLICY ${sanitizeSqlIdentifier(policy.policyname)} ON ${sanitizeSqlIdentifier(table)}`);
664
709
  snackbarController.open({ type: "success",
665
710
  message: `Policy "${policy.policyname}" dropped` });
666
711
  fetchRLSData();
@@ -680,6 +725,40 @@ message: e instanceof Error ? e.message : String(e) });
680
725
  </div>
681
726
  )}
682
727
 
728
+ {activeTableData && mergedPolicies.length === 0 && activeTableData.rlsEnabled && (
729
+ <div className="flex flex-col items-center justify-center py-12 text-center">
730
+ <ShieldIcon size={40} className="text-surface-300 dark:text-surface-600 mb-4"/>
731
+ <Typography variant="subtitle2" className="text-text-secondary dark:text-text-secondary-dark mb-2">
732
+ No policies defined
733
+ </Typography>
734
+ <Typography variant="caption" className="text-text-disabled dark:text-text-disabled-dark max-w-sm mb-4">
735
+ RLS is enabled on this table but no policies exist. All access is denied by default (Postgres deny-all). Create a policy to allow specific access.
736
+ </Typography>
737
+ {activeCollection && (
738
+ <Button
739
+ size="small"
740
+ variant="filled"
741
+ color="primary"
742
+ onClick={() => setEditingPolicy("new")}
743
+ >
744
+ {t("studio_rls_create_policy")}
745
+ </Button>
746
+ )}
747
+ </div>
748
+ )}
749
+
750
+ {activeTableData && mergedPolicies.length === 0 && !activeTableData.rlsEnabled && activeCollection && (
751
+ <div className="flex flex-col items-center justify-center py-12 text-center">
752
+ <AlertTriangleIcon size={40} className="text-yellow-400 dark:text-yellow-600 mb-4"/>
753
+ <Typography variant="subtitle2" className="text-text-secondary dark:text-text-secondary-dark mb-2">
754
+ No access control
755
+ </Typography>
756
+ <Typography variant="caption" className="text-text-disabled dark:text-text-disabled-dark max-w-sm">
757
+ This table has neither RLS nor policies. Enable RLS and create policies to restrict row-level access.
758
+ </Typography>
759
+ </div>
760
+ )}
761
+
683
762
  </div>
684
763
  </div>
685
764
  </div>
@@ -3,7 +3,7 @@ import { IconForView } from "@rebasepro/core";
3
3
  import { useStudioCollectionRegistry, useStudioSideEntityController } from "@rebasepro/core";
4
4
  import React, { useState, useEffect, useCallback, useRef, useMemo } from "react";
5
5
  import { createPortal } from "react-dom";
6
- import { Button, Paper, Typography, CircularProgress, cls, IconButton, InputLabel, Dialog, DialogContent, DialogActions, DialogTitle, TextField, Tooltip, Alert, Tabs, Tab, defaultBorderMixin, Select, SelectItem, Menu, MenuItem, ResizablePanels, Chip, VirtualTable, VirtualTableColumn , iconSize } from "@rebasepro/ui";
6
+ import { Button, Paper, Typography, CircularProgress, cls, IconButton, InputLabel, Dialog, DialogContent, DialogActions, DialogTitle, TextField, Tooltip, Alert, Tabs, Tab, defaultBorderMixin, Select, SelectItem, Menu, MenuItem, ResizablePanels, Chip, VirtualTable, VirtualTableColumn , iconSize, Checkbox, TextareaAutosize } from "@rebasepro/ui";
7
7
  import { DatabaseIcon, TerminalIcon, XIcon, PlusIcon, PencilIcon, MoreVerticalIcon, MenuIcon, PlayIcon } from "lucide-react";
8
8
  // VirtualTableInput is conditionally loaded from CMS when available
9
9
  let VirtualTableInput: React.ComponentType<any> | null = null;
@@ -122,21 +122,15 @@ const FixedEditorOverlay = ({
122
122
  maxWidth: Math.min(400, windowSize.width - left - 10)
123
123
  }}
124
124
  >
125
- <textarea
125
+ <TextareaAutosize
126
126
  className="w-full h-full bg-transparent outline-none border-none ring-0 font-mono text-[13px] text-text-primary dark:text-text-primary-dark px-4 py-1.5 resize-none overflow-y-auto"
127
127
  defaultValue={displayValue}
128
128
  autoFocus
129
- style={{ minHeight: '32px' }}
129
+ style={{ minHeight: '32px', maxHeight: resolvedMaxHeight }}
130
130
  onFocus={(e) => {
131
131
  const val = e.target.value;
132
132
  e.target.value = "";
133
133
  e.target.value = val;
134
- e.target.style.height = 'auto';
135
- e.target.style.height = `${Math.min(e.target.scrollHeight, resolvedMaxHeight)}px`;
136
- }}
137
- onChange={(e) => {
138
- e.target.style.height = 'auto';
139
- e.target.style.height = `${Math.min(e.target.scrollHeight, resolvedMaxHeight)}px`;
140
134
  }}
141
135
  onBlur={(e) => {
142
136
  onSave(e.target.value || null);
@@ -1232,7 +1226,7 @@ id: String(ra.entityId) })}
1232
1226
  </div>
1233
1227
  <div className="flex shrink-0 items-center justify-end pr-2 gap-1.5">
1234
1228
  <Tooltip title={t("studio_sql_format_sql")}>
1235
- <IconButton size="small" onClick={handlePrettify} className="text-text-secondary hover:text-text-primary transition-colors">
1229
+ <IconButton size="small" onClick={handlePrettify}>
1236
1230
  <MenuIcon size={iconSize.smallest}/>
1237
1231
  </IconButton>
1238
1232
  </Tooltip>
@@ -1248,15 +1242,19 @@ id: String(ra.entityId) })}
1248
1242
 
1249
1243
  <div className="h-4 w-px bg-surface-200 dark:bg-surface-950 mx-1"></div>
1250
1244
 
1251
- <div className="flex items-center space-x-2 px-2 cursor-pointer" onClick={() => setAutoLimit(!autoLimit)}>
1245
+ <div className="flex items-center space-x-2 px-2" onClick={(e) => {
1246
+ setAutoLimit(!autoLimit);
1247
+ e.stopPropagation();
1248
+ }}>
1252
1249
  <Typography variant="caption" className="text-[11px] text-text-secondary cursor-pointer select-none">{t("studio_sql_limit_1000")}</Typography>
1253
- <input
1254
- type="checkbox"
1255
- checked={autoLimit}
1256
- onChange={(e) => setAutoLimit(e.target.checked)}
1257
- onClick={(e) => e.stopPropagation()}
1258
- className="w-3.5 h-3.5 rounded border-surface-300 text-primary focus:ring-primary cursor-pointer"
1259
- />
1250
+ <div onClick={(e) => e.stopPropagation()}>
1251
+ <Checkbox
1252
+ checked={autoLimit}
1253
+ onCheckedChange={setAutoLimit}
1254
+ size="smallest"
1255
+ padding={false}
1256
+ />
1257
+ </div>
1260
1258
  </div>
1261
1259
 
1262
1260
  <div className="h-4 w-px bg-surface-200 dark:bg-surface-950 mx-1"></div>
@@ -67,28 +67,26 @@ export const SchemaBrowser = ({
67
67
  <svg className="w-3.5 h-3.5 mr-1 shrink-0 text-text-disabled dark:text-text-disabled-dark" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 10h18M3 14h18m-9-4v8m-7 0h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>
68
68
  <Typography variant="body2" className="text-text-secondary dark:text-text-secondary-dark text-xs truncate flex-1 min-w-0">{table.tableName}</Typography>
69
69
 
70
- <div className="flex opacity-0 group-hover:opacity-100 focus-within:opacity-100 absolute right-1 items-center bg-surface-100 dark:bg-surface-950 px-1 gap-1 rounded transition-opacity">
70
+ <div className="flex opacity-0 group-hover:opacity-100 focus-within:opacity-100 absolute right-1 items-center bg-surface-100 dark:bg-surface-800 px-1 gap-1 rounded transition-opacity">
71
71
  <IconButton
72
- size="smallest"
73
- className="transition-colors text-text-secondary hover:text-text-primary pointer-events-auto"
72
+ size="small"
74
73
  onClick={(e) => {
75
74
  e.stopPropagation();
76
75
  navigator.clipboard.writeText(table.tableName);
77
76
  }}
78
77
  title="CopyIcon table name"
79
78
  >
80
- <CopyIcon size={iconSize.smallest}/>
79
+ <CopyIcon size={iconSize.small}/>
81
80
  </IconButton>
82
81
 
83
82
  <Menu
84
83
  trigger={
85
84
  <IconButton
86
- size={"smallest"}
87
- className="transition-colors text-text-secondary hover:text-text-primary pointer-events-auto"
85
+ size={"small"}
88
86
  onClick={(e) => e.stopPropagation()}
89
87
  title="Generate SQL templates"
90
88
  >
91
- <MoreVerticalIcon size={iconSize.smallest}/>
89
+ <MoreVerticalIcon size={iconSize.small}/>
92
90
  </IconButton>
93
91
  }
94
92
  >
@@ -133,7 +131,7 @@ export const SchemaBrowser = ({
133
131
  <Typography variant="caption" className="text-text-disabled dark:text-text-disabled-dark text-[9px] truncate mr-1 uppercase shrink-0" title={col.dataType}>{col.dataType}</Typography>
134
132
  <IconButton
135
133
  size="smallest"
136
- className="opacity-0 group-hover:opacity-100 absolute right-1 bg-surface-50 dark:bg-surface-950 transition-colors pointer-events-auto"
134
+ className="opacity-0 group-hover:opacity-100 absolute right-1 bg-surface-50 dark:bg-surface-800 transition-colors pointer-events-auto"
137
135
  onClick={(e) => {
138
136
  e.stopPropagation();
139
137
  navigator.clipboard.writeText(col.name);
@@ -13,13 +13,16 @@ import {
13
13
  import type { Node, Edge, NodeChange, EdgeChange } from "@xyflow/react";
14
14
  import "@xyflow/react/dist/style.css";
15
15
  import {
16
- Typography,
16
+ Button,
17
+ SearchBar,
17
18
  TextField,
19
+ Tooltip,
20
+ Alert,
21
+ Typography,
18
22
  cls,
19
23
  defaultBorderMixin,
20
24
  CircularProgress,
21
25
  ResizablePanels,
22
- Tooltip,
23
26
  IconButton
24
27
  } from "@rebasepro/ui";
25
28
  import {
@@ -230,12 +233,11 @@ duration: 400 }
230
233
 
231
234
  {/* Search */}
232
235
  <div className="px-2 py-1.5 border-b border-surface-200/40 dark:border-surface-700/40">
233
- <TextField
236
+ <SearchBar
234
237
  size="smallest"
235
238
  placeholder="Filter tables…"
236
- value={searchQuery}
237
- onChange={(e) => setSearchQuery(e.target.value)}
238
- inputClassName="text-xs"
239
+ onTextSearch={(val) => setSearchQuery(val ?? "")}
240
+ innerClassName="text-xs"
239
241
  />
240
242
  </div>
241
243
 
@@ -443,30 +445,26 @@ duration: 400 }
443
445
  {/* Direction toggle */}
444
446
  <div className="flex items-center bg-surface-100 dark:bg-surface-950 rounded-md border border-surface-200/40 dark:border-surface-700/40">
445
447
  <Tooltip title="Left to right layout">
446
- <button
448
+ <Button
449
+ size="small"
450
+ variant={direction === "LR" ? "filled" : "text"}
451
+ color={direction === "LR" ? "primary" : "neutral"}
447
452
  onClick={() => setDirection("LR")}
448
- className={cls(
449
- "px-2 py-1 text-[10px] font-mono rounded-l-md transition-colors",
450
- direction === "LR"
451
- ? "bg-primary text-white"
452
- : "text-text-secondary dark:text-text-secondary-dark hover:bg-surface-200 dark:hover:bg-surface-700"
453
- )}
453
+ className="!rounded-r-none !px-2 !py-1 !text-[10px] !font-mono"
454
454
  >
455
455
  LR
456
- </button>
456
+ </Button>
457
457
  </Tooltip>
458
458
  <Tooltip title="Top to bottom layout">
459
- <button
459
+ <Button
460
+ size="small"
461
+ variant={direction === "TB" ? "filled" : "text"}
462
+ color={direction === "TB" ? "primary" : "neutral"}
460
463
  onClick={() => setDirection("TB")}
461
- className={cls(
462
- "px-2 py-1 text-[10px] font-mono rounded-r-md transition-colors",
463
- direction === "TB"
464
- ? "bg-primary text-white"
465
- : "text-text-secondary dark:text-text-secondary-dark hover:bg-surface-200 dark:hover:bg-surface-700"
466
- )}
464
+ className="!rounded-l-none !px-2 !py-1 !text-[10px] !font-mono"
467
465
  >
468
466
  TB
469
- </button>
467
+ </Button>
470
468
  </Tooltip>
471
469
  </div>
472
470