@rozenite/storage-plugin 2.1.0 → 2.3.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 (99) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/devtools/assets/panel-CcThj-0Z.js +32 -0
  3. package/dist/devtools/assets/panel-DTYbHqnH.css +1 -0
  4. package/dist/devtools/panel.html +2 -2
  5. package/dist/react-native/cjs/package.json +3 -0
  6. package/dist/react-native/cjs/react-native.js +38 -0
  7. package/dist/react-native/cjs/src/react-native/adapters/async-storage.js +60 -0
  8. package/dist/react-native/cjs/src/react-native/adapters/index.js +10 -0
  9. package/dist/react-native/cjs/src/react-native/adapters/mmkv.js +143 -0
  10. package/dist/react-native/cjs/src/react-native/adapters/secure-storage.js +51 -0
  11. package/dist/react-native/cjs/src/react-native/entry-preview-pagination.js +198 -0
  12. package/dist/react-native/cjs/src/react-native/export-snapshot.js +70 -0
  13. package/dist/react-native/cjs/src/react-native/full-entry-request.js +72 -0
  14. package/dist/react-native/cjs/src/react-native/import.js +185 -0
  15. package/dist/react-native/cjs/src/react-native/storage-discovery.js +69 -0
  16. package/dist/react-native/cjs/src/react-native/storage-view.js +128 -0
  17. package/dist/react-native/cjs/src/react-native/useRozeniteStoragePlugin.js +145 -0
  18. package/dist/react-native/cjs/src/react-native/useStorageAgentTools.js +228 -0
  19. package/dist/react-native/cjs/src/shared/agent-tools.js +119 -0
  20. package/dist/react-native/cjs/src/shared/entry-preview.js +48 -0
  21. package/dist/react-native/cjs/src/shared/messaging.js +2 -0
  22. package/dist/react-native/cjs/src/shared/snapshot.js +185 -0
  23. package/dist/react-native/cjs/src/shared/types.js +13 -0
  24. package/dist/react-native/package.json +3 -0
  25. package/dist/react-native/react-native.d.ts +7 -0
  26. package/dist/react-native/react-native.js +40 -0
  27. package/dist/react-native/src/react-native/adapters/async-storage.d.ts +34 -0
  28. package/dist/react-native/src/react-native/adapters/async-storage.js +56 -0
  29. package/dist/react-native/src/react-native/adapters/index.d.ts +6 -0
  30. package/dist/react-native/src/react-native/adapters/index.js +3 -0
  31. package/dist/react-native/src/react-native/adapters/mmkv.d.ts +13 -0
  32. package/dist/react-native/src/react-native/adapters/mmkv.js +139 -0
  33. package/dist/react-native/src/react-native/adapters/secure-storage.d.ts +18 -0
  34. package/dist/react-native/src/react-native/adapters/secure-storage.js +47 -0
  35. package/dist/react-native/src/react-native/entry-preview-pagination.d.ts +7 -0
  36. package/dist/react-native/src/react-native/entry-preview-pagination.js +194 -0
  37. package/dist/react-native/src/react-native/export-snapshot.d.ts +5 -0
  38. package/dist/react-native/src/react-native/export-snapshot.js +66 -0
  39. package/dist/react-native/src/react-native/full-entry-request.d.ts +5 -0
  40. package/dist/react-native/src/react-native/full-entry-request.js +68 -0
  41. package/dist/react-native/src/react-native/import.d.ts +7 -0
  42. package/dist/react-native/src/react-native/import.js +180 -0
  43. package/dist/react-native/src/react-native/storage-discovery.d.ts +5 -0
  44. package/dist/react-native/src/react-native/storage-discovery.js +65 -0
  45. package/dist/react-native/src/react-native/storage-view.d.ts +19 -0
  46. package/dist/react-native/src/react-native/storage-view.js +123 -0
  47. package/dist/react-native/src/react-native/useRozeniteStoragePlugin.d.ts +6 -0
  48. package/dist/react-native/src/react-native/useRozeniteStoragePlugin.js +141 -0
  49. package/dist/react-native/src/react-native/useStorageAgentTools.d.ts +37 -0
  50. package/dist/react-native/src/react-native/useStorageAgentTools.js +223 -0
  51. package/dist/react-native/src/shared/agent-tools.d.ts +76 -0
  52. package/dist/react-native/src/shared/agent-tools.js +116 -0
  53. package/dist/react-native/src/shared/entry-preview.d.ts +4 -0
  54. package/dist/react-native/src/shared/entry-preview.js +44 -0
  55. package/dist/react-native/src/shared/messaging.d.ts +134 -0
  56. package/dist/react-native/src/shared/messaging.js +1 -0
  57. package/dist/react-native/src/shared/snapshot.d.ts +53 -0
  58. package/dist/react-native/src/shared/snapshot.js +179 -0
  59. package/dist/react-native/src/shared/types.d.ts +78 -0
  60. package/dist/react-native/src/shared/types.js +8 -0
  61. package/dist/rozenite.json +1 -1
  62. package/dist/sdk/package.json +3 -0
  63. package/dist/sdk/sdk.d.ts +44 -0
  64. package/dist/sdk/sdk.js +9 -0
  65. package/dist/sdk/src/shared/agent-tools.d.ts +76 -0
  66. package/dist/sdk/src/shared/agent-tools.js +119 -0
  67. package/dist/sdk/src/shared/types.d.ts +78 -0
  68. package/dist/sdk/src/shared/types.js +13 -0
  69. package/package.json +16 -16
  70. package/react-native.ts +8 -1
  71. package/rozenite.config.ts +1 -0
  72. package/src/__tests__/release-bundle.test.ts +32 -0
  73. package/src/ui/__tests__/panel.test.tsx +14 -0
  74. package/src/ui/add-entry-dialog.tsx +63 -73
  75. package/src/ui/binary-value-editor.tsx +1 -1
  76. package/src/ui/edit-entry-dialog.tsx +59 -70
  77. package/src/ui/entry-detail-dialog.tsx +4 -2
  78. package/src/ui/format-value.tsx +1 -1
  79. package/src/ui/import-dialog.tsx +5 -5
  80. package/src/ui/panel.tsx +70 -88
  81. package/tsconfig.json +4 -7
  82. package/dist/devtools/assets/panel-BlDRXgVW.js +0 -32
  83. package/dist/devtools/assets/panel-DBweZnW5.css +0 -1
  84. package/dist/react-native/chunks/async-storage.require.cjs +0 -1
  85. package/dist/react-native/chunks/async-storage.require.js +0 -53
  86. package/dist/react-native/chunks/index.require.cjs +0 -1
  87. package/dist/react-native/chunks/index.require.js +0 -103
  88. package/dist/react-native/chunks/secure-storage.require.cjs +0 -1
  89. package/dist/react-native/chunks/secure-storage.require.js +0 -46
  90. package/dist/react-native/chunks/types.cjs +0 -1
  91. package/dist/react-native/chunks/types.js +0 -11
  92. package/dist/react-native/chunks/useRozeniteStoragePlugin.require.cjs +0 -1
  93. package/dist/react-native/chunks/useRozeniteStoragePlugin.require.js +0 -1037
  94. package/dist/react-native/index.cjs +0 -1
  95. package/dist/react-native/index.d.ts +0 -365
  96. package/dist/react-native/index.js +0 -28
  97. package/dist/sdk/index.cjs +0 -1
  98. package/dist/sdk/index.d.ts +0 -206
  99. package/dist/sdk/index.js +0 -122
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rozenite/storage-plugin",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "description": "Generic Storage Inspector for Rozenite.",
5
5
  "homepage": "https://github.com/callstackincubator/rozenite#readme",
