Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,6 @@ custom-shell.nix

# Benchmark results.
editor/benchmark

*storybook.log
storybook-static
14 changes: 14 additions & 0 deletions nexus-builder/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:react-hooks/recommended', 'plugin:prettier/recommended', 'plugin:storybook/recommended'],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}
24 changes: 24 additions & 0 deletions nexus-builder/.storybook/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { StorybookConfig } from '@storybook/react-vite';
import tsconfigPaths from 'vite-tsconfig-paths';

const config: StorybookConfig = {
"stories": [
"../packages/**/*.mdx",
"../packages/**/*.stories.@(js|jsx|mjs|ts|tsx)"
],
"addons": [
"@chromatic-com/storybook",
"@storybook/addon-vitest",
"@storybook/addon-a11y",
"@storybook/addon-docs",
"@storybook/addon-onboarding"
],
"framework": "@storybook/react-vite",
async viteFinal(config) {
if (config.plugins) {
config.plugins.push(tsconfigPaths());
}
return config;
},
};
export default config;
21 changes: 21 additions & 0 deletions nexus-builder/.storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { Preview } from '@storybook/react-vite';

const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},

a11y: {
// 'todo' - show a11y violations in the test UI only
// 'error' - fail CI on a11y violations
// 'off' - skip a11y checks entirely
test: 'todo',
},
},
};

export default preview;
7 changes: 7 additions & 0 deletions nexus-builder/.storybook/vitest.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview";
import { setProjectAnnotations } from '@storybook/react-vite';
import * as projectAnnotations from './preview';

// This is an important step to apply the right configuration when testing your stories.
// More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations
setProjectAnnotations([a11yAddonAnnotations, projectAnnotations]);
42 changes: 42 additions & 0 deletions nexus-builder/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "nexus-builder",
"private": true,
"version": "1.0.0",
"description": "A monorepo for Nexus Builder.",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@chromatic-com/storybook": "^4.1.3",
"@storybook/addon-a11y": "^10.1.4",
"@storybook/addon-docs": "^10.1.4",
"@storybook/addon-onboarding": "^10.1.4",
"@storybook/addon-vitest": "^10.1.4",
"@storybook/react-vite": "^10.1.4",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@typescript-eslint/eslint-plugin": "^8.48.1",
"@typescript-eslint/parser": "^8.48.1",
"@vitest/browser-playwright": "^4.0.15",
"@vitest/coverage-v8": "^4.0.15",
"eslint": "^9.39.1",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-storybook": "^10.1.4",
"playwright": "^1.57.0",
"prettier": "^3.7.4",
"react": "^19.2.1",
"react-dom": "^19.2.1",
"storybook": "^10.1.4",
"typescript": "^5.9.3",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^4.0.15"
}
}
20 changes: 20 additions & 0 deletions nexus-builder/packages/core/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "@builder/core",
"version": "1.0.0",
"private": true,
"main": "./src/index.ts",
"types": "./src/index.ts",
"scripts": {
"lint": "eslint . --max-warnings 0",
"test": "echo \"Error: no test specified\"",
"build": "tsc"
},
"dependencies": {
"react": "^19.2.1"
},
"devDependencies": {
"@types/react": "^19.2.7",
"eslint": "^9.39.1",
"typescript": "^5.9.3"
}
}
71 changes: 71 additions & 0 deletions nexus-builder/packages/core/src/components/common/actions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
export type LeftMenuPanel =
| 'filebrowser'
| 'dependencylist'
| 'genericExternalResources'
| 'googleFontsResources'
| 'insertmenu'
| 'projectsettings'
| 'githuboptions'

export type CenterPanel = 'canvas' | 'misccodeeditor'

export type InspectorPanel = 'inspector'

export type CodeEditorPanel = 'codeEditor'

export type NavigatorPanel = 'navigator'

export type EditorPanel =
| LeftMenuPanel
| CenterPanel
| CodeEditorPanel
| InspectorPanel
| NavigatorPanel

export type EditorPane = 'leftmenu' | 'center' | 'inspector' | 'rightmenu'

