Skip to content

Commit b277dbe

Browse files
authored
Better JupyterLab theme (#354)
* fix: theme * fix: build
1 parent 3d6e124 commit b277dbe

File tree

7 files changed

+161
-106
lines changed

7 files changed

+161
-106
lines changed

packages/react/src/components/notebook/BaseNotebook.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ import { Signal } from '@lumino/signaling';
6262
import { Widget } from '@lumino/widgets';
6363
import { Box } from '@primer/react';
6464
import { Banner } from '@primer/react/experimental';
65-
import { useCallback, useEffect, useMemo, useState } from 'react';
65+
import { useEffect, useMemo, useState } from 'react';
6666
import { WebsocketProvider } from 'y-websocket';
6767
import {
6868
COLLABORATION_ROOM_URL_PATH,

packages/react/src/examples/Theme.tsx renamed to packages/react/src/examples/JupyterLabTheme.tsx

Lines changed: 128 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@
44
* MIT License
55
*/
66

7-
import { useState } from 'react';
7+
import { useState, useEffect, useCallback } from 'react';
88
import { createRoot } from 'react-dom/client';
99
import {
1010
Box, Button, Heading, Text, Link, PageLayout, PageHeader, RadioGroup, Radio, FormControl, RelativeTime,
1111
TextInput, TextInputWithTokens, Timeline, Octicon, ToggleSwitch, SegmentedControl, Label, LabelGroup,
12+
// theme,
1213
} from '@primer/react';
13-
import { DataTable, Table } from '@primer/react/experimental';
14+
import { DataTable, Table, SkeletonBox } from '@primer/react/experimental';
1415
import { GitCommitIcon } from '@primer/octicons-react';
15-
import {SkeletonBox} from '@primer/react/experimental'
16-
import { JupyterReactTheme } from '../theme/JupyterReactTheme';
16+
import { Jupyter } from '../jupyter/Jupyter';
17+
import { Colormode } from '../theme/JupyterLabColormode';
18+
import { jupyterLabTheme as theme } from '../theme/themes';
1719

1820
const mockTokens = [
1921
{text: 'zero', id: 0},
@@ -128,13 +130,28 @@ const data: Array<Repo> = [
128130
},
129131
]
130132

131-
const Theme = () => {
133+
const JupyterLabTheme = () => {
134+
const [colormode, setColormode] = useState<Colormode>('light');
132135
const [tokens, setTokens] = useState([...mockTokens].slice(0, 3))
136+
const [isOn, setIsOn] = useState(false);
137+
useEffect(() => {
138+
if (isOn) {
139+
setColormode('dark');
140+
} else {
141+
setColormode('light');
142+
}
143+
}, [isOn]);
144+
const onClick = useCallback(() => {
145+
setIsOn(!isOn);
146+
}, [isOn]);
147+
const handleSwitchChange = useCallback((on: boolean) => {
148+
setIsOn(on);
149+
}, []);
133150
const onTokenRemove: (tokenId: string | number) => void = (tokenId) => {
134151
setTokens(tokens.filter((token) => token.id !== tokenId))
135152
}
136153
return (
137-
<JupyterReactTheme>
154+
<Jupyter theme={theme} colormode={colormode} startDefaultKernel>
138155
<PageLayout containerWidth="full" padding="normal">
139156
<PageLayout.Header>
140157
<PageHeader>
@@ -151,87 +168,112 @@ const Theme = () => {
151168
Useful examples...
152169
</Text>
153170
</PageHeader.Description>
171+
<PageHeader.Actions>
172+
<Text
173+
fontSize={2}
174+
fontWeight="bold"
175+
id="switch-label"
176+
display="block"
177+
mb={1}
178+
>
179+
{ colormode === 'light' ? 'Light' : 'Dark' } Mode
180+
</Text>
181+
<ToggleSwitch
182+
size="small"
183+
onClick={onClick}
184+
onChange={handleSwitchChange}
185+
checked={isOn}
186+
statusLabelPosition="end"
187+
aria-labelledby="switch-label"
188+
/>
189+
</PageHeader.Actions>
154190
</PageHeader>
155191
</PageLayout.Header>
156192
<PageLayout.Content>
157-
<Heading>Heading</Heading>
158-
<Box>
159-
<Text as="h1">Heading 1</Text>
160-
<Text as="h2">Heading 2</Text>
161-
<Text as="h3">Heading 3</Text>
162-
<Text>This is a text.</Text>
163-
<br/>
164-
<Link href="https://datalayer.io" target="_blank">This is a link.</Link>
165-
<br/>
166-
<Text as="h3"><Link href="https://datalayer.io" target="_blank">This is a heading 3 link</Link></Text>
167-
</Box>
168-
<Box>
169-
<Button variant="default">Default</Button>
170-
<Button variant="primary">Primary</Button>
171-
<Button variant="invisible">Invisible</Button>
172-
<Button variant="danger">Danger</Button>
173-
</Box>
174-
<Box as="form">
175-
<RadioGroup name="defaultRadioGroup">
176-
<RadioGroup.Label>Choices</RadioGroup.Label>
177-
<FormControl>
178-
<Radio value="one" defaultChecked />
179-
<FormControl.Label>Choice one</FormControl.Label>
180-
</FormControl>
181-
<FormControl>
182-
<Radio value="two" />
183-
<FormControl.Label>Choice two</FormControl.Label>
184-
</FormControl>
185-
<FormControl>
186-
<Radio value="three" />
187-
<FormControl.Label>Choice three</FormControl.Label>
188-
</FormControl>
189-
</RadioGroup>
190-
</Box>
191-
<Box as="form">
192-
<FormControl>
193-
<FormControl.Label>Default label</FormControl.Label>
194-
<FormControl.Caption>This is a caption</FormControl.Caption>
195-
<TextInput />
196-
</FormControl>
197-
</Box>
198-
<Box>
199-
<FormControl>
200-
<FormControl.Label>Default label</FormControl.Label>
201-
<TextInputWithTokens tokens={tokens} onTokenRemove={onTokenRemove} />
202-
</FormControl>
203-
</Box>
204-
<Box>
205-
<SkeletonBox height="4rem" />
206-
</Box>
207-
<Box>
208-
<Text id="toggle" fontWeight="bold" fontSize={1}>
209-
Toggle label
210-
</Text>
211-
<ToggleSwitch aria-labelledby="toggle" />
212-
</Box>
213-
<Box>
214-
<SegmentedControl aria-label="File view">
215-
<SegmentedControl.Button defaultSelected>Preview</SegmentedControl.Button>
216-
<SegmentedControl.Button>Raw</SegmentedControl.Button>
217-
<SegmentedControl.Button>Blame</SegmentedControl.Button>
218-
</SegmentedControl>
219-
</Box>
220-
<Box>
221-
<Timeline>
222-
<Timeline.Item>
223-
<Timeline.Badge>
224-
<Octicon icon={GitCommitIcon} aria-label="Commit" />
225-
</Timeline.Badge>
226-
<Timeline.Body>This is a message</Timeline.Body>
227-
</Timeline.Item>
228-
<Timeline.Item>
229-
<Timeline.Badge>
230-
<Octicon icon={GitCommitIcon} aria-label="Commit" />
231-
</Timeline.Badge>
232-
<Timeline.Body>This is a message</Timeline.Body>
233-
</Timeline.Item>
234-
</Timeline>
193+
<Box display="flex">
194+
<Box sx={{ width: "100%" }}>
195+
<Heading>Heading</Heading>
196+
<Box>
197+
<Text as="h1">Heading 1</Text>
198+
<Text as="h2">Heading 2</Text>
199+
<Text as="h3">Heading 3</Text>
200+
<Text>This is a text.</Text>
201+
<br/>
202+
<Link href="https://datalayer.io" target="_blank">This is a link.</Link>
203+
<br/>
204+
<Text as="h3"><Link href="https://datalayer.io" target="_blank">This is a heading 3 link</Link></Text>
205+
</Box>
206+
<Box>
207+
<Button variant="default">Default</Button>
208+
<Button variant="primary">Primary</Button>
209+
<Button variant="invisible">Invisible</Button>
210+
<Button variant="danger">Danger</Button>
211+
</Box>
212+
</Box>
213+
<Box sx={{ width: "100%" }}>
214+
<Box as="form">
215+
<RadioGroup name="defaultRadioGroup">
216+
<RadioGroup.Label>Choices</RadioGroup.Label>
217+
<FormControl>
218+
<Radio value="one" defaultChecked />
219+
<FormControl.Label>Choice one</FormControl.Label>
220+
</FormControl>
221+
<FormControl>
222+
<Radio value="two" />
223+
<FormControl.Label>Choice two</FormControl.Label>
224+
</FormControl>
225+
<FormControl>
226+
<Radio value="three" />
227+
<FormControl.Label>Choice three</FormControl.Label>
228+
</FormControl>
229+
</RadioGroup>
230+
</Box>
231+
<Box as="form">
232+
<FormControl>
233+
<FormControl.Label>Default label</FormControl.Label>
234+
<FormControl.Caption>This is a caption</FormControl.Caption>
235+
<TextInput />
236+
</FormControl>
237+
</Box>
238+
<Box>
239+
<FormControl>
240+
<FormControl.Label>Default label</FormControl.Label>
241+
<TextInputWithTokens tokens={tokens} onTokenRemove={onTokenRemove} />
242+
</FormControl>
243+
</Box>
244+
<Box>
245+
<SkeletonBox height="4rem" />
246+
</Box>
247+
<Box>
248+
<Text id="toggle" fontWeight="bold" fontSize={1}>
249+
Toggle label
250+
</Text>
251+
<ToggleSwitch aria-labelledby="toggle" />
252+
</Box>
253+
<Box>
254+
<SegmentedControl aria-label="File view">
255+
<SegmentedControl.Button defaultSelected>Preview</SegmentedControl.Button>
256+
<SegmentedControl.Button>Raw</SegmentedControl.Button>
257+
<SegmentedControl.Button>Blame</SegmentedControl.Button>
258+
</SegmentedControl>
259+
</Box>
260+
<Box>
261+
<Timeline>
262+
<Timeline.Item>
263+
<Timeline.Badge>
264+
<Octicon icon={GitCommitIcon} aria-label="Commit" />
265+
</Timeline.Badge>
266+
<Timeline.Body>This is a message</Timeline.Body>
267+
</Timeline.Item>
268+
<Timeline.Item>
269+
<Timeline.Badge>
270+
<Octicon icon={GitCommitIcon} aria-label="Commit" />
271+
</Timeline.Badge>
272+
<Timeline.Body>This is a message</Timeline.Body>
273+
</Timeline.Item>
274+
</Timeline>
275+
</Box>
276+
</Box>
235277
</Box>
236278
<Box>
237279
<Table.Container>
@@ -310,12 +352,12 @@ const Theme = () => {
310352
</Box>
311353
</PageLayout.Content>
312354
</PageLayout>
313-
</JupyterReactTheme>
355+
</Jupyter>
314356
);
315357
}
316358

317359
const div = document.createElement('div');
318360
document.body.appendChild(div);
319361
const root = createRoot(div);
320362

321-
root.render(<Theme />);
363+
root.render(<JupyterLabTheme />);

packages/react/src/examples/NotebookColormode.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
* MIT License
55
*/
66

7-
import { INotebookContent } from '@jupyterlab/nbformat';
8-
import { Text, ToggleSwitch } from '@primer/react';
97
import { useCallback, useEffect, useMemo, useState } from 'react';
108
import { createRoot } from 'react-dom/client';
9+
import { INotebookContent } from '@jupyterlab/nbformat';
10+
import { Text, ToggleSwitch } from '@primer/react';
1111
import { CellSidebarExtension } from '../components';
1212
import { Notebook } from '../components/notebook/Notebook';
1313
import { Jupyter } from '../jupyter/Jupyter';
@@ -18,7 +18,7 @@ import nbformat from './notebooks/NotebookExample1.ipynb.json';
1818
const NotebookColormode = () => {
1919
const [colormode, setColormode] = useState<Colormode>('light');
2020
const [isOn, setIsOn] = useState(false);
21-
const extensions = useMemo(() => [new CellSidebarExtension()], []);
21+
const extensions = useMemo(() => [new CellSidebarExtension()], []);
2222
useEffect(() => {
2323
if (isOn) {
2424
setColormode('dark');
@@ -34,7 +34,7 @@ const NotebookColormode = () => {
3434
}, []);
3535
return (
3636
<>
37-
<Jupyter colormode={colormode}>
37+
<Jupyter colormode={colormode} startDefaultKernel>
3838
<Text
3939
fontSize={2}
4040
fontWeight="bold"

packages/react/src/jupyter/Jupyter.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
* MIT License
55
*/
66

7-
import { BaseStyles, Box, ThemeProvider, theme as primerTheme } from '@primer/react';
7+
import { BaseStyles, Box, ThemeProvider } from '@primer/react';
88
import { Theme } from '@primer/react/lib/ThemeProvider';
99
import { useMemo } from 'react';
1010
import { ErrorBoundary } from 'react-error-boundary';
1111
import { loadJupyterConfig } from './JupyterConfig';
1212
import { JupyterContextProps, JupyterContextProvider } from './JupyterContext';
13-
import { Colormode, JupyterLabCss } from '../theme';
13+
import { jupyterLabTheme, Colormode, JupyterLabCss } from '../theme';
1414

1515
/**
1616
* Definition of the properties that can be passed
@@ -49,19 +49,19 @@ export const Jupyter = (props: JupyterProps) => {
4949
const {
5050
children,
5151
collaborative,
52-
colormode = 'light',
52+
colormode,
5353
defaultKernelName,
5454
disableCssLoading = false,
55-
initCode = '',
55+
initCode,
5656
jupyterServerUrl,
5757
jupyterServerToken,
5858
lite,
5959
serverless,
6060
serviceManager,
6161
startDefaultKernel,
6262
skeleton,
63-
terminals = false,
64-
theme = primerTheme,
63+
terminals,
64+
theme,
6565
useRunningKernelId,
6666
useRunningKernelIndex,
6767
} = props;
@@ -111,4 +111,11 @@ export const Jupyter = (props: JupyterProps) => {
111111
);
112112
};
113113

114+
Jupyter.defaultProps = {
115+
colormode: 'light',
116+
disableCssLoading: false,
117+
terminals: false,
118+
theme: jupyterLabTheme,
119+
}
120+
114121
export default Jupyter;

packages/react/src/theme/JupyterReactTheme.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,13 @@ export const JupyterReactTheme = (props: React.PropsWithChildren<IJupyterLabThem
2525
dayScheme="light"
2626
nightScheme="dark"
2727
>
28-
<BaseStyles>
28+
<BaseStyles
29+
style={{
30+
backgroundColor: 'var(--bgColor-default)',
31+
color: 'var(--fgColor-default)',
32+
fontSize: 'var(--text-body-size-medium)',
33+
}}
34+
>
2935
{children}
3036
</BaseStyles>
3137
</ThemeProvider>

0 commit comments

Comments
 (0)