Skip to content

Commit

Permalink
feat(ui-devtools): initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
arjunvegda committed Feb 3, 2023
1 parent 7816cce commit 7c38133
Show file tree
Hide file tree
Showing 67 changed files with 9,562 additions and 206 deletions.
27 changes: 23 additions & 4 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module.exports = {
'plugin:react-hooks/recommended',
'plugin:import/recommended',
'plugin:import/typescript',
'plugin:storybook/recommended',
],
plugins: [
'@typescript-eslint',
Expand Down Expand Up @@ -37,22 +38,40 @@ module.exports = {
'prefer-const': 'error',
curly: ['warn', 'multi-line', 'consistent'],
'no-console': 'off',
'import/no-unresolved': ['error', { commonjs: true, amd: true }],
'import/no-unresolved': [
'error',
{
commonjs: true,
amd: true,
},
],
'import/export': 'error',
'@typescript-eslint/no-duplicate-imports': ['error'],
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unused-vars': [
'warn',
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'jest/consistent-test-it': ['error', { fn: 'it', withinDescribe: 'it' }],
'jest/consistent-test-it': [
'error',
{
fn: 'it',
withinDescribe: 'it',
},
],
'import/order': [
'error',
{
alphabetize: { order: 'asc', caseInsensitive: true },
alphabetize: {
order: 'asc',
caseInsensitive: true,
},
groups: [
'builtin',
'external',
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
18.13.0
16.13.2
12 changes: 12 additions & 0 deletions .storybook/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module.exports = {
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
],
framework: '@storybook/react',
docs: {
autodocs: true,
},
};
9 changes: 9 additions & 0 deletions .storybook/preview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
};
3 changes: 3 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"]
}
17 changes: 17 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"editor.formatOnSave": true,
"[javascript,typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"files.autoSave": "onFocusChange",
"editor.smoothScrolling": true,
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}
26 changes: 24 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@
"release": "release-it",
"release:next": "yarn run release --preRelease=next",
"release:minor": "yarn run release minor",
"release:patch": "yarn run release patch"
"release:patch": "yarn run release patch",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
},
"repository": {
"type": "git",
Expand Down Expand Up @@ -83,9 +85,18 @@
"registry": "https://registry.npmjs.org"
},
"devDependencies": {
"@babel/core": "^7.20.12",
"@commitlint/cli": "^17.4.0",
"@commitlint/config-conventional": "^17.4.0",
"@release-it/conventional-changelog": "^5.1.1",
"@storybook/addon-actions": "^6.5.15",
"@storybook/addon-essentials": "^6.5.15",
"@storybook/addon-interactions": "^6.5.15",
"@storybook/addon-links": "^6.5.15",
"@storybook/builder-webpack4": "^6.5.15",
"@storybook/manager-webpack4": "^6.5.15",
"@storybook/react": "^6.5.15",
"@storybook/testing-library": "^0.0.13",
"@swc/core": "^1.3.25",
"@swc/jest": "^0.2.24",
"@testing-library/react": "^13.4.0",
Expand All @@ -95,6 +106,7 @@
"@types/react-dom": "^18.0.10",
"@typescript-eslint/eslint-plugin": "^5.48.0",
"@typescript-eslint/parser": "^5.48.0",
"babel-loader": "^8.3.0",
"esbuild-plugin-replace": "^1.3.0",
"eslint": "^8.31.0",
"eslint-config-prettier": "^8.6.0",
Expand All @@ -105,6 +117,7 @@
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.31.11",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-storybook": "^0.6.10",
"husky": "^8.0.0",
"jest": "^29.3.1",
"jest-environment-jsdom": "^29.3.1",
Expand All @@ -126,6 +139,15 @@
"react": ">=17.0.0"
},
"dependencies": {
"@redux-devtools/extension": "^3.2.3"
"@emotion/react": "^11.10.5",
"@fontsource/inter": "^4.5.15",
"@fontsource/jetbrains-mono": "^4.5.12",
"@mantine/core": "^5.10.1",
"@mantine/hooks": "^5.10.1",
"@mantine/prism": "^5.10.1",
"@redux-devtools/extension": "^3.2.3",
"@tabler/icons": "^1.119.0",
"react-resizable-panels": "^0.0.35",
"superjson": "^1.12.2"
}
}
84 changes: 84 additions & 0 deletions src/DevTools/DevTools.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { StrictMode, useEffect, useState } from 'react';
import {
ColorScheme,
ColorSchemeProvider,
MantineProvider,
MantineThemeOverride,
} from '@mantine/core';
import {
DevToolsOptions,
useSetDevToolsOptions,
} from './atoms/devtools-options';
import { Extension, ExtensionProps } from './Extension';
import '@fontsource/inter/latin-400.css';
import '@fontsource/inter/latin-500.css';
import '@fontsource/inter/latin-600.css';
import '@fontsource/inter/latin-700.css';
import '@fontsource/jetbrains-mono/latin-400.css';
import '@fontsource/jetbrains-mono/latin-600.css';
import '@fontsource/jetbrains-mono/latin-700.css';
import {
InternalDevToolsContext,
internalJotaiStore,
} from './internal-jotai-store';

const theme: MantineThemeOverride = {
primaryColor: 'dark',
activeStyles: { transform: 'scale(1)' },
fontFamily:
'Inter, JetBrains Mono, -apple-system, BlinkMacSystemFont, Segoe, sans-serif',
fontFamilyMonospace:
'JetBrains Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace',
headings: {
fontFamily:
'Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji',
},
};

