@marimo-team/islands 0.23.14-dev37 → 0.23.14-dev38

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.23.14-dev37",
3
+ "version": "0.23.14-dev38",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -0,0 +1,79 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { fireEvent, render, screen } from "@testing-library/react";
4
+ import { beforeAll, describe, expect, it } from "vitest";
5
+ import { SetupMocks } from "@/__mocks__/common";
6
+ import { TooltipProvider } from "@/components/ui/tooltip";
7
+ import { ReadonlyCode } from "../readonly-python-code";
8
+
9
+ beforeAll(() => {
10
+ SetupMocks.resizeObserver();
11
+ });
12
+
13
+ /**
14
+ * The only button is the show/hide toggle: copy is disabled and the
15
+ * insert-cell button is off by default.
16
+ */
17
+ function renderReadonly(props: { initiallyHideCode?: boolean }) {
18
+ return render(
19
+ <TooltipProvider>
20
+ <ReadonlyCode code="x = 1" showCopyCode={false} {...props} />
21
+ </TooltipProvider>,
22
+ );
23
+ }
24
+
25
+ function isCollapsed(root: ParentNode) {
26
+ return root.querySelector(".cm")?.classList.contains("opacity-20") ?? false;
27
+ }
28
+
29
+ describe("ReadonlyCode", () => {
30
+ it("starts collapsed when initiallyHideCode is true", () => {
31
+ const { container } = renderReadonly({ initiallyHideCode: true });
32
+ expect(isCollapsed(container)).toBe(true);
33
+ });
34
+
35
+ it("starts expanded when initiallyHideCode is false", () => {
36
+ const { container } = renderReadonly({ initiallyHideCode: false });
37
+ expect(isCollapsed(container)).toBe(false);
38
+ });
39
+
40
+ it("starts expanded when initiallyHideCode is unset", () => {
41
+ const { container } = renderReadonly({});
42
+ expect(isCollapsed(container)).toBe(false);
43
+ });
44
+
45
+ it("toggles visibility locally on click", () => {
46
+ const { container } = renderReadonly({ initiallyHideCode: true });
47
+ const toggle = screen.getByRole("button");
48
+
49
+ fireEvent.click(toggle);
50
+ expect(isCollapsed(container)).toBe(false);
51
+
52
+ fireEvent.click(toggle);
53
+ expect(isCollapsed(container)).toBe(true);
54
+ });
55
+
56
+ it("keeps each instance's visibility independent", () => {
57
+ const { container } = render(
58
+ <TooltipProvider>
59
+ <ReadonlyCode
60
+ code="a = 1"
61
+ showCopyCode={false}
62
+ initiallyHideCode={true}
63
+ />
64
+ <ReadonlyCode
65
+ code="b = 2"
66
+ showCopyCode={false}
67
+ initiallyHideCode={true}
68
+ />
69
+ </TooltipProvider>,
70
+ );
71
+ const [firstToggle] = screen.getAllByRole("button");
72
+ const [first, second] = container.querySelectorAll(".cm");
73
+
74
+ fireEvent.click(firstToggle);
75
+
76
+ expect(first.classList.contains("opacity-20")).toBe(false);
77
+ expect(second.classList.contains("opacity-20")).toBe(true);
78
+ });
79
+ });
@@ -2,18 +2,24 @@
2
2
 
3
3
  import { markdown } from "@codemirror/lang-markdown";
4
4
  import { sql } from "@codemirror/lang-sql";
5
+ import {
6
+ defaultHighlightStyle,
7
+ syntaxHighlighting,
8
+ } from "@codemirror/language";
5
9
  import CodeMirror, {
6
10
  EditorView,
7
11
  type ReactCodeMirrorProps,
8
12
  } from "@uiw/react-codemirror";
9
13
  import { CopyIcon, EyeIcon, EyeOffIcon, PlusIcon } from "lucide-react";
10
- import { memo, useState } from "react";
14
+ import { memo, useMemo, useState } from "react";
11
15
  import { useAddCodeToNewCell } from "@/components/editor/cell/useAddCell";
12
16
  import { Button } from "@/components/ui/button";
13
17
  import { Tooltip } from "@/components/ui/tooltip";
14
18
  import { toast } from "@/components/ui/use-toast";
15
19
  import type { LanguageAdapterType } from "@/core/codemirror/language/types";
16
20
  import { customPythonLanguageSupport } from "@/core/codemirror/language/languages/python";
