@bendyline/docblocks-react 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 (104) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +70 -0
  3. package/dist/AppMenu/AppMenu.d.ts +11 -0
  4. package/dist/AppMenu/AppMenu.d.ts.map +1 -0
  5. package/dist/AppMenu/AppMenu.js +27 -0
  6. package/dist/AppMenu/AppMenu.js.map +1 -0
  7. package/dist/AppMenu/index.d.ts +3 -0
  8. package/dist/AppMenu/index.d.ts.map +1 -0
  9. package/dist/AppMenu/index.js +2 -0
  10. package/dist/AppMenu/index.js.map +1 -0
  11. package/dist/DocBlocksShell/DocBlocksShell.d.ts +16 -0
  12. package/dist/DocBlocksShell/DocBlocksShell.d.ts.map +1 -0
  13. package/dist/DocBlocksShell/DocBlocksShell.js +487 -0
  14. package/dist/DocBlocksShell/DocBlocksShell.js.map +1 -0
  15. package/dist/DocBlocksShell/index.d.ts +3 -0
  16. package/dist/DocBlocksShell/index.d.ts.map +1 -0
  17. package/dist/DocBlocksShell/index.js +2 -0
  18. package/dist/DocBlocksShell/index.js.map +1 -0
  19. package/dist/Export/ExportDialog.d.ts +16 -0
  20. package/dist/Export/ExportDialog.d.ts.map +1 -0
  21. package/dist/Export/ExportDialog.js +42 -0
  22. package/dist/Export/ExportDialog.js.map +1 -0
  23. package/dist/Export/ExportToolbarControls.d.ts +17 -0
  24. package/dist/Export/ExportToolbarControls.d.ts.map +1 -0
  25. package/dist/Export/ExportToolbarControls.js +109 -0
  26. package/dist/Export/ExportToolbarControls.js.map +1 -0
  27. package/dist/Export/export-options.d.ts +18 -0
  28. package/dist/Export/export-options.d.ts.map +1 -0
  29. package/dist/Export/export-options.js +44 -0
  30. package/dist/Export/export-options.js.map +1 -0
  31. package/dist/Export/index.d.ts +6 -0
  32. package/dist/Export/index.d.ts.map +1 -0
  33. package/dist/Export/index.js +3 -0
  34. package/dist/Export/index.js.map +1 -0
  35. package/dist/Export/run-export.d.ts +8 -0
  36. package/dist/Export/run-export.d.ts.map +1 -0
  37. package/dist/Export/run-export.js +208 -0
  38. package/dist/Export/run-export.js.map +1 -0
  39. package/dist/FileExplorer/FileExplorer.d.ts +21 -0
  40. package/dist/FileExplorer/FileExplorer.d.ts.map +1 -0
  41. package/dist/FileExplorer/FileExplorer.js +125 -0
  42. package/dist/FileExplorer/FileExplorer.js.map +1 -0
  43. package/dist/FileExplorer/FileTreeNode.d.ts +18 -0
  44. package/dist/FileExplorer/FileTreeNode.d.ts.map +1 -0
  45. package/dist/FileExplorer/FileTreeNode.js +86 -0
  46. package/dist/FileExplorer/FileTreeNode.js.map +1 -0
  47. package/dist/FileExplorer/index.d.ts +6 -0
  48. package/dist/FileExplorer/index.d.ts.map +1 -0
  49. package/dist/FileExplorer/index.js +4 -0
  50. package/dist/FileExplorer/index.js.map +1 -0
  51. package/dist/FileExplorer/useFileTree.d.ts +35 -0
  52. package/dist/FileExplorer/useFileTree.d.ts.map +1 -0
  53. package/dist/FileExplorer/useFileTree.js +124 -0
  54. package/dist/FileExplorer/useFileTree.js.map +1 -0
  55. package/dist/WorkspacePicker/WorkspacePicker.d.ts +17 -0
  56. package/dist/WorkspacePicker/WorkspacePicker.d.ts.map +1 -0
  57. package/dist/WorkspacePicker/WorkspacePicker.js +57 -0
  58. package/dist/WorkspacePicker/WorkspacePicker.js.map +1 -0
  59. package/dist/WorkspacePicker/WorkspaceSettingsButton.d.ts +10 -0
  60. package/dist/WorkspacePicker/WorkspaceSettingsButton.d.ts.map +1 -0
  61. package/dist/WorkspacePicker/WorkspaceSettingsButton.js +27 -0
  62. package/dist/WorkspacePicker/WorkspaceSettingsButton.js.map +1 -0
  63. package/dist/WorkspacePicker/index.d.ts +3 -0
  64. package/dist/WorkspacePicker/index.d.ts.map +1 -0
  65. package/dist/WorkspacePicker/index.js +2 -0
  66. package/dist/WorkspacePicker/index.js.map +1 -0
  67. package/dist/hooks/index.d.ts +2 -0
  68. package/dist/hooks/index.d.ts.map +1 -0
  69. package/dist/hooks/index.js +2 -0
  70. package/dist/hooks/index.js.map +1 -0
  71. package/dist/hooks/useAutoSave.d.ts +9 -0
  72. package/dist/hooks/useAutoSave.d.ts.map +1 -0
  73. package/dist/hooks/useAutoSave.js +48 -0
  74. package/dist/hooks/useAutoSave.js.map +1 -0
  75. package/dist/icons.d.ts +6 -0
  76. package/dist/icons.d.ts.map +1 -0
  77. package/dist/icons.js +24 -0
  78. package/dist/icons.js.map +1 -0
  79. package/dist/index.d.ts +20 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +19 -0
  82. package/dist/index.js.map +1 -0
  83. package/package.json +65 -0
  84. package/src/AppMenu/AppMenu.tsx +124 -0
  85. package/src/AppMenu/index.ts +2 -0
  86. package/src/DocBlocksShell/DocBlocksShell.tsx +662 -0
  87. package/src/DocBlocksShell/index.ts +2 -0
  88. package/src/Export/ExportDialog.tsx +173 -0
  89. package/src/Export/ExportToolbarControls.tsx +183 -0
  90. package/src/Export/export-options.ts +57 -0
  91. package/src/Export/index.ts +5 -0
  92. package/src/Export/run-export.ts +238 -0
  93. package/src/FileExplorer/FileExplorer.tsx +260 -0
  94. package/src/FileExplorer/FileTreeNode.tsx +172 -0
  95. package/src/FileExplorer/index.ts +5 -0
  96. package/src/FileExplorer/useFileTree.ts +178 -0
  97. package/src/WorkspacePicker/WorkspacePicker.tsx +126 -0
  98. package/src/WorkspacePicker/WorkspaceSettingsButton.tsx +79 -0
  99. package/src/WorkspacePicker/index.ts +2 -0
  100. package/src/hooks/index.ts +1 -0
  101. package/src/hooks/useAutoSave.ts +58 -0
  102. package/src/icons.tsx +60 -0
  103. package/src/index.ts +31 -0
  104. package/src/styles/docblocks.css +961 -0