6
6
  "bugs": {
@@ -12,20 +12,19 @@
12
12
  "url": "https://github.com/callstackincubator/rozenite.git"
13
13
  },
14
14
  "type": "module",
15
- "main": "./dist/react-native/index.cjs",
16
- "module": "./dist/react-native/index.js",
17
- "types": "./dist/react-native/index.d.ts",
15
+ "main": "./dist/react-native/cjs/react-native.js",
16
+ "module": "./dist/react-native/react-native.js",
17
+ "types": "./dist/react-native/react-native.d.ts",
18
18
  "exports": {
19
19
  ".": {
20
- "types": "./dist/react-native/index.d.ts",
21
- "import": "./dist/react-native/index.js",
22
- "require": "./dist/react-native/index.cjs"
20
+ "types": "./dist/react-native/react-native.d.ts",
21
+ "import": "./dist/react-native/react-native.js",
22
+ "require": "./dist/react-native/cjs/react-native.js"
23
23
  },
24
24
  "./sdk": {
25
25
  "development": "./sdk.ts",
26
- "types": "./dist/sdk/index.d.ts",
27
- "import": "./dist/sdk/index.js",
28
- "require": "./dist/sdk/index.cjs"
26
+ "types": "./dist/sdk/sdk.d.ts",
27
+ "default": "./dist/sdk/sdk.js"
29
28
  },
30
29
  "./package.json": "./package.json"
31
30
  },
