Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(files): read-only mode #300

Merged
merged 15 commits into from
Jan 14, 2022
1 change: 1 addition & 0 deletions sandpack-client/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { ITemplate } from "codesandbox-import-util-types";

export interface SandpackBundlerFile {
code: string;
readOnly?: boolean;
}

export type SandpackBundlerFiles = Record<string, SandpackBundlerFile>;
Expand Down
12 changes: 10 additions & 2 deletions sandpack-react/src/components/CodeEditor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ export interface CodeEditorProps {
*/
extensionsKeymap?: Array<readonly KeyBinding[]>;
id?: string;
/**
* This disables editing of the editor content by the user.
*/
readOnly?: boolean;
}

export { CodeMirror as CodeEditor };
Expand All @@ -63,11 +67,12 @@ export const SandpackCodeEditor = React.forwardRef<
extensions,
extensionsKeymap,
id,
readOnly,
},
ref
) => {
const { sandpack } = useSandpack();
const { code, updateCode } = useActiveCode();
const { code, updateCode, readOnly: readOnlyFile } = useActiveCode();
const { activePath, status, editorState } = sandpack;
const shouldShowTabs = showTabs ?? sandpack.openPaths.length > 1;

Expand All @@ -79,7 +84,9 @@ export const SandpackCodeEditor = React.forwardRef<

return (
<SandpackStack customStyle={customStyle}>
{shouldShowTabs ? <FileTabs closableTabs={closableTabs} /> : null}
{shouldShowTabs && (
<FileTabs readOnly={readOnly} closableTabs={closableTabs} />
)}

<div className={c("code-editor")}>
<CodeMirror
Expand All @@ -96,6 +103,7 @@ export const SandpackCodeEditor = React.forwardRef<
showInlineErrors={showInlineErrors}
showLineNumbers={showLineNumbers}
wrapContent={wrapContent}
readOnly={readOnly || readOnlyFile}
/>

{showRunButton && status === "idle" ? <RunButton /> : null}
Expand Down
30 changes: 30 additions & 0 deletions sandpack-react/src/components/FileTabs/FileTabs.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as React from "react";
import { SandpackLayout } from "../../common/Layout";
import { SandpackProvider } from "../../contexts/sandpackContext";
import { SandpackCodeViewer } from "../CodeViewer";
import { Sandpack } from "../../";

import { FileTabs } from "./index";

Expand Down Expand Up @@ -60,3 +61,32 @@ export const WithHiddenFiles: React.FC = () => (
</SandpackLayout>
</SandpackProvider>
);

export const ReadOnlyByFile: React.FC = () => (
<Sandpack
customSetup={{ entry: "/index.tsx", main: "/App.tsx" }}
files={{
"/index.tsx": { code: "", hidden: true },
"/src/App.tsx": { code: "Hello", readOnly: true },
"/src/components/button.tsx": { code: "World", readOnly: false },
}}
options={{ showTabs: true }}
template="react-ts"
/>
);

export const ReadOnlyGlobal: React.FC = () => (
<Sandpack options={{ showTabs: true, readOnly: true }} template="react-ts" />
);

export const ReadOnlyGlobalAndPerFile: React.FC = () => (
<Sandpack
options={{ showTabs: true, readOnly: true }}
files={{
"/index.tsx": { code: "", hidden: true },
"/src/App.tsx": { code: "Hello", readOnly: true },
"/src/components/button.tsx": { code: "World", readOnly: false },
}}
template="react-ts"
/>
);
32 changes: 28 additions & 4 deletions sandpack-react/src/components/FileTabs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,29 @@ import {
} from "../../utils/stringUtils";

export interface FileTabsProps {
/**
* This adds a close button next to each file with a proper trigger to close it.
danilowoz marked this conversation as resolved.
Show resolved Hide resolved
*/
closableTabs?: boolean;
/**
* This adds a "Read-only" label beside the file list.
danilowoz marked this conversation as resolved.
Show resolved Hide resolved
*/
readOnly?: boolean;
}

/**
* FileTabs is a list of all open files, active file and its state.
danilowoz marked this conversation as resolved.
Show resolved Hide resolved
*
* @category Components
*/
export const FileTabs = ({ closableTabs }: FileTabsProps): JSX.Element => {
export const FileTabs = ({
closableTabs,
readOnly: globalReadOnly,
}: FileTabsProps): JSX.Element => {
const { sandpack } = useSandpack();
const c = useClasser("sp");

const { activePath, openPaths, setActiveFile } = sandpack;
const { activePath, openPaths, setActiveFile, files } = sandpack;

const handleCloseFile = (ev: React.MouseEvent<HTMLDivElement>): void => {
ev.stopPropagation();
Expand Down Expand Up @@ -80,14 +92,26 @@ export const FileTabs = ({ closableTabs }: FileTabsProps): JSX.Element => {
type="button"
>
{getTriggerText(filePath)}
{closableTabs && openPaths.length > 1 ? (
{closableTabs && openPaths.length > 1 && (
<span className={c("close-button")} onClick={handleCloseFile}>
<CloseIcon />
</span>
) : null}
)}

{!globalReadOnly && files[filePath].readOnly && (
<span className={c("label-wrap")}>
<span className={c("label")}>Read-only</span>
</span>
)}
</button>
))}
</div>

{globalReadOnly && (
<span className={c("label-wrap")}>
<span className={c("label")}>Read-only</span>
</span>
)}
</div>
);
};
5 changes: 5 additions & 0 deletions sandpack-react/src/hooks/useActiveCode.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import { useSandpack } from "./useSandpack";

/**
* This returns the current state of the active file
* and a method to update its content.
*
* @category Hooks
*/
export const useActiveCode = (): {
code: string;
readOnly: boolean;
updateCode: (newCode: string) => void;
} => {
const { sandpack } = useSandpack();

return {
code: sandpack.files[sandpack.activePath].code,
readOnly: sandpack.files[sandpack.activePath].readOnly ?? false,
updateCode: sandpack.updateCurrentFile,
};
};
4 changes: 2 additions & 2 deletions sandpack-react/src/presets/Playground.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ export const Main = (): JSX.Element => {
return (
<Sandpack
customSetup={{ entry: "/index.tsx", main: "/App.tsx" }}
files={{ "/App.tsx": code }}
options={{ showTabs: true, openPaths: ["/index.tsx"] }}
files={{ "/App.tsx": { code, readOnly: true } }}
options={{ showTabs: true }}
template="react-ts"
/>
);
Expand Down
6 changes: 6 additions & 0 deletions sandpack-react/src/presets/Sandpack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ export interface SandpackProps {
recompileMode?: "immediate" | "delayed";
recompileDelay?: number;
codeEditor?: SandpackCodeOptions;

/**
* This disables editing of the all files content by the user.
danilowoz marked this conversation as resolved.
Show resolved Hide resolved
*/
readOnly?: boolean;
};
}

Expand Down Expand Up @@ -88,6 +93,7 @@ export const Sandpack: React.FC<SandpackProps> = (props) => {
initMode: props.options?.initMode,
extensions: props.options?.codeEditor?.extensions,
extensionsKeymap: props.options?.codeEditor?.extensionsKeymap,
readOnly: props.options?.readOnly,
};

const providerOptions: SandpackProviderProps = {
Expand Down
20 changes: 19 additions & 1 deletion sandpack-react/src/styles/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@
.sp-tabs {
border-bottom: 1px solid var(--sp-colors-fg-inactive);
background: var(--sp-colors-bg-default);
display: flex;
}

.sp-tabs-scrollable-container {
Expand All @@ -209,6 +210,7 @@
align-items: stretch;
min-height: 40px;
margin-bottom: -1px;
flex: 1;
}

.sp-preview-container {
Expand Down Expand Up @@ -255,7 +257,8 @@
/* Common Styling */

.sp-tab-button {
display: block;
display: flex;
align-items: center;
background: transparent;
appearance: none;
font-size: inherit;
Expand Down Expand Up @@ -299,6 +302,21 @@
visibility: unset;
}

.sp-label-wrap {
display: inline-flex;
padding: 0 var(--sp-space-4);
align-items: center;
}

.sp-label {
padding: 1px var(--sp-space-1);
border-radius: var(--sp-border-radius);
font-size: 0.6em;
text-transform: uppercase;
background-color: var(--sp-colors-fg-inactive);
white-space: nowrap;
}

.sp-button {
appearance: none;
border: 0;
Expand Down
1 change: 1 addition & 0 deletions sandpack-react/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export interface SandpackFile {
code: string;
hidden?: boolean;
active?: boolean;
readOnly?: boolean;
}

export type SandpackFiles = Record<string, string | SandpackFile>;
Expand Down
37 changes: 33 additions & 4 deletions website/docs/docs/getting-started/custom-content.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,12 @@ default:
```jsx
<Sandpack
files={{
'/App.js': reactCode,
'/button.js': {
"/App.js": reactCode,
"/button.js": {
code: buttonCode,
active: true,
}
'/link.js': {
},
"/link.js": {
code: linkCode,
hidden: true,
},
Expand All @@ -152,6 +152,35 @@ default:
The `active` flag has precendence over the `hidden` flag. So a file with both `hidden` and `active` set as `true` will be visible.
:::

### Read-only mode

You can set just one or more files as read-only or the entire Sandpack, which will make all files non-editable.
danilowoz marked this conversation as resolved.
Show resolved Hide resolved

**Per file:**

```jsx
<Sandpack
files={{
"/App.js": reactCode,
"/button.js": {
code: buttonCode,
readOnly: true,
},
}}
template="react"
/>
```

**Globally:**

```jsx
<Sandpack
options={{
readOnly: true,
}}
/>
```

### openPaths and activePath

You can override the entire hidden/active system with two settings (`openPaths` and `activePath`) inside the
Expand Down