likec4 0.52.0 → 0.53.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.
@@ -0,0 +1,174 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { isEqualSimple } from "@react-hookz/deep-equal/esnext";
3
+ import { useToggle } from "@react-hookz/web/esm";
4
+ import { useSpring } from "@react-spring/konva";
5
+ import { lighten, mix, toHex } from "khroma";
6
+ import { memo } from "react";
7
+ import { AnimatedCircle, AnimatedGroup, Group } from "../../konva.js";
8
+ import { ZoomInIcon } from "../icons/index.js";
9
+ import { DiagramGesture } from "../state/index.js";
10
+ import { mouseDefault, mousePointer } from "../utils.js";
11
+ export const NodeZoomBtn = memo(({ animate, node, theme, isHovered: _isHovered, onNodeClick }) => {
12
+ const size = 30;
13
+ const halfSize = size / 2;
14
+ const colors = theme.elements[node.color];
15
+ let zoomInIconY;
16
+ switch (node.shape) {
17
+ case "browser":
18
+ case "mobile":
19
+ zoomInIconY = node.size.height - 20;
20
+ break;
21
+ default:
22
+ zoomInIconY = node.size.height - 16;
23
+ }
24
+ const fill = toHex(mix(colors.fill, colors.stroke, 65));
25
+ const onOver = toHex(mix(colors.fill, colors.stroke, 75));
26
+ const [isOver, toggleOver] = useToggle(false);
27
+ const isHovered = _isHovered || isOver;
28
+ const props = useSpring({
29
+ to: {
30
+ fill: isOver ? onOver : fill,
31
+ opacity: isOver ? 1 : 0,
32
+ y: zoomInIconY + (isOver ? 2 : 0),
33
+ scale: isOver ? 1.38 : 1,
34
+ // shadowBlur: isOver ? 6 : 4,
35
+ shadowOpacity: isOver ? 0.3 : 0.15
36
+ // shadowOffsetY: isOver ? 8 : 6
37
+ },
38
+ delay: isHovered && !isOver ? 100 : 0,
39
+ immediate: !animate
40
+ });
41
+ return /* @__PURE__ */ jsxs(
42
+ AnimatedGroup,
43
+ {
44
+ x: node.size.width / 2,
45
+ y: props.y,
46
+ offsetX: halfSize,
47
+ offsetY: halfSize,
48
+ scaleX: props.scale,
49
+ scaleY: props.scale,
50
+ width: size,
51
+ height: size,
52
+ onPointerEnter: (e) => {
53
+ toggleOver(true);
54
+ mousePointer(e);
55
+ },
56
+ onPointerLeave: (e) => {
57
+ toggleOver(false);
58
+ mouseDefault(e);
59
+ },
60
+ onPointerClick: (e) => {
61
+ if (DiagramGesture.isDragging || e.evt.button !== 0) {
62
+ return;
63
+ }
64
+ e.cancelBubble = true;
65
+ onNodeClick(node, e);
66
+ },
67
+ children: [
68
+ /* @__PURE__ */ jsx(
69
+ AnimatedCircle,
70
+ {
71
+ x: halfSize,
72
+ y: halfSize,
73
+ radius: halfSize,
74
+ fill: props.fill,
75
+ shadowBlur: 4,
76
+ shadowOpacity: props.shadowOpacity,
77
+ shadowOffsetX: 2,
78
+ shadowOffsetY: 6,
79
+ shadowColor: theme.shadow,
80
+ shadowEnabled: isHovered,
81
+ perfectDrawEnabled: false,
82
+ opacity: props.opacity,
83
+ hitStrokeWidth: halfSize
84
+ }
85
+ ),
86
+ /* @__PURE__ */ jsx(ZoomInIcon, { size: 16, x: halfSize, y: halfSize })
87
+ ]
88
+ }
89
+ );
90
+ }, isEqualSimple);
91
+ NodeZoomBtn.displayName = "NodeZoomBtn";
92
+ export const CompoundZoomBtn = memo(({
93
+ animate,
94
+ node,
95
+ theme,
96
+ ctrl,
97
+ isHovered: _isHovered,
98
+ onNodeClick
99
+ }) => {
100
+ const size = 28;
101
+ const [isOver, toggleOver] = useToggle(false);
102
+ const halfSize = size / 2;
103
+ const fill = toHex(lighten(ctrl.springs.fill.get(), 10));
104
+ const isHovered = _isHovered || isOver;
105
+ const props = useSpring({
106
+ to: {
107
+ opacity: isOver ? 1 : 0,
108
+ x: halfSize + 4 - (isOver ? 4 : 0),
109
+ y: halfSize + 6 - (isOver ? 4 : 0),
110
+ scale: isOver ? 1.35 : 1,
111
+ // shadowBlur: isOver ? 6 : 4,
112
+ shadowOpacity: isOver ? 0.3 : 0.15
113
+ // shadowOffsetY: isOver ? 8 : 6
114
+ },
115
+ delay: isHovered && !isOver ? 100 : 0,
116
+ // delay: isOver ? 150 : (isHovered ? 70 : 0),
117
+ immediate: !animate
118
+ });
119
+ return /* @__PURE__ */ jsx(
120
+ Group,
121
+ {
122
+ onPointerEnter: (e) => {
123
+ toggleOver(true);
124
+ mousePointer(e);
125
+ },
126
+ onPointerLeave: (e) => {
127
+ toggleOver(false);
128
+ mouseDefault(e);
129
+ },
130
+ onPointerClick: (e) => {
131
+ if (DiagramGesture.isDragging || e.evt.button !== 0) {
132
+ return;
133
+ }
134
+ e.cancelBubble = true;
135
+ onNodeClick(node, e);
136
+ },
137
+ children: /* @__PURE__ */ jsxs(
138
+ AnimatedGroup,
139
+ {
140
+ x: props.x,
141
+ y: props.y,
142
+ offsetX: halfSize,
143
+ offsetY: halfSize,
144
+ scaleX: props.scale,
145
+ scaleY: props.scale,
146
+ width: size,
147
+ height: size,
148
+ children: [
149
+ /* @__PURE__ */ jsx(
150
+ AnimatedCircle,
151
+ {
152
+ x: halfSize,
153
+ y: halfSize,
154
+ radius: halfSize,
155
+ fill,
156
+ shadowBlur: 4,
157
+ shadowOpacity: props.shadowOpacity,
158
+ shadowOffsetX: 2,
159
+ shadowOffsetY: 6,
160
+ shadowColor: theme.shadow,
161
+ shadowEnabled: isHovered,
162
+ perfectDrawEnabled: false,
163
+ opacity: props.opacity,
164
+ hitStrokeWidth: halfSize
165
+ }
166
+ ),
167
+ /* @__PURE__ */ jsx(ZoomInIcon, { size: 16, x: halfSize, y: halfSize })
168
+ ]
169
+ }
170
+ )
171
+ }
172
+ );
173
+ }, isEqualSimple);
174
+ CompoundZoomBtn.displayName = "CompoundZoomBtn";
@@ -0,0 +1,2 @@
1
+ export * from "./NodeLinkBtn.js";
2
+ export * from "./NodeZoomBtn.js";
@@ -9,9 +9,9 @@ export function CompoundShape({ node, theme, springs, labelOffsetX = 0 }) {
9
9
  cornerRadius: 4,
10
10
  shadowColor: theme.shadow,
11
11
  shadowBlur: node.level > 0 ? 20 : 10,
12
- shadowOpacity: node.level > 0 ? 0.35 : 0.8,
12
+ shadowOpacity: node.level > 0 ? 0.35 : 0.6,
13
13
  shadowOffsetX: 0,
14
- shadowOffsetY: 4,
14
+ shadowOffsetY: 5,
15
15
  shadowEnabled: springs.opacity.to((v) => v > 0.7),
16
16
  width: springs.width,
17
17
  height: springs.height,
@@ -53,7 +53,7 @@ function EdgeLabelBg({
53
53
  ...props,
54
54
  perfectDrawEnabled: false,
55
55
  fill: springs.labelBgColor,
56
- cornerRadius: 3,
56
+ cornerRadius: 2,
57
57
  globalCompositeOperation: "lighten",
58
58
  hitStrokeWidth: 20
59
59
  }
@@ -42,10 +42,10 @@ export const useNodeSpringsFn = (theme) => {
42
42
  export const useShadowSprings = (isHovered = false, theme, springs) => {
43
43
  const [values] = useSpring(
44
44
  {
45
- shadowBlur: isHovered ? 30 : 12,
46
- shadowOpacity: isHovered ? 0.5 : 0.35,
45
+ shadowBlur: isHovered ? 30 : 10,
46
+ shadowOpacity: isHovered ? 0.5 : 0.3,
47
47
  shadowOffsetX: 0,
48
- shadowOffsetY: isHovered ? 16 : 4,
48
+ shadowOffsetY: isHovered ? 16 : 5,
49
49
  shadowColor: theme.shadow
50
50
  },
51
51
  [isHovered, theme]
@@ -24,7 +24,7 @@ export const hoveredNodeAtom = atom(
24
24
  if (equals(_prev, _next)) {
25
25
  return false;
26
26
  }
27
- const timeout = !!_next && !!_prev ? 120 : 175;
27
+ const timeout = !!_next && !!_prev ? 50 : 120;
28
28
  if (_next != null) {
29
29
  clearTimeout(get(edgeTimeoutAtom));
30
30
  scheduleHoveredEdge(set, null, timeout);
@@ -57,9 +57,8 @@ export const hoveredEdgeAtom = atom(
57
57
  if (equals(_prev, _next)) {
58
58
  return false;
59
59
  }
60
- let timeout = 175;
60
+ const timeout = !!_next && !!_prev ? 50 : 120;
61
61
  if (_next != null) {
62
- timeout = _prev != null ? 120 : 300;
63
62
  clearTimeout(get(nodeTimeoutAtom));
64
63
  scheduleHoveredNode(set, null, timeout);
65
64
  }
@@ -12,3 +12,36 @@ export function mouseDefault(e) {
12
12
  }
13
13
  }
14
14
  export const isNumber = is(Number);
15
+ export const getBoundingRect = (elements) => {
16
+ let minX = Infinity;
17
+ let minY = Infinity;
18
+ let maxX = -Infinity;
19
+ let maxY = -Infinity;
20
+ for (const element of elements) {
21
+ if ("size" in element && "position" in element) {
22
+ minX = Math.min(minX, element.position[0]);
23
+ minY = Math.min(minY, element.position[1]);
24
+ maxX = Math.max(maxX, element.position[0] + element.size.width);
25
+ maxY = Math.max(maxY, element.position[1] + element.size.height);
26
+ continue;
27
+ }
28
+ element.points.forEach(([x, y]) => {
29
+ minX = Math.min(minX, x);
30
+ minY = Math.min(minY, y);
31
+ maxX = Math.max(maxX, x);
32
+ maxY = Math.max(maxY, y);
33
+ });
34
+ if (element.labelBBox) {
35
+ minX = Math.min(minX, element.labelBBox.x);
36
+ minY = Math.min(minY, element.labelBBox.y);
37
+ maxX = Math.max(maxX, element.labelBBox.x + element.labelBBox.width);
38
+ maxY = Math.max(maxY, element.labelBBox.y + element.labelBBox.height);
39
+ }
40
+ }
41
+ return {
42
+ x: minX,
43
+ y: minY,
44
+ width: maxX - minX,
45
+ height: maxY - minY
46
+ };
47
+ };
@@ -9,29 +9,22 @@ import { useRoute } from './router';
9
9
  const Routes = () => {
10
10
  const r = useDeferredValue(useRoute());
11
11
  const theme = r.params?.theme;
12
- let page = null;
13
- switch (r.route) {
14
- case 'view': {
15
- page = (<ViewPage key='view' viewId={r.params.viewId} viewMode={r.params.mode} showUI={r.showUI}/>);
16
- break;
12
+ const page = () => {
13
+ switch (r.route) {
14
+ case 'view':
15
+ return <ViewPage viewId={r.params.viewId} viewMode={r.params.mode} showUI={r.showUI}/>;
16
+ case 'export':
17
+ return <ExportPage viewId={r.params.viewId} padding={r.params.padding}/>;
18
+ case 'embed':
19
+ return (<EmbedPage viewId={r.params.viewId} padding={r.params.padding} transparentBg={isNil(r.params.theme)}/>);
20
+ case 'index':
21
+ return <IndexPage />;
22
+ default:
23
+ nonexhaustive(r);
17
24
  }
18
- case 'export': {
19
- page = <ExportPage key='export' viewId={r.params.viewId} padding={r.params.padding}/>;
20
- break;
21
- }
22
- case 'embed': {
23
- page = (<EmbedPage key='embed' viewId={r.params.viewId} padding={r.params.padding} transparentBg={isNil(r.params.theme)}/>);
24
- break;
25
- }
26
- case 'index': {
27
- page = <IndexPage key='index'/>;
28
- break;
29
- }
30
- default:
31
- nonexhaustive(r);
32
- }
25
+ };
33
26
  return (<Theme hasBackground={!!theme} accentColor='indigo' radius='small' appearance={theme ?? 'inherit'}>
34
- {page}
27
+ {page()}
35
28
  <Fragment key='ui'>{r.showUI && <Sidebar />}</Fragment>
36
29
  </Theme>);
37
30
  };
@@ -1,7 +1,8 @@
1
- import { Box, Code, Flex, ScrollArea } from '@radix-ui/themes';
2
- import useSWR from 'swr';
1
+ import { Box, Button, Code, ScrollArea } from '@radix-ui/themes';
2
+ import { useAsync } from '@react-hookz/web/esm';
3
3
  import { d2Source } from 'virtual:likec4/d2-sources';
4
4
  import { CopyToClipboard } from '../../../components';
5
+ import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
5
6
  const fetchFromKroki = async (d2) => {
6
7
  const res = await fetch('https://kroki.io/d2/svg', {
7
8
  method: 'POST',
@@ -21,16 +22,9 @@ const fetchFromKroki = async (d2) => {
21
22
  };
22
23
  export default function ViewAsD2({ viewId }) {
23
24
  const src = d2Source(viewId);
24
- const { data: krokiSvg } = useSWR(src, fetchFromKroki, {
25
- keepPreviousData: true,
26
- revalidateIfStale: false
27
- });
28
- return (<Flex gap='2' shrink='1' grow='1' align={'stretch'} wrap={'nowrap'} style={{
29
- overflow: 'hidden'
30
- }}>
31
- <Box py={'2'} position={'relative'} style={{
32
- overflow: 'scroll'
33
- }}>
25
+ const [krokiSvg, { execute }] = useAsync(fetchFromKroki, null);
26
+ return (<PanelGroup direction='horizontal' autoSaveId='ViewAsD2'>
27
+ <Panel minSizePixels={100}>
34
28
  <ScrollArea scrollbars='both'>
35
29
  <Box asChild display={'block'} p='2' style={{
36
30
  whiteSpace: 'pre',
@@ -40,18 +34,24 @@ export default function ViewAsD2({ viewId }) {
40
34
  {src}
41
35
  </Code>
42
36
  </Box>
37
+ <CopyToClipboard text={src}/>
38
+ </ScrollArea>
39
+ </Panel>
40
+ <PanelResizeHandle style={{
41
+ width: 10
42
+ }}/>
43
+ <Panel minSizePixels={100}>
44
+ <ScrollArea scrollbars='both'>
45
+ {krokiSvg.status !== 'success' && (<>
46
+ <Button disabled={krokiSvg.status === 'loading'} onClick={() => void execute(src)}>
47
+ {krokiSvg.status === 'loading' ? 'Loading...' : 'Render with Kroki'}
48
+ </Button>
49
+ {krokiSvg.status === 'error' && <Box>{krokiSvg.error?.message}</Box>}
50
+ </>)}
51
+ {krokiSvg.status === 'success' && (<Box grow={'1'} asChild className={'svg-container'}>
52
+ {!krokiSvg.result ? (<Box>Empty result</Box>) : (<div dangerouslySetInnerHTML={{ __html: krokiSvg.result }}></div>)}
53
+ </Box>)}
43
54
  </ScrollArea>
44
- <CopyToClipboard text={src}/>
45
- </Box>
46
- {krokiSvg && (<Box py={'2'} grow={'1'} shrink={'0'} style={{
47
- width: '50%',
48
- overflow: 'scroll'
49
- }}>
50
- <ScrollArea scrollbars='both'>
51
- <Box grow={'1'} asChild className={'svg-container'}>
52
- <div dangerouslySetInnerHTML={{ __html: krokiSvg }}></div>
53
- </Box>
54
- </ScrollArea>
55
- </Box>)}
56
- </Flex>);
55
+ </Panel>
56
+ </PanelGroup>);
57
57
  }
@@ -1,14 +1,11 @@
1
- import { Box, Code, Grid, ScrollArea } from '@radix-ui/themes';
1
+ import { Box, Code, ScrollArea } from '@radix-ui/themes';
2
2
  import { dotSource, svgSource } from 'virtual:likec4/dot-sources';
3
3
  import { CopyToClipboard } from '../../../components';
4
+ import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
4
5
  export default function ViewAsDot({ viewId }) {
5
6
  const dot = dotSource(viewId);
6
- return (<Grid
7
- //@ts-expect-error TODO: fails on columns prop due to `exactOptionalPropertyTypes: true` in tsconfig
8
- columns='2' gap='2' shrink='1' grow='1'>
9
- <Box py={'2'} position={'relative'} style={{
10
- overflow: 'scroll'
11
- }}>
7
+ return (<PanelGroup direction='horizontal' autoSaveId='viewAsDot'>
8
+ <Panel minSizePixels={100}>
12
9
  <ScrollArea scrollbars='both'>
13
10
  <Box asChild display={'block'} p='2' style={{
14
11
  whiteSpace: 'pre',
@@ -18,16 +15,23 @@ export default function ViewAsDot({ viewId }) {
18
15
  {dot}
19
16
  </Code>
20
17
  </Box>
18
+ <CopyToClipboard text={dot}/>
21
19
  </ScrollArea>
22
- <CopyToClipboard text={dot}/>
23
- </Box>
24
- <Box py={'2'} style={{
20
+ </Panel>
21
+ <PanelResizeHandle style={{
22
+ width: 10
23
+ }}/>
24
+ <Panel minSizePixels={100}>
25
+ <ScrollArea scrollbars='both'>
26
+ <Box py={'2'} style={{
25
27
  overflow: 'scroll',
26
28
  overscrollBehavior: 'none'
27
29
  }}>
28
- <Box asChild position={'relative'} className={'svg-container'}>
29
- <div dangerouslySetInnerHTML={{ __html: svgSource(viewId) }}></div>
30
- </Box>
31
- </Box>
32
- </Grid>);
30
+ <Box asChild position={'relative'} className={'svg-container'}>
31
+ <div dangerouslySetInnerHTML={{ __html: svgSource(viewId) }}></div>
32
+ </Box>
33
+ </Box>
34
+ </ScrollArea>
35
+ </Panel>
36
+ </PanelGroup>);
33
37
  }
@@ -1,8 +1,9 @@
1
- import { Box, Code, Flex, ScrollArea } from '@radix-ui/themes';
1
+ import { Box, Code, ScrollArea } from '@radix-ui/themes';
2
2
  import { useAsync } from '@react-hookz/web/esm';
3
3
  import { useEffect } from 'react';
4
4
  import { mmdSource } from 'virtual:likec4/mmd-sources';
5
5
  import { CopyToClipboard } from '../../../components';
6
+ import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
6
7
  const renderSvg = async (viewId, diagram) => {
7
8
  const { default: mermaid } = await import('https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs');
8
9
  mermaid.initialize({
@@ -17,12 +18,8 @@ export default function ViewAsMmd({ viewId }) {
17
18
  useEffect(() => {
18
19
  void execute(viewId, src);
19
20
  }, [src]);
20
- return (<Flex gap='2' shrink='1' grow='1' align={'stretch'} wrap={'nowrap'} style={{
21
- overflow: 'hidden'
22
- }}>
23
- <Box py={'2'} position={'relative'} style={{
24
- overflow: 'scroll'
25
- }}>
21
+ return (<PanelGroup direction='horizontal' autoSaveId='ViewAsD2'>
22
+ <Panel minSizePixels={100}>
26
23
  <ScrollArea scrollbars='both'>
27
24
  <Box asChild display={'block'} p='2' style={{
28
25
  whiteSpace: 'pre',
@@ -34,16 +31,16 @@ export default function ViewAsMmd({ viewId }) {
34
31
  </Box>
35
32
  <CopyToClipboard text={src}/>
36
33
  </ScrollArea>
37
- </Box>
38
- <Box py={'2'} position={'relative'} grow={'1'} shrink={'0'} style={{
39
- minWidth: '50vw',
40
- overflow: 'scroll'
41
- }}>
34
+ </Panel>
35
+ <PanelResizeHandle style={{
36
+ width: 10
37
+ }}/>
38
+ <Panel minSizePixels={100}>
42
39
  <ScrollArea scrollbars='both'>
43
40
  {mmdSvg.result && (<Box grow={'1'} asChild position={'relative'} className={'svg-container'}>
44
41
  <div dangerouslySetInnerHTML={{ __html: mmdSvg.result }}></div>
45
42
  </Box>)}
46
43
  </ScrollArea>
47
- </Box>
48
- </Flex>);
44
+ </Panel>
45
+ </PanelGroup>);
49
46
  }
@@ -4,54 +4,35 @@ import ViewAsD2 from './other-formats/ViewAsD2';
4
4
  import ViewAsDot from './other-formats/ViewAsDot';
5
5
  import ViewAsMmd from './other-formats/ViewAsMmd';
6
6
  import styles from './view-page.module.css';
7
- import { SWRConfig } from 'swr';
8
- function localStorageProvider() {
9
- // When initializing, we restore the data from `localStorage` into a map.
10
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
- const map = new Map(JSON.parse(localStorage.getItem('swr-cache') || '[]'));
12
- // Before unloading the app, we write back all the data into `localStorage`.
13
- window.addEventListener('beforeunload', () => {
14
- const appCache = JSON.stringify(Array.from(map.entries()));
15
- localStorage.setItem('swr-cache', appCache);
16
- });
17
- // We still use the map for write & read for performance.
18
- return map;
19
- }
20
7
  export default function ViewDiagramInOtherFormats({ viewId, viewMode }) {
21
- return (<SWRConfig value={{
22
- keepPreviousData: true,
23
- errorRetryCount: 5,
24
- provider: localStorageProvider
25
- }}>
26
- <Flex asChild position={'fixed'} inset={'0'} pt={'8'} pl={'8'} pr={'2'} align={'stretch'} direction={'column'}>
27
- <Tabs.Root value={viewMode} onValueChange={mode => mode !== viewMode && updateSearchParams({ mode: mode })}>
28
- <Box asChild shrink={'0'} grow={'0'}>
29
- <Tabs.List>
30
- <Tabs.Trigger value='react'>React</Tabs.Trigger>
31
- <Tabs.Trigger value='dot'>Graphviz</Tabs.Trigger>
32
- <Tabs.Trigger value='mmd'>Mermaid</Tabs.Trigger>
33
- <Tabs.Trigger value='d2'>D2</Tabs.Trigger>
34
- </Tabs.List>
35
- </Box>
8
+ return (<Flex asChild position={'fixed'} inset={'0'} pt={'8'} pl={'8'} pr={'2'} align={'stretch'} direction={'column'}>
9
+ <Tabs.Root value={viewMode} onValueChange={mode => mode !== viewMode && updateSearchParams({ mode: mode })}>
10
+ <Box asChild shrink={'0'} grow={'0'}>
11
+ <Tabs.List>
12
+ <Tabs.Trigger value='react'>React</Tabs.Trigger>
13
+ <Tabs.Trigger value='dot'>Graphviz</Tabs.Trigger>
14
+ <Tabs.Trigger value='mmd'>Mermaid</Tabs.Trigger>
15
+ <Tabs.Trigger value='d2'>D2</Tabs.Trigger>
16
+ </Tabs.List>
17
+ </Box>
36
18
 
37
- <Box p='2' className={styles.otherFormats} position={'relative'}>
38
- <Tabs.Content value='react'>{''}</Tabs.Content>
19
+ <Box p='2' className={styles.otherFormats} position={'relative'}>
20
+ <Tabs.Content value='react'>{''}</Tabs.Content>
39
21
 
40
- <Tabs.Content value='dot'>
41
- <ViewAsDot viewId={viewId}/>
42
- </Tabs.Content>
22
+ <Tabs.Content value='dot'>
23
+ <ViewAsDot viewId={viewId}/>
24
+ </Tabs.Content>
43
25
 
44
- <Tabs.Content value='mmd'>
45
- <ViewAsMmd viewId={viewId}/>
46
- </Tabs.Content>
26
+ <Tabs.Content value='mmd'>
27
+ <ViewAsMmd viewId={viewId}/>
28
+ </Tabs.Content>
47
29
 
48
- <Tabs.Content value='d2'>
49
- <ViewAsD2 viewId={viewId}/>
50
- </Tabs.Content>
51
- </Box>
52
- </Tabs.Root>
53
- </Flex>
54
- </SWRConfig>);
30
+ <Tabs.Content value='d2'>
31
+ <ViewAsD2 viewId={viewId}/>
32
+ </Tabs.Content>
33
+ </Box>
34
+ </Tabs.Root>
35
+ </Flex>);
55
36
  // switch (viewMode) {
56
37
  // case 'dot':
57
38
  // return <ViewAsDot viewId={viewId} />
@@ -68,7 +68,7 @@
68
68
  display: flex;
69
69
  align-items: stretch;
70
70
  flex: 1 1 auto;
71
- overflow: scroll;
71
+ overflow: auto;
72
72
 
73
73
  & > :global(.rt-TabsContent) {
74
74
  display: flex;