@@ -33,10 +32,10 @@
33
32
  "access": "public"
34
33
  },
35
34
  "dependencies": {
36
- "@rozenite/agent-bridge": "2.1.0",
37
- "@rozenite/agent-shared": "2.1.0",
38
- "@rozenite/plugin-bridge": "2.1.0",
39
- "@rozenite/ui": "2.1.0"
35
+ "@rozenite/agent-shared": "2.3.0",
36
+ "@rozenite/plugin-bridge": "2.3.0",
37
+ "@rozenite/ui": "2.3.0",
38
+ "@rozenite/agent-bridge": "2.3.0"
40
39
  },
41
40
  "devDependencies": {
42
41
  "@codemirror/commands": "^6.10.3",
@@ -57,8 +56,9 @@
57
56
  "react-virtuoso": "^4.14.1",
58
57
  "typescript": "~5.9.3",
59
58
  "vite": "^7.3.1",
60
- "@rozenite/vite-plugin": "2.1.0",
61
- "rozenite": "2.1.0"
59
+ "@rozenite/test-utils": "2.3.0",
60
+ "@rozenite/vite-plugin": "2.3.0",
61
+ "rozenite": "2.3.0"
62
62
  },
63
63
  "peerDependencies": {
64
64
  "@react-native-async-storage/async-storage": "*",
package/react-native.ts CHANGED
@@ -26,8 +26,15 @@ export let createMMKVStorageAdapter: typeof import('./src/react-native/adapters'
26
26
  export let useRozeniteStoragePlugin: typeof import('./src/react-native/useRozeniteStoragePlugin').useRozeniteStoragePlugin;
27
27
 
28
28
  const isWeb = typeof window !== 'undefined' && window.navigator.product !== 'ReactNative';
29
+ // Neither Lynx runtime has a `window`, so `typeof window` alone reported
30
+ // every Lynx app as a server and installed the no-op stub below. `lynx` is
31
+ // a free binding in module scope, not a property of `globalThis`. Kept
32
+ // inline rather than imported so this stays a foldable expression and the
33
+ // `require`s below can still be dropped from production bundles.
34
+ declare const lynx: unknown;
35
+
29
36
  const isDev = process.env.NODE_ENV !== 'production';
30
- const isServer = typeof window === 'undefined';
37
+ const isServer = typeof window === 'undefined' && typeof lynx === 'undefined';
31
38
 
32
39
  const createNoopStorageAdapter = (
33
40
  options: { adapterId?: string; adapterName?: string } | undefined,
@@ -51,6 +51,7 @@ const storageFixtures = [
51
51
  ];
52
52
 
53
53
  export default {
54
+ integrations: ['react-native'],
54
55
  panels: [
55
56
  {
56
57
  name: 'Storage',
@@ -0,0 +1,32 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { bundleForRelease, RELEASE_BUNDLE_TIMEOUT } from '@rozenite/test-utils';
4
+ import { describe, expect, it } from 'vitest';
5
+
6
+ // An app importing this plugin ships its React Native code -- that much is
7
+ // the app's own choice. What must never follow it into the bundle is the
8
+ // panel: the DevTools UI, its React DOM tree and `@rozenite/ui`.
9
+ // See docs/agents/release-bundle-testing.md.
10
+ const packageRoot = path.resolve(fileURLToPath(import.meta.url), '../../..');
11
+ const reactNativeEntry = path.join(packageRoot, 'dist/react-native/cjs/react-native.js');
12
+
13
+ describe('@rozenite/storage-plugin in a release bundle', () => {
14
+ it(
15
+ 'ships no panel code when an app imports it',
16
+ async () => {
17
+ const result = await bundleForRelease({
18
+ resolveFrom: packageRoot,
19
+ files: {
20
+ 'index.js': `require(${JSON.stringify(reactNativeEntry)});\n`,
21
+ },
22
+ });
23
+
24
+ // Keeps the check below honest: the entry really did get bundled.
25
+ expect(result.rozeniteModules).toContain(
26
+ 'packages/storage-plugin/dist/react-native/cjs/react-native.js',
27
+ );
28
+ expect(result.panelModules).toEqual([]);
29
+ },
30
+ RELEASE_BUNDLE_TIMEOUT,
31
+ );
32
+ });
@@ -93,9 +93,23 @@ vi.mock('@rozenite/ui', async () => {
93
93
  Button: ({ children, onClick }: { children?: ReactNode; onClick?: () => void }) => (
94
94
  <button onClick={onClick}>{children}</button>
95
95
  ),
96
+ IconButton: ({
97
+ children,
98
+ onClick,
99
+ label,
100
+ }: {
101
+ children?: ReactNode;
102
+ onClick?: () => void;
103
+ label: string;
104
+ }) => (
105
+ <button onClick={onClick} aria-label={label}>
106
+ {children}
107
+ </button>
108
+ ),
96
109
  ConfirmDialog: () => null,
97
110
  EmptyState: () => <div />,
98
111
  PluginShell,
112
+ useConfirmDialog: () => async () => true,
99
113
  SearchField: ({
100
114
  value,
101
115
  onChange,
@@ -1,4 +1,4 @@
1
- import { Button, ConfirmDialog, Dialog, Field, Input } from '@rozenite/ui';
1
+ import { Button, Dialog, Field, Input, useConfirmDialog } from '@rozenite/ui';
2
2
  import { useEffect, useState } from 'react';
3
3
  import type { StorageEntry, StorageEntryType, StorageEntryValue } from '../shared/types';
4
4
  import { TypedValueEditor } from './typed-value-editor';
@@ -45,7 +45,7 @@ export const AddEntryDialog = ({
45
45
  const [currentValue, setCurrentValue] = useState<StorageEntryValue | null>(
46
46
  defaultValueForType(initialType),
47
47
  );
48
- const [alert, setAlert] = useState<{ title: string; message: string } | null>(null);
48
+ const confirm = useConfirmDialog();
49
49
 
50
50
  // Reset state every time the dialog opens, so a previous session's
51
51
  // type / value doesn't bleed in.
@@ -66,21 +66,23 @@ export const AddEntryDialog = ({
66
66
  onClose();
67
67
  };
68
68
 
69
- const handleAdd = () => {
69
+ const handleAdd = async () => {
70
70
  if (!newEntryKey.trim() || currentValue === null) return;
71
71
 
72
72
  if (!isCurrentTypeSupported) {
73
- setAlert({
73
+ await confirm({
74
+ variant: 'alert',
74
75
  title: 'Unsupported Type',
75
- message: 'Selected type is not supported by this storage.',
76
+ description: 'Selected type is not supported by this storage.',
76
77
  });
77
78
  return;
78
79
  }
79
80
 
80
81
  if (existingKeys.includes(newEntryKey)) {
81
- setAlert({
82
+ await confirm({
83
+ variant: 'alert',
82
84
  title: 'Key Already Exists',
83
- message: 'An entry with this key already exists.',
85
+ description: 'An entry with this key already exists.',
84
86
  });
85
87
  return;
86
88
  }
@@ -91,7 +93,7 @@ export const AddEntryDialog = ({
91
93
 
92
94
  const handleKeyDown = (event: React.KeyboardEvent) => {
93
95
  if (event.key === 'Enter' && newEntryKey.trim() && currentType !== 'buffer') {
94
- handleAdd();
96
+ void handleAdd();
95
97
  }
96
98
  };
97
99
 
@@ -100,70 +102,58 @@ export const AddEntryDialog = ({
100
102
  const isAddDisabled = !newEntryKey.trim() || !isCurrentTypeSupported || currentValue === null;
101
103
 
102
104
  return (
103
- <>
104
- <Dialog
105
- open={isOpen}
106
- onOpenChange={(open) => {
107
- if (!open) resetAndClose();
108
- }}
109
- >
110
- <Dialog.Content onKeyDown={handleKeyDown}>
111
- <Dialog.Header>
112
- <Dialog.Title>Add New Entry</Dialog.Title>
113
- </Dialog.Header>
114
-
115
- <div className="flex flex-col gap-4">
116
- <Field>
117
- <Field.Label htmlFor="new-entry-key">Key</Field.Label>
118
- <Input
119
- id="new-entry-key"
120
- value={newEntryKey}
121
- onChange={(event) => setNewEntryKey(event.target.value)}
122
- placeholder="Enter key name"
123
- autoFocus
124
- />
125
- </Field>
126
-
127
- <Field>
128
- <Field.Label htmlFor="new-entry-value">Value</Field.Label>
129
- <TypedValueEditor
130
- supportedTypes={supportedTypes}
131
- type={currentType}
132
- value={currentValue}
133
- onChange={(nextType, nextValue) => {
134
- setCurrentType(nextType);
135
- setCurrentValue(nextValue);
136
- }}
137
- inputId="new-entry-value"
138
- />
139
- {!isCurrentTypeSupported && (
140
- <Field.Description className="text-destructive">
141
- Selected type is not supported by this storage.
142
- </Field.Description>
143
- )}
144
- </Field>
145
- </div>
146
-
147
- <Dialog.Footer>
148
- <Button variant="outline" onClick={resetAndClose}>
149
- Cancel
150
- </Button>
151
- <Button onClick={handleAdd} disabled={isAddDisabled}>
152
- Add Entry
153
- </Button>
154
- </Dialog.Footer>
155
- </Dialog.Content>
156
- </Dialog>
157
-
158
- <ConfirmDialog
159
- open={alert !== null}
160
- onOpenChange={(open) => {
161
- if (!open) setAlert(null);
162
- }}
163
- variant="alert"
164
- title={alert?.title ?? ''}
165
- description={alert?.message}
166
- />
167
- </>
105
+ <Dialog
106
+ open={isOpen}
107
+ onOpenChange={(open) => {
108
+ if (!open) resetAndClose();
109
+ }}
110
+ >
111
+ <Dialog.Content onKeyDown={handleKeyDown}>
112
+ <Dialog.Header>
113
+ <Dialog.Title>Add New Entry</Dialog.Title>
114
+ </Dialog.Header>
115
+
116
+ <div className="flex flex-col gap-4">
117
+ <Field>
118
+ <Field.Label htmlFor="new-entry-key">Key</Field.Label>
119
+ <Input
120
+ id="new-entry-key"
121
+ value={newEntryKey}
122
+ onChange={(event) => setNewEntryKey(event.target.value)}
123
+ placeholder="Enter key name"
124
+ autoFocus
125
+ />
126
+ </Field>
127
+
128
+ <Field>
129
+ <Field.Label htmlFor="new-entry-value">Value</Field.Label>
130
+ <TypedValueEditor
131
+ supportedTypes={supportedTypes}
132
+ type={currentType}
133
+ value={currentValue}
134
+ onChange={(nextType, nextValue) => {
135
+ setCurrentType(nextType);
136
+ setCurrentValue(nextValue);
137
+ }}
138
+ inputId="new-entry-value"
139
+ />
140
+ {!isCurrentTypeSupported && (
141
+ <Field.Description className="text-danger">
142
+ Selected type is not supported by this storage.
143
+ </Field.Description>
144
+ )}
145
+ </Field>
146
+ </div>
147
+
148
+ <Dialog.Footer>
149
+ <Button tone="neutral" variant="outline" onClick={resetAndClose}>
150
+ Cancel
151
+ </Button>
152
+ <Button onClick={() => void handleAdd()} disabled={isAddDisabled}>
153
+ Add Entry
154
+ </Button>
155
+ </Dialog.Footer>
156
+ </Dialog.Content>
157
+ </Dialog>
168
158
  );
169
159
  };
@@ -173,7 +173,7 @@ export const BinaryValueEditor = ({ initialBytes, onChange }: BinaryValueEditorP
173
173
  ASCII: <span className="font-mono text-foreground">{asciiPreview}</span>
174
174
  </div>
175
175
  )}
176
- {state.error && <div className="text-destructive">{state.error}</div>}
176
+ {state.error && <div className="text-danger">{state.error}</div>}
177
177
  </div>
178
178
  </div>
179
179
  );
@@ -1,4 +1,4 @@
1
- import { Button, ConfirmDialog, Dialog, Field } from '@rozenite/ui';
1
+ import { Button, Dialog, Field, useConfirmDialog } from '@rozenite/ui';
2
2
  import { useEffect, useRef, useState } from 'react';
3
3
  import type { StorageEntry, StorageEntryType, StorageEntryValue } from '../shared/types';
4
4
  import { TypedValueEditor } from './typed-value-editor';
@@ -28,7 +28,7 @@ export const EditEntryDialog = ({
28
28
  const [currentValue, setCurrentValue] = useState<StorageEntryValue | null>(
29
29
  defaultValueForType('string'),
30
30
  );
31
- const [alert, setAlert] = useState<{ title: string; message: string } | null>(null);
31
+ const confirm = useConfirmDialog();
32
32
 
33
33
  useEffect(() => {
34
34
  if (entry && isOpen) {
@@ -48,13 +48,14 @@ export const EditEntryDialog = ({
48
48
  onClose();
49
49
  };
50
50
 
51
- const handleSave = () => {
51
+ const handleSave = async () => {
52
52
  if (!entry || currentValue === null) return;
53
53
 
54
54
  if (!isCurrentTypeSupported) {
55
- setAlert({
55
+ await confirm({
56
+ variant: 'alert',
56
57
  title: 'Unsupported Type',
57
- message: 'This storage does not support the selected type.',
58
+ description: 'This storage does not support the selected type.',
58
59
  });
59
60
  return;
60
61
  }
@@ -65,7 +66,7 @@ export const EditEntryDialog = ({
65
66
 
66
67
  const handleKeyDown = (event: React.KeyboardEvent) => {
67
68
  if (event.key === 'Enter' && currentType !== 'buffer') {
68
- handleSave();
69
+ void handleSave();
69
70
  }
70
71
  };
71
72
 
@@ -79,69 +80,57 @@ export const EditEntryDialog = ({
79
80
  const isSaveDisabled = !isCurrentTypeSupported || currentValue === null;
80
81
 
81
82
  return (
82
- <>
83
- <Dialog
84
- open={isOpen}
85
- onOpenChange={(open) => {
86
- if (!open) resetAndClose();
87
- }}
88
- >
89
- <Dialog.Content onKeyDown={handleKeyDown}>
90
- <Dialog.Header>
91
- <Dialog.Title>Edit Entry</Dialog.Title>
92
- </Dialog.Header>
93
-
94
- <div className="flex flex-col gap-4">
95
- <Field>
96
- <Field.Label>Key</Field.Label>
97
- <div className="h-8 w-full truncate rounded-md border border-input bg-muted px-3 py-1.5 font-mono text-sm text-foreground">
98
- {entryForDisplay.key}
99
- </div>
100
- <Field.Description>Key cannot be changed during editing</Field.Description>
101
- </Field>
102
-
103
- <Field>
104
- <Field.Label htmlFor="edit-entry-value">Value</Field.Label>
105
- <TypedValueEditor
106
- key={entryForDisplay.key}
107
- supportedTypes={supportedTypes}
108
- type={currentType}
109
- value={currentValue}
110
- onChange={(nextType, nextValue) => {
111
- setCurrentType(nextType);
112
- setCurrentValue(nextValue);
113
- }}
114
- inputId="edit-entry-value"
115
- autoFocus
116
- />
117
- {!isCurrentTypeSupported && (
118
- <Field.Description className="text-destructive">
119
- This storage does not support {currentType} values.
120
- </Field.Description>
121
- )}
122
- </Field>
123
- </div>
124
-
125
- <Dialog.Footer>
126
- <Button variant="outline" onClick={resetAndClose}>
127
- Cancel
128
- </Button>
129
- <Button onClick={handleSave} disabled={isSaveDisabled}>
130
- Save Changes
131
- </Button>
132
- </Dialog.Footer>
133
- </Dialog.Content>
134
- </Dialog>
135
-
136
- <ConfirmDialog
137
- open={alert !== null}
138
- onOpenChange={(open) => {
139
- if (!open) setAlert(null);
140
- }}
141
- variant="alert"
142
- title={alert?.title ?? ''}
143
- description={alert?.message}
144
- />
145
- </>
83
+ <Dialog
84
+ open={isOpen}
85
+ onOpenChange={(open) => {
86
+ if (!open) resetAndClose();
87
+ }}
88
+ >
89
+ <Dialog.Content onKeyDown={handleKeyDown}>
90
+ <Dialog.Header>
91
+ <Dialog.Title>Edit Entry</Dialog.Title>
92
+ </Dialog.Header>
93
+
94
+ <div className="flex flex-col gap-4">
95
+ <Field>
96
+ <Field.Label>Key</Field.Label>
97
+ <div className="h-8 w-full truncate rounded-md border border-input bg-muted px-3 py-1.5 font-mono text-sm text-foreground">
98
+ {entryForDisplay.key}
99
+ </div>
100
+ <Field.Description>Key cannot be changed during editing</Field.Description>
101
+ </Field>
102
+
103
+ <Field>
104
+ <Field.Label htmlFor="edit-entry-value">Value</Field.Label>
105
+ <TypedValueEditor
106
+ key={entryForDisplay.key}
107
+ supportedTypes={supportedTypes}
108
+ type={currentType}
109
+ value={currentValue}
110
+ onChange={(nextType, nextValue) => {
111
+ setCurrentType(nextType);
112
+ setCurrentValue(nextValue);
113
+ }}
114
+ inputId="edit-entry-value"
115
+ autoFocus
116
+ />
117
+ {!isCurrentTypeSupported && (
118
+ <Field.Description className="text-danger">
119
+ This storage does not support {currentType} values.
120
+ </Field.Description>
121
+ )}
122
+ </Field>
123
+ </div>
124
+
125
+ <Dialog.Footer>
126
+ <Button tone="neutral" variant="outline" onClick={resetAndClose}>
127
+ Cancel
128
+ </Button>
129
+ <Button onClick={() => void handleSave()} disabled={isSaveDisabled}>
130
+ Save Changes
131
+ </Button>
132
+ </Dialog.Footer>
133
+ </Dialog.Content>
134
+ </Dialog>
146
135
  );
147
136
  };
@@ -69,7 +69,9 @@ export const EntryDetailDialog = ({
69
69
 
70
70
  <div>
71
71
  <div className="mb-1 text-sm font-medium text-foreground">Type</div>
72
- <Badge variant="outline">{entry.type}</Badge>
72
+ <Badge tone="neutral" variant="outline">
73
+ {entry.type}
74
+ </Badge>
73
75
  </div>
74
76
 
75
77
  <div className="flex min-h-0 flex-1 flex-col">
@@ -96,7 +98,7 @@ export const EntryDetailDialog = ({
96
98
 
97
99
  <Dialog.Footer>
98
100
  {entry && onEdit && (
99
- <Button variant="outline" onClick={() => onEdit(entry)}>
101
+ <Button tone="neutral" variant="outline" onClick={() => onEdit(entry)}>
100
102
  <Edit3 className="h-3.5 w-3.5" />
101
103
  Edit
102
104
  </Button>
@@ -13,7 +13,7 @@ export const formatValue = (entry: StorageEntry) => {
13
13
 
14
14
  if (entry.type === 'boolean') {
15
15
  return (
16
- <span className={cn('font-mono', entry.value ? 'text-primary' : 'text-destructive')}>
16
+ <span className={cn('font-mono', entry.value ? 'text-primary' : 'text-danger')}>
17
17
  {entry.value ? 'true' : 'false'}
18
18
  </span>
19
19
  );
@@ -89,7 +89,7 @@ const PreviewBody = ({
89
89
  )}
90
90
 
91
91
  {hasUnsupported && (
92
- <div className="flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/10 p-2 text-xs text-destructive">
92
+ <div className="flex items-start gap-2 rounded-md border border-danger/40 bg-danger/10 p-2 text-xs text-danger">
93
93
  <XCircle className="mt-0.5 h-4 w-4 shrink-0" />
94
94
  <div>
95
95
  {preview.unsupportedTypes.length}{' '}
@@ -117,7 +117,7 @@ const PreviewBody = ({
117
117
  </div>
118
118
 
119
119
  <Dialog.Footer>
120
- <Button variant="outline" onClick={onCancel}>
120
+ <Button tone="neutral" variant="outline" onClick={onCancel}>
121
121
  Cancel
122
122
  </Button>
123
123
  <Button onClick={onApply} disabled={hasUnsupported}>
@@ -158,7 +158,7 @@ const ResultBody = ({
158
158
  </div>
159
159
  ) : (
160
160
  <div className="flex flex-col gap-2 py-2">
161
- <div className="flex items-center gap-2 text-destructive">
161
+ <div className="flex items-center gap-2 text-danger">
162
162
  <XCircle className="h-5 w-5" />
163
163
  <div className="text-sm font-medium">
164
164
  Import failed after {state.written} of {state.total}.
@@ -167,12 +167,12 @@ const ResultBody = ({
167
167
  {state.failedKey && (
168
168
  <div className="text-xs text-muted-foreground">
169
169
  Failed at key:{' '}
170
- <Badge variant="outline" className="font-mono">
170
+ <Badge tone="neutral" variant="outline" className="font-mono">
171
171
  {state.failedKey}
172
172
  </Badge>
173
173
  </div>
174
174
  )}
175
- <div className="rounded-md bg-muted px-2 py-1 font-mono text-xs text-destructive">
175
+ <div className="rounded-md bg-muted px-2 py-1 font-mono text-xs text-danger">
176
176
  {state.error}
177
177
  </div>
178
178
  </div>