export type DevToolsProps = ExtensionProps & {
theme?: 'dark' | 'light';
options?: DevToolsOptions;
};

export const DevTools = ({
store,
isInitialOpen,
theme: userColorScheme = 'light',
options,
}: DevToolsProps): JSX.Element => {
const [colorScheme, setColorScheme] = useState<ColorScheme>(userColorScheme);
const setDevToolsOptions = useSetDevToolsOptions();

const toggleColorScheme = (value?: ColorScheme) =>
setColorScheme(value || (colorScheme === 'dark' ? 'light' : 'dark'));

useEffect(() => {
setColorScheme(userColorScheme);
}, [userColorScheme]);

useEffect(() => {
// Should we consider caching these options in the future instead of allowing users to change these?
setDevToolsOptions(options);
}, [setDevToolsOptions, options]);

const theme_ = {
...theme,
colorScheme,
};

return (
<StrictMode>
<ColorSchemeProvider
colorScheme={colorScheme}
toggleColorScheme={toggleColorScheme}
>
{/* FIXME remove `withGlobalStyles` - this changes themes outside of this component and may impact userland */}
<MantineProvider withGlobalStyles withNormalizeCSS theme={theme_}>
<InternalDevToolsContext.Provider value={internalJotaiStore}>
<Extension store={store} isInitialOpen={isInitialOpen} />
</InternalDevToolsContext.Provider>
</MantineProvider>
</ColorSchemeProvider>
</StrictMode>
);
};
73 changes: 73 additions & 0 deletions src/DevTools/Extension/Extension.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { useEffect } from 'react';
import { ActionIcon, Sx } from '@mantine/core';
import { useAtom, useSetAtom } from 'jotai/react';
import { Store } from 'src/types';
import { isShellOpenAtom } from '../atoms/is-shell-open-atom';
import {
devtoolsJotaiStoreOptions,
useDevtoolsJotaiStoreOptions,
} from '../internal-jotai-store';
import logo from './assets/jotai-mascot.png';
import { Shell } from './components/Shell';

const shellTriggerButtonStyles: Sx = () => ({
position: 'fixed',
left: 10,
bottom: 10,
borderRadius: '50%',
padding: '2rem',
zIndex: 99999,
img: {
height: '2rem',
},
});

const ShellTriggerButton = () => {
const setIsShellOpen = useSetAtom(isShellOpenAtom, devtoolsJotaiStoreOptions);

return (
<ActionIcon
variant="filled"
// TODO make this themable
color="dark"
onClick={() => setIsShellOpen(true)}
sx={shellTriggerButtonStyles}
>
<img src={logo} alt="Jotai Mascot" />
</ActionIcon>
);
};

export type ExtensionProps = {
// false by default
isInitialOpen?: boolean;
store?: Store;
// TODO Allow user to pass theme
// theme?: 'dark' | 'light';
};

export const Extension = ({
isInitialOpen = false,
store,
}: ExtensionProps): JSX.Element => {
const [isShellOpen, setIsShellOpen] = useAtom(
isShellOpenAtom,
useDevtoolsJotaiStoreOptions(),
);

useEffect(() => {
// Avoid setting the initial value if the value is found in the local storage
if (typeof isShellOpen !== 'boolean') {
setIsShellOpen(isInitialOpen);
}
// Intentionally disabled
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return (
<>
{!isShellOpen && <ShellTriggerButton />}
{isShellOpen ? <Shell store={store} /> : null}
</>
);
};
Binary file added src/DevTools/Extension/assets/jotai-mascot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
61 changes: 61 additions & 0 deletions src/DevTools/Extension/components/Shell/Shell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { useEffect, useRef } from 'react';
import { Tabs } from '@mantine/core';
import { useAtomValue } from 'jotai/react';
import { Store } from 'src/types';
import { shellStylesAtom } from '../../../atoms/shell-styles';
import { useSetCustomStore } from '../../../atoms/user-custom-store';
import { TabKeys, shellStyleDefaults } from '../../../constants';
import { devtoolsJotaiStoreOptions } from '../../../internal-jotai-store';
import { AtomViewer } from './components/AtomViewer';
import { Header } from './components/Header';
import { ShellResizeBar } from './components/ShellResizeBar';
import { shellStyles } from './styles';

type ShellProps = {
store?: Store;
};

export const Shell = ({ store }: ShellProps) => {
const setUserStore = useSetCustomStore();

useEffect(() => {
setUserStore(store);
}, [setUserStore, store]);

const shellRef = useRef<HTMLDivElement>(null);
const { height } = useAtomValue(shellStylesAtom, devtoolsJotaiStoreOptions);

useEffect(() => {
// Allocating more height at the end of the content allows users to scroll down fully
// FIXME should we handle a use-case where there is padding set around `body`?
document.body.style.paddingBottom = height + 'px';

return () => {
document.body.style.paddingBottom = `0px`;
};
}, [height]);

return (
<Tabs
keepMounted={false}
variant="default"
defaultValue={TabKeys.AtomViewer}
m={10}
sx={shellStyles}
h={height}
mah={shellStyleDefaults.maxHeight}
ref={shellRef}
>
<ShellResizeBar shellRef={shellRef} />
<Header />

<Tabs.Panel
value={TabKeys.AtomViewer}
h="100%"
sx={{ overflow: 'hidden' }}
>
<AtomViewer />
</Tabs.Panel>
</Tabs>
);
};
Loading

0 comments on commit 7c38133

Please sign in to comment.