21
+ import { darkTheme } from "@/core/codemirror/theme/dark";
22
+ import { lightTheme } from "@/core/codemirror/theme/light";
17
23
  import { useTheme } from "@/theme/useTheme";
18
24
  import { cn } from "@/utils/cn";
19
25
  import { copyToClipboard } from "@/utils/copy";
@@ -72,33 +78,39 @@ export const ReadonlyCode = memo(
72
78
  } = props;
73
79
  const [hideCode, setHideCode] = useState(initiallyHideCode);
74
80
 
81
+ const extensions = useMemo(
82
+ () => [
83
+ theme === "dark" ? darkTheme : lightTheme,
84
+ syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
85
+ ...readonlyCodeExtensions(language),
86
+ ],
87
+ [theme, language],
88
+ );
89
+
75
90
  return (
76
91
  <div
77
92
  className={cn(
78
- "relative hover-actions-parent w-full overflow-hidden",
93
+ "relative hover-actions-parent w-full overflow-hidden pb-1",
79
94
  className,
80
95
  )}
81
96
  >
82
- {showHideCode && hideCode && (
83
- <HideCodeButton
84
- tooltip="Show code"
85
- onClick={() => setHideCode(false)}
86
- />
87
- )}
88
97
  <div className="absolute top-0 right-0 my-1 mx-2 z-10 hover-action flex gap-2">
89
98
  {showCopyCode && <CopyButton text={code} />}
90
99
  {insertNewCell && <InsertNewCell code={code} />}
91
- {showHideCode && !hideCode && (
92
- <EyeCloseButton onClick={() => setHideCode(true)} />
100
+ {showHideCode && (
101
+ <ToggleCodeButton
102
+ hidden={hideCode ?? false}
103
+ onClick={() => setHideCode(!hideCode)}
104
+ />
93
105
  )}
94
106
  </div>
95
107
  <CodeMirror
96
108
  {...rest}
97
109
  className={cn("cm", hideCode && "opacity-20 h-8 overflow-hidden")}
98
- theme={theme === "dark" ? "dark" : "light"}
110
+ theme="none"
99
111
  height="100%"
100
- editable={!hideCode}
101
- extensions={readonlyCodeExtensions(language)}
112
+ editable={false}
113
+ extensions={extensions}
102
114
  value={code}
103
115
  readOnly={true}
104
116
  />
@@ -123,35 +135,28 @@ const CopyButton = (props: { text: string }) => {
123
135
  );
124
136
  };
125
137
 
126
- const EyeCloseButton = (props: { onClick: () => void }) => {
138
+ const ToggleCodeButton = (props: { hidden: boolean; onClick: () => void }) => {
127
139
  return (
128
- <Tooltip content="Hide code" usePortal={false}>
140
+ <Tooltip
141
+ content={props.hidden ? "Show code" : "Hide code"}
142
+ usePortal={false}
143
+ >
129
144
  <Button
130
145
  onClick={props.onClick}
131
146
  size="xs"
132
147
  className="py-0"
133
148
  variant="secondary"
134
149
  >
135
- <EyeOffIcon size={14} strokeWidth={1.5} />
150
+ {props.hidden ? (
151
+ <EyeIcon size={14} strokeWidth={1.5} />
152
+ ) : (
153
+ <EyeOffIcon size={14} strokeWidth={1.5} />
154
+ )}
136
155
  </Button>
137
156
  </Tooltip>
138
157
  );
139
158
  };
140
159
 
141
- export const HideCodeButton = (props: {
142
- tooltip?: string;
143
- className?: string;
144
- onClick: () => void;
145
- }) => {
146
- return (
147
- <div className={props.className} onClick={props.onClick}>
148
- <Tooltip usePortal={false} content={props.tooltip}>
149
- <EyeIcon className="hover-action w-5 h-5 text-muted-foreground cursor-pointer absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 opacity-80 hover:opacity-100 z-20" />
150
- </Tooltip>
151
- </div>
152
- );
153
- };
154
-
155
160
  const InsertNewCell = (props: { code: string }) => {
156
161
  const addCodeToNewCell = useAddCodeToNewCell();
157
162
 
@@ -399,7 +399,7 @@ const VerticalCell = memo(
399
399
  {display && (
400
400
  <div className="tray">
401
401
  <ReadonlyCode
402
- initiallyHideCode={config.hide_code || kiosk}
402
+ initiallyHideCode={config.hide_code}
403
403
  code={display.code}
404
404
  language={display.language}
405
405
  />