Skip to content

Commit 5962354

Browse files
committed
feat(react): add allowEditPatterns to FileTree
1 parent eef08aa commit 5962354

File tree

8 files changed

+62
-17
lines changed

8 files changed

+62
-17
lines changed

docs/tutorialkit.dev/src/content/docs/reference/react-components.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ A component to list files in a tree view.
116116
}
117117
```
118118

119+
* `allowEditPatterns?: string[]` - Glob patterns for paths that allow editing files and folders. Defaults to `['**']`.
120+
119121
* `hideRoot: boolean` - Whether or not to hide the root directory in the tree. Defaults to `false`.
120122

121123
* `hiddenFiles: (string | RegExp)[]` - A list of file paths that should be hidden from the tree.

packages/react/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,14 @@
8585
"codemirror": "^6.0.1",
8686
"framer-motion": "^11.2.11",
8787
"nanostores": "^0.10.3",
88+
"picomatch": "^4.0.2",
8889
"react": "^18.3.1",
8990
"react-resizable-panels": "^2.0.19"
9091
},
9192
"devDependencies": {
9293
"@codemirror/search": "^6.5.6",
9394
"@tutorialkit/types": "workspace:*",
95+
"@types/picomatch": "^3.0.1",
9496
"@types/react": "^18.3.3",
9597
"chokidar": "3.6.0",
9698
"execa": "^9.2.0",

packages/react/src/Panels/EditorPanel.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ interface Props {
2525
helpAction?: 'solve' | 'reset';
2626
editorDocument?: EditorDocument;
2727
selectedFile?: string | undefined;
28+
allowEditPatterns?: ComponentProps<typeof FileTree>['allowEditPatterns'];
2829
onEditorChange?: OnEditorChange;
2930
onEditorScroll?: OnEditorScroll;
3031
onHelpClick?: () => void;
@@ -43,6 +44,7 @@ export function EditorPanel({
4344
helpAction,
4445
editorDocument,
4546
selectedFile,
47+
allowEditPatterns,
4648
onEditorChange,
4749
onEditorScroll,
4850
onHelpClick,
@@ -83,6 +85,7 @@ export function EditorPanel({
8385
hideRoot={hideRoot ?? true}
8486
files={files}
8587
scope={fileTreeScope}
88+
allowEditPatterns={allowEditPatterns}
8689
onFileSelect={onFileSelect}
8790
onFileChange={onFileTreeChange}
8891
/>

packages/react/src/Panels/WorkspacePanel.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ function EditorSection({ theme, tutorialStore, hasEditor }: PanelProps) {
161161
onHelpClick={lessonFullyLoaded ? onHelpClick : undefined}
162162
onFileSelect={(filePath) => tutorialStore.setSelectedFile(filePath)}
163163
onFileTreeChange={editorConfig.fileTree.allowEdits ? onFileTreeChange : undefined}
164+
allowEditPatterns={editorConfig.fileTree.allowEdits ? editorConfig.fileTree.allowEdits : undefined}
164165
selectedFile={selectedFile}
165166
onEditorScroll={(position) => tutorialStore.setCurrentDocumentScrollPosition(position)}
166167
onEditorChange={(update) => tutorialStore.setCurrentDocumentContent(update.content)}

packages/react/src/core/ContextMenu.tsx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useRef, useState, type ComponentProps } from 'react';
22
import { Root, Portal, Content, Item, Trigger } from '@radix-ui/react-context-menu';
3+
import picomatch from 'picomatch/posix';
34
import type { FileDescriptor, I18n } from '@tutorialkit/types';
45

56
interface FileChangeEvent {
@@ -17,6 +18,9 @@ interface Props extends ComponentProps<'div'> {
1718
/** Callback invoked when file is changed. */
1819
onFileChange?: (event: FileChangeEvent | FileRenameEvent) => void;
1920

21+
/** Glob patterns for paths that allow editing files and folders. Defaults to `['**']`. */
22+
allowEditPatterns?: string[];
23+
2024
/** Directory of the clicked file. */
2125
directory: string;
2226

@@ -32,6 +36,7 @@ interface Props extends ComponentProps<'div'> {
3236

3337
export function ContextMenu({
3438
onFileChange,
39+
allowEditPatterns = ['**'],
3540
directory,
3641
i18n,
3742
position = 'before',
@@ -50,11 +55,19 @@ export function ContextMenu({
5055
const name = event.currentTarget.value;
5156

5257
if (name) {
53-
onFileChange?.({
54-
value: `${directory}/${name}`,
55-
type: state === 'add_file' ? 'file' : 'folder',
56-
method: 'add',
57-
});
58+
const value = `${directory}/${name}`;
59+
const isAllowed = picomatch.isMatch(value, allowEditPatterns);
60+
61+
if (isAllowed) {
62+
onFileChange?.({
63+
value,
64+
type: state === 'add_file' ? 'file' : 'folder',
65+
method: 'add',
66+
});
67+
} else {
68+
// TODO: Use `@radix-ui/react-dialog` instead
69+
alert(`File "${value}" is not allowed. Allowed patterns: [${allowEditPatterns.join(', ')}].`);
70+
}
5871
}
5972

6073
setState('idle');

packages/react/src/core/FileTree.tsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ interface Props {
1111
selectedFile?: string;
1212
onFileSelect?: (filePath: string) => void;
1313
onFileChange?: ComponentProps<typeof ContextMenu>['onFileChange'];
14+
allowEditPatterns?: ComponentProps<typeof ContextMenu>['allowEditPatterns'];
1415
i18n?: ComponentProps<typeof ContextMenu>['i18n'];
1516
hideRoot: boolean;
1617
scope?: string;
@@ -22,6 +23,7 @@ export function FileTree({
2223
files,
2324
onFileSelect,
2425
onFileChange,
26+
allowEditPatterns,
2527
selectedFile,
2628
hideRoot,
2729
scope,
@@ -109,6 +111,7 @@ export function FileTree({
109111
collapsed={collapsedFolders.has(fileOrFolder.id)}
110112
onClick={() => toggleCollapseState(fileOrFolder.id)}
111113
onFileChange={onFileChange}
114+
allowEditPatterns={allowEditPatterns}
112115
/>
113116
);
114117
}
@@ -121,6 +124,7 @@ export function FileTree({
121124
style={getDepthStyle(0)}
122125
directory=""
123126
onFileChange={onFileChange}
127+
allowEditPatterns={allowEditPatterns}
124128
triggerProps={{ className: 'h-full', 'data-testid': 'file-tree-root-context-menu' }}
125129
/>
126130
</div>
@@ -134,12 +138,26 @@ interface FolderProps {
134138
collapsed: boolean;
135139
onClick: () => void;
136140
onFileChange: Props['onFileChange'];
141+
allowEditPatterns: Props['allowEditPatterns'];
137142
i18n: Props['i18n'];
138143
}
139144

140-
function Folder({ folder: { depth, name, fullPath }, i18n, collapsed, onClick, onFileChange }: FolderProps) {
145+
function Folder({
146+
folder: { depth, name, fullPath },
147+
i18n,
148+
collapsed,
149+
onClick,
150+
onFileChange,
151+
allowEditPatterns,
152+
}: FolderProps) {
141153
return (
142-
<ContextMenu onFileChange={onFileChange} i18n={i18n} directory={fullPath} style={getDepthStyle(1 + depth)}>
154+
<ContextMenu
155+
onFileChange={onFileChange}
156+
allowEditPatterns={allowEditPatterns}
157+
i18n={i18n}
158+
directory={fullPath}
159+
style={getDepthStyle(1 + depth)}
160+
>
143161
<NodeButton
144162
className="group transition-theme bg-tk-elements-fileTree-folder-backgroundColor hover:bg-tk-elements-fileTree-folder-backgroundColorHover text-tk-elements-fileTree-folder-textColor hover:text-tk-elements-fileTree-folder-textColor"
145163
depth={depth}

packages/react/src/types.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// https://github.com/micromatch/picomatch?tab=readme-ov-file#api
2+
declare module 'picomatch/posix' {
3+
export { default } from 'picomatch';
4+
}

pnpm-lock.yaml

Lines changed: 12 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)