@@ -0,0 +1,126 @@
1
+ /**
2
+ * WorkspacePicker — dropdown for switching between workspaces
3
+ * (IndexedDB-based or native folder via File System Access API).
4
+ */
5
+
6
+ import { useState, useEffect, useCallback, useRef } from 'react';
7
+ import type { WorkspaceDescriptor } from '@bendyline/docblocks/workspace';
8
+ import { listWorkspaces, saveWorkspace, touchWorkspace } from '@bendyline/docblocks/workspace';
9
+ import { isNativeFileSystemSupported } from '@bendyline/docblocks/filesystem';
10
+
11
+ export interface WorkspacePickerProps {
12
+ /** Currently active workspace id. */
13
+ activeWorkspaceId: string | null;
14
+ /** Called when the user picks a different workspace. */
15
+ onSelect: (descriptor: WorkspaceDescriptor) => void;
16
+ /** Called when the user chooses "Open Folder" (native FS). */
17
+ onOpenFolder: () => void;
18
+ /** Optional className. */
19
+ className?: string;
20
+ }
21
+
22
+ export function WorkspacePicker({
23
+ activeWorkspaceId,
24
+ onSelect,
25
+ onOpenFolder,
26
+ className,
27
+ }: WorkspacePickerProps) {
28
+ const [workspaces, setWorkspaces] = useState<WorkspaceDescriptor[]>([]);
29
+ const [isOpen, setIsOpen] = useState(false);
30
+ const pickerRef = useRef<HTMLDivElement>(null);
31
+
32
+ // Close dropdown on outside click
33
+ useEffect(() => {
34
+ if (!isOpen) return;
35
+ function handleOutsideClick(e: MouseEvent) {
36
+ if (pickerRef.current && !pickerRef.current.contains(e.target as Node)) {
37
+ setIsOpen(false);
38
+ }
39
+ }
40
+ document.addEventListener('mousedown', handleOutsideClick);
41
+ return () => document.removeEventListener('mousedown', handleOutsideClick);
42
+ }, [isOpen]);
43
+
44
+ const refresh = useCallback(async () => {
45
+ const list = await listWorkspaces();
46
+ setWorkspaces(list);
47
+ }, []);
48
+
49
+ useEffect(() => {
50
+ refresh();
51
+ }, [refresh, activeWorkspaceId]);
52
+
53
+ const handleSelect = useCallback(
54
+ async (ws: WorkspaceDescriptor) => {
55
+ await touchWorkspace(ws.id);
56
+ onSelect(ws);
57
+ setIsOpen(false);
58
+ },
59
+ [onSelect],
60
+ );
61
+
62
+ const handleCreateNew = useCallback(async () => {
63
+ const name = `Workspace ${workspaces.length + 1}`;
64
+ const id = `ws-${Date.now()}`;
65
+ const descriptor: WorkspaceDescriptor = {
66
+ id,
67
+ name,
68
+ type: 'indexeddb',
69
+ lastOpened: new Date().toISOString(),
70
+ };
71
+ await saveWorkspace(descriptor);
72
+ await refresh();
73
+ onSelect(descriptor);
74
+ setIsOpen(false);
75
+ }, [workspaces.length, onSelect, refresh]);
76
+
77
+ const activeWs = workspaces.find((w) => w.id === activeWorkspaceId);
78
+
79
+ return (
80
+ <div ref={pickerRef} className={`db-workspace-picker ${className ?? ''}`}>
81
+ <button
82
+ className="db-workspace-picker-btn"
83
+ onClick={() => setIsOpen(!isOpen)}
84
+ title="Switch workspace"
85
+ >
86
+ {activeWs?.name ?? 'No workspace'}
87
+ <span className="db-workspace-picker-caret">{isOpen ? '\u25B4' : '\u25BE'}</span>
88
+ </button>
89
+
90
+ {isOpen && (
91
+ <div className="db-workspace-dropdown">
92
+ {workspaces.map((ws) => (
93
+ <button
94
+ key={ws.id}
95
+ className={`db-workspace-dropdown-item ${
96
+ ws.id === activeWorkspaceId ? 'db-workspace-dropdown-item--active' : ''
97
+ }`}
98
+ onClick={() => handleSelect(ws)}
99
+ >
100
+ {ws.name}
101
+ <span className="db-workspace-type">{ws.type === 'native' ? '(folder)' : ''}</span>
102
+ </button>
103
+ ))}
104
+
105
+ <div className="db-workspace-dropdown-divider" />
106
+
107
+ <button className="db-workspace-dropdown-item" onClick={handleCreateNew}>
108
+ + New Workspace
109
+ </button>
110
+
111
+ {isNativeFileSystemSupported() && (
112
+ <button
113
+ className="db-workspace-dropdown-item"
114
+ onClick={() => {
115
+ setIsOpen(false);
116
+ onOpenFolder();
117
+ }}
118
+ >
119
+ Open Folder...
120
+ </button>
121
+ )}
122
+ </div>
123
+ )}
124
+ </div>
125
+ );
126
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * WorkspaceSettingsButton — gear icon dropdown for workspace actions.
3
+ */
4
+
5
+ import { useState, useCallback, useRef, useEffect } from 'react';
6
+ import { WorkspaceIcon } from '../icons.js';
7
+
8
+ export interface WorkspaceSettingsButtonProps {
9
+ onRename: () => void;
10
+ onDownload: () => void;
11
+ onRemove: () => void;
12
+ }
13
+
14
+ export function WorkspaceSettingsButton({
15
+ onRename,
16
+ onDownload,
17
+ onRemove,
18
+ }: WorkspaceSettingsButtonProps) {
19
+ const [isOpen, setIsOpen] = useState(false);
20
+ const ref = useRef<HTMLDivElement>(null);
21
+
22
+ useEffect(() => {
23
+ if (!isOpen) return;
24
+ function handleOutsideClick(e: MouseEvent) {
25
+ if (ref.current && !ref.current.contains(e.target as Node)) {
26
+ setIsOpen(false);
27
+ }
28
+ }
29
+ document.addEventListener('mousedown', handleOutsideClick);
30
+ return () => document.removeEventListener('mousedown', handleOutsideClick);
31
+ }, [isOpen]);
32
+
33
+ const handleAction = useCallback((action: () => void) => {
34
+ setIsOpen(false);
35
+ action();
36
+ }, []);
37
+
38
+ return (
39
+ <div ref={ref} className="db-ws-settings">
40
+ <button
41
+ className="db-ws-settings-btn"
42
+ onClick={() => setIsOpen(!isOpen)}
43
+ aria-expanded={isOpen}
44
+ aria-haspopup="true"
45
+ aria-label="Workspace settings"
46
+ title="Workspace settings"
47
+ >
48
+ <WorkspaceIcon />
49
+ </button>
50
+
51
+ {isOpen && (
52
+ <div className="db-ws-settings-dropdown" role="menu">
53
+ <button
54
+ className="db-ws-settings-item"
55
+ role="menuitem"
56
+ onClick={() => handleAction(onRename)}
57
+ >
58
+ Rename workspace
59
+ </button>
60
+ <button
61
+ className="db-ws-settings-item"
62
+ role="menuitem"
63
+ onClick={() => handleAction(onDownload)}
64
+ >
65
+ Download workspace
66
+ </button>
67
+ <div className="db-ws-settings-divider" />
68
+ <button
69
+ className="db-ws-settings-item db-ws-settings-item--danger"
70
+ role="menuitem"
71
+ onClick={() => handleAction(onRemove)}
72
+ >
73
+ Remove workspace
74
+ </button>
75
+ </div>
76
+ )}
77
+ </div>
78
+ );
79
+ }
@@ -0,0 +1,2 @@
1
+ export { WorkspacePicker } from './WorkspacePicker.js';
2
+ export type { WorkspacePickerProps } from './WorkspacePicker.js';
@@ -0,0 +1 @@
1
+ export { useAutoSave } from './useAutoSave.js';
@@ -0,0 +1,58 @@
1
+ /**
2
+ * useAutoSave — debounced auto-save hook.
3
+ *
4
+ * Writes content to the FileSystemProvider after a delay
5
+ * whenever the content changes.
6
+ */
7
+
8
+ import { useEffect, useRef, useCallback } from 'react';
9
+ import type { FileSystemProvider } from '@bendyline/docblocks/filesystem';
10
+
11
+ export function useAutoSave(
12
+ provider: FileSystemProvider | null,
13
+ filePath: string | null,
14
+ content: string,
15
+ delayMs = 500,
16
+ ): void {
17
+ const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
18
+ const lastSavedRef = useRef<string>(content);
19
+
20
+ const save = useCallback(async () => {
21
+ if (!provider || !filePath) return;
22
+ if (content === lastSavedRef.current) return;
23
+ lastSavedRef.current = content;
24
+ await provider.writeFile(filePath, content);
25
+ }, [provider, filePath, content]);
26
+
27
+ useEffect(() => {
28
+ if (!provider || !filePath) return;
29
+
30
+ if (timerRef.current) {
31
+ clearTimeout(timerRef.current);
32
+ }
33
+
34
+ timerRef.current = setTimeout(() => {
35
+ save();
36
+ }, delayMs);
37
+
38
+ return () => {
39
+ if (timerRef.current) {
40
+ clearTimeout(timerRef.current);
41
+ }
42
+ };
43
+ }, [content, delayMs, save, provider, filePath]);
44
+
45
+ // Flush on unmount
46
+ useEffect(() => {
47
+ return () => {
48
+ if (timerRef.current) {
49
+ clearTimeout(timerRef.current);
50
+ }
51
+ // Sync save on unmount is best-effort
52
+ if (provider && filePath && content !== lastSavedRef.current) {
53
+ provider.writeFile(filePath, content);
54
+ }
55
+ };
56
+ // eslint-disable-next-line react-hooks/exhaustive-deps
57
+ }, []);
58
+ }
package/src/icons.tsx ADDED
@@ -0,0 +1,60 @@
1
+ import type { SVGProps } from 'react';
2
+
3
+ const defaults: SVGProps<SVGSVGElement> = {
4
+ width: 16,
5
+ height: 16,
6
+ viewBox: '0 0 16 16',
7
+ fill: 'none',
8
+ stroke: 'currentColor',
9
+ strokeWidth: 1.5,
10
+ strokeLinecap: 'round' as const,
11
+ strokeLinejoin: 'round' as const,
12
+ };
13
+
14
+ export function NewFileIcon(props: SVGProps<SVGSVGElement>) {
15
+ return (
16
+ <svg {...defaults} {...props}>
17
+ {/* Page outline */}
18
+ <path d="M9.5 1.5H4a1 1 0 0 0-1 1v11a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V5l-3.5-3.5Z" />
19
+ {/* Fold */}
20
+ <path d="M9.5 1.5V5H13" />
21
+ {/* Plus sign */}
22
+ <line x1="6.5" y1="10" x2="9.5" y2="10" />
23
+ <line x1="8" y1="8.5" x2="8" y2="11.5" />
24
+ </svg>
25
+ );
26
+ }
27
+
28
+ export function NewFolderIcon(props: SVGProps<SVGSVGElement>) {
29
+ return (
30
+ <svg {...defaults} {...props}>
31
+ {/* Folder outline */}
32
+ <path d="M2 3.5a1 1 0 0 1 1-1h3l1.5 1.5H13a1 1 0 0 1 1 1v7a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5Z" />
33
+ {/* Plus sign */}
34
+ <line x1="6" y1="8.5" x2="10" y2="8.5" />
35
+ <line x1="8" y1="6.5" x2="8" y2="10.5" />
36
+ </svg>
37
+ );
38
+ }
39
+
40
+ export function RefreshIcon(props: SVGProps<SVGSVGElement>) {
41
+ return (
42
+ <svg {...defaults} {...props}>
43
+ <path d="M13.5 8A5.5 5.5 0 1 1 8 2.5" />
44
+ <polyline points="13.5 3 13.5 6.5 10 6.5" />
45
+ </svg>
46
+ );
47
+ }
48
+
49
+ export function WorkspaceIcon(props: SVGProps<SVGSVGElement>) {
50
+ return (
51
+ <svg {...defaults} {...props}>
52
+ {/* Desk surface (top-down rectangle) */}
53
+ <rect x="1.5" y="2.5" width="13" height="11" rx="1" />
54
+ {/* Blotter/pad with two vertical lines */}
55
+ <rect x="4" y="4.5" width="8" height="7" rx="0.5" />
56
+ <line x1="6.5" y1="5.5" x2="6.5" y2="10.5" strokeWidth="1" />
57
+ <line x1="9.5" y1="5.5" x2="9.5" y2="10.5" strokeWidth="1" />
58
+ </svg>
59
+ );
60
+ }
package/src/index.ts ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @bendyline/docblocks-react — React components for DocBlocks
3
+ */
4
+
5
+ // FileExplorer
6
+ export { FileExplorer } from './FileExplorer/index.js';
7
+ export type { FileExplorerProps } from './FileExplorer/index.js';
8
+ export { FileTreeNode } from './FileExplorer/index.js';
9
+ export type { FileTreeNodeProps } from './FileExplorer/index.js';
10
+ export { useFileTree } from './FileExplorer/index.js';
11
+
12
+ // WorkspacePicker
13
+ export { WorkspacePicker } from './WorkspacePicker/index.js';
14
+ export type { WorkspacePickerProps } from './WorkspacePicker/index.js';
15
+
16
+ // DocBlocksShell
17
+ export { DocBlocksShell } from './DocBlocksShell/index.js';
18
+ export type { DocBlocksShellProps } from './DocBlocksShell/index.js';
19
+
20
+ // AppMenu
21
+ export { AppMenu } from './AppMenu/index.js';
22
+ export type { AppMenuProps } from './AppMenu/index.js';
23
+
24
+ // Export
25
+ export { ExportToolbarControls } from './Export/index.js';
26
+ export type { ExportToolbarControlsProps, ExportFormat, ExportOptions } from './Export/index.js';
27
+ export { ExportDialog } from './Export/index.js';
28
+ export type { ExportDialogProps } from './Export/index.js';
29
+
30
+ // Hooks
31
+ export { useAutoSave } from './hooks/index.js';