export function paneForPanel(panel: EditorPanel | null): EditorPane | null {
switch (panel) {
case null:
return null
case 'filebrowser':
return 'leftmenu'
case 'dependencylist':
return 'leftmenu'
case 'genericExternalResources':
return 'leftmenu'
case 'googleFontsResources':
return 'leftmenu'
case 'githuboptions':
return 'leftmenu'
case 'insertmenu':
return 'rightmenu'
case 'projectsettings':
return 'leftmenu'
case 'navigator':
return 'center'
case 'canvas':
return 'center'
case 'misccodeeditor':
return 'center'
case 'inspector':
return 'rightmenu'
case 'codeEditor':
return 'center'
default:
const _exhaustiveCheck: never = panel
throw new Error(`Unhandled panel ${panel}`)
}
}

export interface SetFocus {
action: 'SET_FOCUS'
focusedPanel: EditorPanel | null
}

export function setFocus(panel: EditorPanel | null): SetFocus {
return {
action: 'SET_FOCUS',
focusedPanel: panel,
}
}
101 changes: 101 additions & 0 deletions nexus-builder/packages/core/src/components/common/ellipsis-spinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/** @jsxRuntime classic */
/** @jsx jsx */
import { jsx, css, keyframes } from '@emotion/react'
import React from 'react'

const ellipsis1Anim = keyframes`{
0% {
transform: scale(0);
}
100% {
transform: scale(1);
}
}`

const ellipsis2Anim = keyframes`{
0% {
transform: translate(0, 0);
}
100% {
transform: translate(19px, 0);
}
}`

const ellipsis3Anim = ellipsis2Anim

const ellipsis4Anim = keyframes`{
0% {
transform: scale(1);
}
100% {
transform: scale(0);
}
}`

// Don't even waste your time trying to type the `css` prop because it will haunt you forever
const Ellipsis = (props: any) => (
<div
css={{
position: 'absolute',
top: '27px',
width: '11px',
height: '11px',
borderRadius: '50%',
background: '#4400FF',
animationTimingFunction: 'cubic-bezier(0, 1, 1, 0)',
}}
{...props}
/>
)

export const EllipsisSpinner = React.memo(() => {
return (
<div
id='spinner'
css={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
width: '100%',
}}
>
<div
id='lds-ellipsis'
css={{
position: 'relative',
width: '64px',
height: '64px',
}}
>
<Ellipsis
css={{
left: '6px',
animation: `${ellipsis1Anim} 0.6s infinite`,
}}
></Ellipsis>

<Ellipsis
css={{
left: '6px',
animation: `${ellipsis2Anim} 0.6s infinite`,
}}
></Ellipsis>

<Ellipsis
css={{
left: '26px',
animation: `${ellipsis3Anim} 0.6s infinite`,
}}
></Ellipsis>

<Ellipsis
css={{
left: '45px',
animation: `${ellipsis4Anim} 0.6s infinite`,
}}
></Ellipsis>
</div>
</div>
)
})
29 changes: 29 additions & 0 deletions nexus-builder/packages/core/src/components/common/notice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type React from 'react'
import { v4 as UUID } from 'uuid'

/**
* - _Error_: High visibility
* - _Warning_: High visisibility
* - _Primary_: Medium high visibility. Use for CTAs, nudges
* - _Success_: Medium visibility. Use for async or higher-failure-probability positive outcomes
* - _Notice_: Medium visibility. Use for synchronous info, confirmation, or to stand out from background
* - _Info_: Low visibility. Use to display information that can blend into background
*
* */

export type NoticeLevel = 'ERROR' | 'WARNING' | 'PRIMARY' | 'SUCCESS' | 'NOTICE' | 'INFO'
export interface Notice {
message: React.ReactChild
level: NoticeLevel
persistent: boolean
id: string
}

export function notice(
message: React.ReactChild,
level: NoticeLevel = 'INFO',
persistent: boolean = false,
id: string = UUID(),
): Notice {
return { message: message, persistent: persistent, level: level, id: id }
}
Loading