├── .env ├── .eslintrc.json ├── .github ├── FUNDING.yml └── workflows │ └── check.yml ├── .gitignore ├── .prettierrc ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── empty-module.js ├── jest.config.js ├── jest.setup.js ├── next-env.d.ts ├── next.config.js ├── package.json ├── public ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-touch-icon.png ├── browserconfig.xml ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── images │ ├── favicon-512.png │ ├── github-banner.png │ ├── og-graph-color.png │ ├── og-graph-white.png │ └── screenshot.png ├── index.html ├── logo192.png ├── logo512.png ├── mstile-150x150.png ├── robots.txt └── site.webmanifest ├── scripts ├── format.js ├── generate.js ├── ref.json └── types.json ├── splash.png ├── src ├── components │ ├── CodePanel.tsx │ ├── Header.tsx │ ├── Metadata.tsx │ ├── editor │ │ ├── ComponentPreview.test.tsx │ │ ├── ComponentPreview.tsx │ │ ├── Editor.tsx │ │ ├── PreviewContainer.tsx │ │ ├── WithChildrenPreviewContainer.tsx │ │ └── previews │ │ │ ├── AccordionPreview.tsx │ │ │ ├── AlertPreview.tsx │ │ │ ├── AspectRatioBoxPreview.tsx │ │ │ ├── AvatarPreview.tsx │ │ │ ├── BoxPreview.tsx │ │ │ ├── BreadcrumbItemPreview.tsx │ │ │ ├── BreadcrumbPreview.tsx │ │ │ ├── ButtonPreview.tsx │ │ │ ├── HighlightPreview.tsx │ │ │ ├── IconButtonPreview.tsx │ │ │ ├── IconPreview.tsx │ │ │ ├── InputGroupPreview.tsx │ │ │ ├── InputLeftAddonPreview.tsx │ │ │ ├── InputLeftElement.tsx │ │ │ ├── InputRightAddonPreview.tsx │ │ │ ├── InputRightElement.tsx │ │ │ ├── NumberInputPreview.tsx │ │ │ ├── SelectPreview.tsx │ │ │ ├── SkeletonPreview.tsx │ │ │ ├── StackPreview.tsx │ │ │ └── StatPreview.tsx │ ├── errorBoundaries │ │ ├── AppErrorBoundary.tsx │ │ └── EditorErrorBoundary.tsx │ ├── headerMenu │ │ ├── ExportMenuItem.tsx │ │ ├── HeaderMenu.tsx │ │ └── ImportMenuItem.tsx │ ├── inspector │ │ ├── AccordionContainer.tsx │ │ ├── ActionButton.tsx │ │ ├── ChildrenInspector.tsx │ │ ├── Inspector.tsx │ │ ├── ParentInspector.tsx │ │ ├── controls │ │ │ ├── ChildrenControl.tsx │ │ │ ├── ColorPickerControl.tsx │ │ │ ├── ColorsControl.tsx │ │ │ ├── FormControl.tsx │ │ │ ├── GradientControl.tsx │ │ │ ├── HuesPickerControl.tsx │ │ │ ├── IconControl.tsx │ │ │ ├── NumberControl.tsx │ │ │ ├── SizeControl.tsx │ │ │ ├── SwitchControl.tsx │ │ │ ├── TextControl.tsx │ │ │ └── VariantsControl.tsx │ │ ├── elements-list │ │ │ ├── ElementListItem.tsx │ │ │ ├── ElementListItemDraggable.tsx │ │ │ └── ElementsList.tsx │ │ ├── inputs │ │ │ └── InputSuggestion.tsx │ │ └── panels │ │ │ ├── CustomPropsPanel.tsx │ │ │ ├── Panels.tsx │ │ │ ├── StylesPanel.tsx │ │ │ ├── components │ │ │ ├── AccordionItemPanel.tsx │ │ │ ├── AccordionPanel.tsx │ │ │ ├── AlertDescriptionPanel.tsx │ │ │ ├── AlertIconPanel.tsx │ │ │ ├── AlertPanel.tsx │ │ │ ├── AlertTitlePanel.tsx │ │ │ ├── AspectRatioPanel.tsx │ │ │ ├── AvatarBadgePanel.tsx │ │ │ ├── AvatarGroupPanel.tsx │ │ │ ├── AvatarPanel.tsx │ │ │ ├── BadgePanel.tsx │ │ │ ├── BoxPanel.tsx │ │ │ ├── BreadcrumbItemPanel.tsx │ │ │ ├── BreadcrumbPanel.tsx │ │ │ ├── ButtonPanel.tsx │ │ │ ├── CheckboxPanel.tsx │ │ │ ├── CircularProgressPanel.tsx │ │ │ ├── CloseButtonPanel.tsx │ │ │ ├── CodePanel.tsx │ │ │ ├── DividerPanel.tsx │ │ │ ├── FormControlPanel.tsx │ │ │ ├── FormErrorMessagePanel.tsx │ │ │ ├── FormHelperTextPanel.tsx │ │ │ ├── FormLabelPanel.tsx │ │ │ ├── GridPanel.tsx │ │ │ ├── HeadingPanel.tsx │ │ │ ├── HighlightPanel.tsx │ │ │ ├── IconButtonPanel.tsx │ │ │ ├── IconPanel.tsx │ │ │ ├── ImagePanel.tsx │ │ │ ├── InputPanel.tsx │ │ │ ├── KbdPanel.tsx │ │ │ ├── LinkPanel.tsx │ │ │ ├── ListIconPanel.tsx │ │ │ ├── ListItemPanel.tsx │ │ │ ├── ListPanel.tsx │ │ │ ├── NumberInputPanel.tsx │ │ │ ├── ProgressPanel.tsx │ │ │ ├── RadioGroupPanel.tsx │ │ │ ├── RadioPanel.tsx │ │ │ ├── SelectPanel.tsx │ │ │ ├── SimpleGridPanel.tsx │ │ │ ├── SkeletonPanel.tsx │ │ │ ├── SpinnerPanel.tsx │ │ │ ├── StackPanel.tsx │ │ │ ├── StatArrowPanel.tsx │ │ │ ├── StatLabelPanel.tsx │ │ │ ├── SwitchPanel.tsx │ │ │ ├── TabPanel.tsx │ │ │ ├── TabsPanel.tsx │ │ │ ├── TagPanel.tsx │ │ │ └── TextareaPanel.tsx │ │ │ └── styles │ │ │ ├── BorderPanel.tsx │ │ │ ├── DimensionPanel.tsx │ │ │ ├── DisplayPanel.tsx │ │ │ ├── EffectsPanel.tsx │ │ │ ├── FlexPanel.tsx │ │ │ ├── PaddingPanel.tsx │ │ │ └── TextPanel.tsx │ └── sidebar │ │ ├── DragItem.tsx │ │ └── Sidebar.tsx ├── componentsList.ts ├── contexts │ └── inspector-context.tsx ├── core │ ├── models │ │ ├── app.ts │ │ ├── components.test.ts │ │ ├── components.ts │ │ ├── composer │ │ │ ├── builder.ts │ │ │ └── composer.ts │ │ └── index.ts │ ├── selectors │ │ ├── app.ts │ │ └── components.ts │ └── store.ts ├── hooks │ ├── useClipboard.ts │ ├── useDispatch.ts │ ├── useDropComponent.ts │ ├── useForm.ts │ ├── useInteractive.ts │ ├── usePropsSelector.ts │ └── useShortcuts.ts ├── iconsList.ts ├── models │ └── doc.ts ├── pages │ ├── _app.tsx │ └── index.tsx ├── react-app-env.d.ts ├── templates │ ├── airbnb.ts │ ├── index.ts │ ├── onboarding.ts │ ├── producthunt.ts │ └── secretchakra.ts └── utils │ ├── bugsnag.ts │ ├── code.test.ts │ ├── code.ts │ ├── codesandbox.ts │ ├── defaultProps.tsx │ ├── editor.ts │ ├── generateId.ts │ ├── import.ts │ ├── recursive.test.ts │ ├── recursive.ts │ ├── share.ts │ └── undo.ts ├── tsconfig.json ├── tsconfig.test.json └── yarn.lock /.env: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_VERSION=1 2 | NEXT_PUBLIC_BUGSNAG_API_KEY=18bc83982a86e6477448b6bc16c0c18e 3 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: openchakra 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | on: [pull_request] 2 | 3 | jobs: 4 | check: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | 9 | - name: Check dependencies and build 10 | run: yarn 11 | 12 | - name: Run tests 13 | run: yarn test:ci 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | .next 26 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "trailingComma": "all", 4 | "bracketSpacing": true, 5 | "singleQuote": true 6 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Premier Octet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /empty-module.js: -------------------------------------------------------------------------------- 1 | module.exports = '' 2 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const nextJest = require('next/jest') 2 | 3 | const createJestConfig = nextJest({ 4 | // Provide the path to your Next.js app to load next.config.js and .env files in your test environment 5 | dir: './', 6 | }) 7 | 8 | // Add any custom config to be passed to Jest 9 | const customJestConfig = { 10 | setupFilesAfterEnv: ['/jest.setup.js'], 11 | moduleNameMapper: { 12 | '~(.*)$': '/src/$1', 13 | }, 14 | testEnvironment: 'jest-environment-jsdom', 15 | } 16 | 17 | // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async 18 | module.exports = createJestConfig(customJestConfig) 19 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | // Optional: configure or set up a testing framework before each test. 2 | // If you delete this file, remove `setupFilesAfterEnv` from `jest.config.js` 3 | 4 | // Used for __tests__/testing-library.js 5 | // Learn more: https://github.com/testing-library/jest-dom 6 | import '@testing-library/jest-dom/extend-expect' 7 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('next').NextConfig} 3 | */ 4 | const nextConfig = { 5 | /* config options here */ 6 | } 7 | 8 | module.exports = nextConfig 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "open-chakra", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@bugsnag/js": "^6.5.2", 7 | "@bugsnag/plugin-react": "^6.5.0", 8 | "@chakra-ui/icons": "^2.0.9", 9 | "@chakra-ui/react": "^2.3.2", 10 | "@chakra-ui/theme": "^2.1.11", 11 | "@emotion/react": "^11.10.4", 12 | "@emotion/styled": "^11.10.4", 13 | "@mdx-js/react": "^1.5.5", 14 | "@reach/combobox": "^0.7.3", 15 | "@rehooks/local-storage": "^2.1.1", 16 | "@rematch/core": "^1.3.0", 17 | "browser-nativefs": "^0.7.1", 18 | "codesandbox": "^2.1.11", 19 | "coloreact": "^0.3.1", 20 | "copy-to-clipboard": "^3.2.1", 21 | "framer-motion": "7.3.0", 22 | "immer": "^9.0.15", 23 | "lodash": "^4.17.15", 24 | "lodash-es": "^4.17.15", 25 | "lz-string": "^1.4.4", 26 | "next": "^12.0.1", 27 | "next-redux-wrapper": "^6.0.2", 28 | "prism-react-renderer": "^1.0.2", 29 | "react": "^18.2.0", 30 | "react-color": "^2.18.0", 31 | "react-color-picker": "^4.0.2", 32 | "react-dnd": "^13.0.0", 33 | "react-dnd-html5-backend": "15.1.1", 34 | "react-dom": "^18.2.0", 35 | "react-hotkeys-hook": "^2.4.0", 36 | "react-icons": "^4.4.0", 37 | "react-redux": "^7.1.3", 38 | "react-split-pane": "^0.1.89", 39 | "redux": "^4.0.5", 40 | "redux-persist": "^6.0.0", 41 | "redux-undo": "^1.0.0", 42 | "webpack-bundle-analyzer": "^3.6.0" 43 | }, 44 | "scripts": { 45 | "dev": "next dev", 46 | "build": "next build", 47 | "start": "next start", 48 | "test": "jest --watch", 49 | "test:ci": "jest --ci", 50 | "analyze": "BUNDLE_VISUALIZE=1 react-app-rewired build", 51 | "lint": "next lint" 52 | }, 53 | "browserslist": { 54 | "production": [ 55 | ">0.2%", 56 | "not dead", 57 | "not op_mini all" 58 | ], 59 | "development": [ 60 | "last 1 chrome version", 61 | "last 1 firefox version", 62 | "last 1 safari version" 63 | ] 64 | }, 65 | "devDependencies": { 66 | "@next/bundle-analyzer": "^9.5.1", 67 | "@testing-library/jest-dom": "5.16.4", 68 | "@testing-library/react": "13.2.0", 69 | "@testing-library/user-event": "14.2.0", 70 | "@types/jest": "^25.1.2", 71 | "@types/lodash": "^4.14.149", 72 | "@types/lz-string": "^1.3.33", 73 | "@types/prettier": "^1.19.0", 74 | "@types/react": "^18.0.18", 75 | "@types/react-color": "^3.0.1", 76 | "@types/react-dom": "^18.0.0", 77 | "@types/react-redux": "^7.1.7", 78 | "eslint": "^8.23.0", 79 | "eslint-config-next": "12.2.5", 80 | "husky": "^4.2.1", 81 | "jest": "28.1.0", 82 | "jest-environment-jsdom": "28.1.0", 83 | "lint-staged": "^10.0.7", 84 | "next-compose-plugins": "^2.2.0", 85 | "next-transpile-modules": "^9.0.0", 86 | "prettier": "^1.19.1", 87 | "pretty-quick": "^2.0.1", 88 | "typedoc": "^0.16.9", 89 | "typescript": "4.6.4" 90 | }, 91 | "resolutions": { 92 | "@types/react": "^17.0.0" 93 | }, 94 | "lint-staged": { 95 | "src/**/*.{js,jsx,ts,tsx,json,md}": [ 96 | "eslint src/**/*.{ts,tsx} --fix", 97 | "git add" 98 | ] 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/premieroctet/openchakra/5605d412cb7570fb4b261655a88d2abb229352bb/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/premieroctet/openchakra/5605d412cb7570fb4b261655a88d2abb229352bb/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/premieroctet/openchakra/5605d412cb7570fb4b261655a88d2abb229352bb/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/premieroctet/openchakra/5605d412cb7570fb4b261655a88d2abb229352bb/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/premieroctet/openchakra/5605d412cb7570fb4b261655a88d2abb229352bb/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/premieroctet/openchakra/5605d412cb7570fb4b261655a88d2abb229352bb/public/favicon.ico -------------------------------------------------------------------------------- /public/images/favicon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/premieroctet/openchakra/5605d412cb7570fb4b261655a88d2abb229352bb/public/images/favicon-512.png -------------------------------------------------------------------------------- /public/images/github-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/premieroctet/openchakra/5605d412cb7570fb4b261655a88d2abb229352bb/public/images/github-banner.png -------------------------------------------------------------------------------- /public/images/og-graph-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/premieroctet/openchakra/5605d412cb7570fb4b261655a88d2abb229352bb/public/images/og-graph-color.png -------------------------------------------------------------------------------- /public/images/og-graph-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/premieroctet/openchakra/5605d412cb7570fb4b261655a88d2abb229352bb/public/images/og-graph-white.png -------------------------------------------------------------------------------- /public/images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/premieroctet/openchakra/5605d412cb7570fb4b261655a88d2abb229352bb/public/images/screenshot.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | OpenChakra 17 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 32 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 47 | 51 | 52 | 53 | 57 | 58 | 59 | 63 | 64 | 65 | 66 |
67 | 68 | 69 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/premieroctet/openchakra/5605d412cb7570fb4b261655a88d2abb229352bb/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/premieroctet/openchakra/5605d412cb7570fb4b261655a88d2abb229352bb/public/logo512.png -------------------------------------------------------------------------------- /public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/premieroctet/openchakra/5605d412cb7570fb4b261655a88d2abb229352bb/public/mstile-150x150.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "OpenChakra", 3 | "short_name": "React JSX visual editor for Chakra UI", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /scripts/format.js: -------------------------------------------------------------------------------- 1 | const types = require("./types.json"); 2 | const fs = require("fs"); 3 | const references = {}; 4 | 5 | const inferType = (type, children) => { 6 | if (type.type === "union") { 7 | if ( 8 | type.types.length === 3 && 9 | type.types[1].name === "false" && 10 | type.types[2].name === "true" 11 | ) { 12 | return { type: "boolean" }; 13 | } else if ( 14 | type.types.length === 2 && 15 | type.types[0].name === "undefined" && 16 | type.types[1].name === "string" 17 | ) { 18 | return { type: "string" }; 19 | } else if ( 20 | type.types.length === 2 && 21 | type.types[0].name === "undefined" && 22 | type.types[1].type === "reflection" 23 | ) { 24 | return { type: "callback" }; 25 | } else if ( 26 | type.types.length > 0 && 27 | type.types[0].type === "stringLiteral" 28 | ) { 29 | return { 30 | type: "enum", 31 | choices: type.types.map(item => item.value) 32 | }; 33 | } 34 | } 35 | 36 | return { type: "string" }; 37 | }; 38 | 39 | types.children 40 | .filter(child => !!child.children) 41 | .filter(child => child.kindString === "Interface") 42 | .map(child => { 43 | references[child.sources[0].fileName.split("/")[0]] = child.children.map( 44 | props => { 45 | const { name, type, comment } = props; 46 | return { 47 | name, 48 | type: inferType(type, types.children), 49 | description: comment && comment.shortText 50 | }; 51 | } 52 | ); 53 | }); 54 | 55 | fs.writeFileSync("./ref.json", JSON.stringify(references)); 56 | -------------------------------------------------------------------------------- /scripts/generate.js: -------------------------------------------------------------------------------- 1 | const TypeDoc = require("typedoc"); 2 | 3 | const app = new TypeDoc.Application(); 4 | 5 | // If you want TypeDoc to load tsconfig.json / typedoc.json files 6 | app.options.addReader(new TypeDoc.TSConfigReader()); 7 | app.options.addReader(new TypeDoc.TypeDocReader()); 8 | 9 | app.bootstrap({ 10 | mode: "file", 11 | includeDeclarations: true, 12 | excludeExternals: true 13 | }); 14 | 15 | //node_modules/@chakra-ui/core/dist/theme/icons.d.ts 16 | const COMPONENTS = [ 17 | "Badge", 18 | "Checkbox", 19 | "Button", 20 | "Image", 21 | "Badge", 22 | "Icon", 23 | "Text", 24 | "Avatar", 25 | "AvatarGroup", 26 | "AvatarBadge", 27 | "Tag" 28 | ]; 29 | const project = app.convert( 30 | app.expandInputFiles( 31 | COMPONENTS.map( 32 | name => `../node_modules/@chakra-ui/core/dist/${name}/index.d.ts` 33 | ) 34 | ) 35 | ); 36 | 37 | if (project) { 38 | // Project may not have converted correctly 39 | app.generateJson(project, "types.json"); 40 | } 41 | -------------------------------------------------------------------------------- /splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/premieroctet/openchakra/5605d412cb7570fb4b261655a88d2abb229352bb/splash.png -------------------------------------------------------------------------------- /src/components/CodePanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo, useState, useEffect } from 'react' 2 | import Highlight, { defaultProps } from 'prism-react-renderer' 3 | import { Box, Button, useClipboard } from '@chakra-ui/react' 4 | import { generateCode } from '~utils/code' 5 | import theme from 'prism-react-renderer/themes/nightOwl' 6 | import { useSelector } from 'react-redux' 7 | import { getComponents } from '~core/selectors/components' 8 | 9 | const CodePanel = () => { 10 | const components = useSelector(getComponents) 11 | const [code, setCode] = useState(undefined) 12 | 13 | useEffect(() => { 14 | const getCode = async () => { 15 | const code = await generateCode(components) 16 | setCode(code) 17 | } 18 | 19 | getCode() 20 | }, [components]) 21 | 22 | const { onCopy, hasCopied } = useClipboard(code!) 23 | 24 | return ( 25 | 37 | 50 | 56 | {({ className, style, tokens, getLineProps, getTokenProps }) => ( 57 |
58 |             {tokens.map((line, i) => (
59 |               
60 | {line.map((token, key) => ( 61 | 62 | ))} 63 |
64 | ))} 65 |
66 | )} 67 |
68 |
69 | ) 70 | } 71 | 72 | export default memo(CodePanel) 73 | -------------------------------------------------------------------------------- /src/components/Metadata.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Head from 'next/head' 3 | 4 | const Metadata = () => { 5 | return ( 6 | 7 | 8 | 9 | 10 | 15 | 21 | 27 | 28 | 29 | 30 | 31 | OpenChakra 32 | 36 | 40 | 41 | {/* OpenGraph tags */} 42 | 43 | 44 | 45 | 46 | 50 | 55 | 56 | 57 | 58 | {/* Twitter Card tags */} 59 | 60 | 61 | 65 | 69 | 70 | 71 | 75 | 76 | 77 | 81 | 82 | ) 83 | } 84 | 85 | export default Metadata 86 | -------------------------------------------------------------------------------- /src/components/editor/ComponentPreview.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from '@testing-library/react' 3 | import { init } from '@rematch/core' 4 | import { Provider } from 'react-redux' 5 | import { DndProvider } from 'react-dnd' 6 | import { HTML5Backend } from 'react-dnd-html5-backend' 7 | import { ChakraProvider } from '@chakra-ui/react' 8 | import theme from '@chakra-ui/theme' 9 | 10 | import ComponentPreview from './ComponentPreview' 11 | import { storeConfig } from '~core/store' 12 | 13 | function renderWithRedux( 14 | components: React.ReactNode, 15 | { 16 | // @ts-ignore 17 | initialState, 18 | // @ts-ignore 19 | store = init(storeConfig), 20 | } = {}, 21 | ) { 22 | return { 23 | ...render( 24 | 25 | 26 | {components} 27 | 28 | , 29 | ), 30 | // adding `store` to the returned utilities to allow us 31 | // to reference it in our tests (just try to avoid using 32 | // this to test implementation details). 33 | store, 34 | } 35 | } 36 | 37 | const componentsToTest = [ 38 | 'Badge', 39 | 'Button', 40 | 'Icon', 41 | 'IconButton', 42 | 'Image', 43 | 'Text', 44 | 'Progress', 45 | 'Link', 46 | 'Spinner', 47 | 'CloseButton', 48 | 'Checkbox', 49 | 'Divider', 50 | 'Code', 51 | 'Textarea', 52 | 'CircularProgress', 53 | 'Heading', 54 | 'Highlight', 55 | 'Tag', 56 | 'Switch', 57 | 'FormLabel', 58 | // 'Tab', 59 | 'Input', 60 | 'Radio', 61 | //'ListItem', 62 | //'ListIcon', 63 | // 'AlertIcon', 64 | // 'AccordionIcon', 65 | 'Box', 66 | 'SimpleGrid', 67 | 'Flex', 68 | // 'AccordionPanel', 69 | // 'AccordionItem', 70 | 'FormControl', 71 | // 'Tabs', 72 | // 'TabList', 73 | // 'TabPanels', 74 | 'List', 75 | 'Avatar', 76 | 'AvatarGroup', 77 | 'Alert', 78 | 'Stack', 79 | 'Accordion', 80 | // 'AccordionButton', 81 | 'RadioGroup', 82 | 'Select', 83 | 'InputGroup', 84 | ] 85 | 86 | test.each(componentsToTest)('Component Preview for %s', componentName => { 87 | // const spy = jest.spyOn(global.console, 'error') 88 | // @ts-ignore 89 | const store = init(storeConfig) 90 | store.dispatch.components.addComponent({ 91 | // @ts-ignore 92 | parentName: 'root', 93 | type: componentName, 94 | rootParentType: componentName, 95 | testId: 'test', 96 | }) 97 | 98 | // console.log(componentName, store.getState().components.present.components); 99 | // @ts-ignore 100 | renderWithRedux(, { store }) 101 | // expect(spy).not.toHaveBeenCalled(); 102 | }) 103 | -------------------------------------------------------------------------------- /src/components/editor/Editor.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import { Box, Text, Link } from '@chakra-ui/react' 3 | import { useDropComponent } from '~hooks/useDropComponent' 4 | import SplitPane from 'react-split-pane' 5 | import CodePanel from '~components/CodePanel' 6 | import { useSelector } from 'react-redux' 7 | import useDispatch from '~hooks/useDispatch' 8 | import { getComponents } from '~core/selectors/components' 9 | import { getShowLayout, getShowCode } from '~core/selectors/app' 10 | import ComponentPreview from '~components/editor/ComponentPreview' 11 | 12 | export const gridStyles = { 13 | backgroundImage: 14 | 'linear-gradient(to right, #d9e2e9 1px, transparent 1px),linear-gradient(to bottom, #d9e2e9 1px, transparent 1px);', 15 | backgroundSize: '20px 20px', 16 | bgColor: '#edf2f6', 17 | p: 10, 18 | } 19 | 20 | const Editor: React.FC = () => { 21 | const showCode = useSelector(getShowCode) 22 | const showLayout = useSelector(getShowLayout) 23 | const components = useSelector(getComponents) 24 | const dispatch = useDispatch() 25 | 26 | const { drop } = useDropComponent('root') 27 | const isEmpty = !components.root.children.length 28 | const rootProps = components.root.props 29 | 30 | let editorBackgroundProps = {} 31 | 32 | const onSelectBackground = () => { 33 | dispatch.components.unselect() 34 | } 35 | 36 | if (showLayout) { 37 | editorBackgroundProps = gridStyles 38 | } 39 | 40 | editorBackgroundProps = { 41 | ...editorBackgroundProps, 42 | ...rootProps, 43 | } 44 | 45 | const Playground = ( 46 | 61 | {isEmpty && ( 62 | 63 | Drag some component to start coding without code! Or load{' '} 64 | { 67 | e.stopPropagation() 68 | dispatch.components.loadDemo('onboarding') 69 | }} 70 | textDecoration="underline" 71 | > 72 | the onboarding components 73 | 74 | . 75 | 76 | )} 77 | 78 | {components.root.children.map((name: string) => ( 79 | 80 | ))} 81 | 82 | ) 83 | 84 | if (!showCode) { 85 | return Playground 86 | } 87 | 88 | return ( 89 | // @ts-ignore 90 | 100 | {Playground} 101 | 102 | 103 | ) 104 | } 105 | 106 | export default memo(Editor) 107 | -------------------------------------------------------------------------------- /src/components/editor/PreviewContainer.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent, ComponentClass } from 'react' 2 | import { useInteractive } from '~hooks/useInteractive' 3 | import { Box } from '@chakra-ui/react' 4 | 5 | const PreviewContainer: React.FC<{ 6 | component: IComponent 7 | type: string | FunctionComponent | ComponentClass 8 | enableVisualHelper?: boolean 9 | isBoxWrapped?: boolean 10 | }> = ({ 11 | component, 12 | type, 13 | enableVisualHelper, 14 | isBoxWrapped, 15 | ...forwardedProps 16 | }) => { 17 | const { props, ref } = useInteractive(component, enableVisualHelper) 18 | 19 | const children = React.createElement(type, { 20 | ...props, 21 | ...forwardedProps, 22 | ref, 23 | }) 24 | 25 | if (isBoxWrapped) { 26 | let boxProps: any = {} 27 | 28 | return ( 29 | 30 | {children} 31 | 32 | ) 33 | } 34 | 35 | return children 36 | } 37 | 38 | export default PreviewContainer 39 | -------------------------------------------------------------------------------- /src/components/editor/WithChildrenPreviewContainer.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent, ComponentClass } from 'react' 2 | import { useInteractive } from '~hooks/useInteractive' 3 | import { useDropComponent } from '~hooks/useDropComponent' 4 | import ComponentPreview from '~components/editor/ComponentPreview' 5 | import { Box } from '@chakra-ui/react' 6 | 7 | const WithChildrenPreviewContainer: React.FC<{ 8 | component: IComponent 9 | type: string | FunctionComponent | ComponentClass 10 | enableVisualHelper?: boolean 11 | isBoxWrapped?: boolean 12 | }> = ({ 13 | component, 14 | type, 15 | enableVisualHelper = false, 16 | isBoxWrapped, 17 | ...forwardedProps 18 | }) => { 19 | const { drop, isOver } = useDropComponent(component.id) 20 | const { props, ref } = useInteractive(component, enableVisualHelper) 21 | const propsElement = { ...props, ...forwardedProps, pos: 'relative' } 22 | 23 | if (!isBoxWrapped) { 24 | propsElement.ref = drop(ref) 25 | } 26 | 27 | if (isOver) { 28 | propsElement.bg = 'teal.50' 29 | } 30 | 31 | const children = React.createElement( 32 | type, 33 | propsElement, 34 | component.children.map((key: string) => ( 35 | 36 | )), 37 | ) 38 | 39 | if (isBoxWrapped) { 40 | let boxProps: any = { 41 | display: 'inline', 42 | } 43 | 44 | return ( 45 | 46 | {children} 47 | 48 | ) 49 | } 50 | 51 | return children 52 | } 53 | 54 | export default WithChildrenPreviewContainer 55 | -------------------------------------------------------------------------------- /src/components/editor/previews/AccordionPreview.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useInteractive } from '~hooks/useInteractive' 3 | import { useDropComponent } from '~hooks/useDropComponent' 4 | import { 5 | Box, 6 | Accordion, 7 | AccordionButton, 8 | AccordionItem, 9 | AccordionPanel, 10 | } from '@chakra-ui/react' 11 | import ComponentPreview from '~components/editor/ComponentPreview' 12 | import { AccordionWhitelist } from '~utils/editor' 13 | 14 | const acceptedTypes: ComponentType[] = ['AccordionItem'] 15 | 16 | const AccordionPreview: React.FC = ({ component }) => { 17 | const { props, ref } = useInteractive(component, true) 18 | const { drop, isOver } = useDropComponent(component.id, acceptedTypes) 19 | 20 | let boxProps: any = {} 21 | 22 | if (isOver) { 23 | props.bg = 'teal.50' 24 | } 25 | 26 | return ( 27 | 28 | 29 | {component.children.map((key: string) => ( 30 | 31 | ))} 32 | 33 | 34 | ) 35 | } 36 | 37 | export const AccordionButtonPreview = ({ component }: IPreviewProps) => { 38 | const { props, ref } = useInteractive(component, true) 39 | const { drop, isOver } = useDropComponent(component.id, AccordionWhitelist) 40 | 41 | if (isOver) { 42 | props.bg = 'teal.50' 43 | } 44 | 45 | return ( 46 | 47 | {component.children.map((key: string) => ( 48 | 49 | ))} 50 | 51 | ) 52 | } 53 | 54 | export const AccordionItemPreview = ({ component }: IPreviewProps) => { 55 | const { props, ref } = useInteractive(component, true) 56 | const { drop, isOver } = useDropComponent(component.id, AccordionWhitelist) 57 | 58 | let boxProps: any = {} 59 | 60 | if (isOver) { 61 | props.bg = 'teal.50' 62 | } 63 | 64 | return ( 65 | 66 | 67 | {component.children.map((key: string) => ( 68 | 69 | ))} 70 | 71 | 72 | ) 73 | } 74 | 75 | export const AccordionPanelPreview = ({ component }: IPreviewProps) => { 76 | const { props, ref } = useInteractive(component, true) 77 | const { drop, isOver } = useDropComponent(component.id, AccordionWhitelist) 78 | 79 | let boxProps: any = {} 80 | 81 | if (isOver) { 82 | props.bg = 'teal.50' 83 | } 84 | 85 | return ( 86 | 87 | 88 | {component.children.map((key: string) => ( 89 | 90 | ))} 91 | 92 | 93 | ) 94 | } 95 | 96 | export default AccordionPreview 97 | -------------------------------------------------------------------------------- /src/components/editor/previews/AlertPreview.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useInteractive } from '~hooks/useInteractive' 3 | import { useDropComponent } from '~hooks/useDropComponent' 4 | import ComponentPreview from '~components/editor/ComponentPreview' 5 | import { Alert, Box } from '@chakra-ui/react' 6 | 7 | const AlertPreview: React.FC = ({ component }) => { 8 | const acceptedTypes = [ 9 | 'AlertIcon', 10 | 'AlertTitle', 11 | 'AlertDescription', 12 | ] as ComponentType[] 13 | const { props, ref } = useInteractive(component, false) 14 | const { drop, isOver } = useDropComponent(component.id, acceptedTypes) 15 | 16 | let boxProps: any = {} 17 | 18 | if (isOver) { 19 | props.bg = 'teal.50' 20 | } 21 | 22 | return ( 23 | 24 | 25 | {component.children.map((key: string) => ( 26 | 27 | ))} 28 | 29 | 30 | ) 31 | } 32 | 33 | export default AlertPreview 34 | -------------------------------------------------------------------------------- /src/components/editor/previews/AspectRatioBoxPreview.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Box, AspectRatio } from '@chakra-ui/react' 3 | import { useInteractive } from '~hooks/useInteractive' 4 | import { useDropComponent } from '~hooks/useDropComponent' 5 | import ComponentPreview from '~components/editor/ComponentPreview' 6 | 7 | const AspectRatioPreview: React.FC<{ component: IComponent }> = ({ 8 | component, 9 | }) => { 10 | const { props, ref } = useInteractive(component, true) 11 | const { drop, isOver } = useDropComponent( 12 | component.id, 13 | undefined, 14 | component.children.length === 0, 15 | ) 16 | const children = component.children 17 | 18 | const boxProps: any = {} 19 | 20 | if (isOver) { 21 | props.bg = 'teal.50' 22 | } 23 | 24 | return ( 25 | 26 | 27 | {!children.length ? ( 28 | /* 29 | * We need at least one children because of the implementation 30 | * of AspectRatio 31 | */ 32 | 33 | ) : ( 34 | 35 | 36 | 37 | )} 38 | 39 | 40 | ) 41 | } 42 | 43 | export default AspectRatioPreview 44 | -------------------------------------------------------------------------------- /src/components/editor/previews/AvatarPreview.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | Avatar, 4 | AvatarGroup, 5 | Box, 6 | AvatarBadge, 7 | BoxProps, 8 | } from '@chakra-ui/react' 9 | import { useInteractive } from '~hooks/useInteractive' 10 | import { useDropComponent } from '~hooks/useDropComponent' 11 | import ComponentPreview from '~components/editor/ComponentPreview' 12 | import { useSelector } from 'react-redux' 13 | import { getComponents } from '~core/selectors/components' 14 | 15 | const AvatarPreview: React.FC = ({ component, spacing, index }) => { 19 | const { drop, isOver } = useDropComponent(component.id, ['AvatarBadge']) 20 | const { props, ref } = useInteractive(component) 21 | 22 | let boxProps: any = { 23 | display: 'inline-block', 24 | zIndex: index ? 20 - index : null, 25 | } 26 | 27 | props.p = 0 28 | 29 | if (isOver) { 30 | props.bg = 'teal.50' 31 | } 32 | 33 | return ( 34 | 35 | 36 | {component.children.map((key: string) => ( 37 | 38 | ))} 39 | 40 | 41 | ) 42 | } 43 | 44 | export const AvatarGroupPreview = ({ component }: IPreviewProps) => { 45 | const { props, ref } = useInteractive(component, true) 46 | const { drop, isOver } = useDropComponent(component.id, ['Avatar']) 47 | const components = useSelector(getComponents) 48 | let boxProps: any = { display: 'inline' } 49 | 50 | if (isOver) { 51 | props.bg = 'teal.50' 52 | } 53 | 54 | return ( 55 | 56 | 57 | {component.children.map((key: string, i: number) => ( 58 | 64 | ))} 65 | 66 | 67 | ) 68 | } 69 | 70 | export const AvatarBadgePreview = ({ component }: IPreviewProps) => { 71 | const { props, ref } = useInteractive(component) 72 | let boxProps: any = {} 73 | 74 | return ( 75 | 76 | 77 | 78 | ) 79 | } 80 | 81 | export default AvatarPreview 82 | -------------------------------------------------------------------------------- /src/components/editor/previews/BoxPreview.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Box } from '@chakra-ui/react' 3 | import ComponentPreview from '~components/editor/ComponentPreview' 4 | import { useDropComponent } from '~hooks/useDropComponent' 5 | import { useInteractive } from '~hooks/useInteractive' 6 | 7 | const BoxPreview: React.FC<{ component: IComponent }> = ({ component }) => { 8 | const { drop, isOver } = useDropComponent(component.id) 9 | const { props, ref } = useInteractive(component, true) 10 | 11 | if (isOver) { 12 | props.bg = 'teal.50' 13 | } 14 | 15 | return ( 16 | 17 | {component.children.map((key: string) => ( 18 | 19 | ))} 20 | 21 | ) 22 | } 23 | 24 | export default BoxPreview 25 | -------------------------------------------------------------------------------- /src/components/editor/previews/BreadcrumbItemPreview.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useInteractive } from '~hooks/useInteractive' 3 | import { useDropComponent } from '~hooks/useDropComponent' 4 | import ComponentPreview from '~components/editor/ComponentPreview' 5 | import { BreadcrumbItem } from '@chakra-ui/react' 6 | 7 | const BreadcrumbItemPreview: React.FC = ({ component }) => { 8 | const acceptedTypes = ['BreadcrumbLink'] as ComponentType[] 9 | const { props, ref } = useInteractive(component, true) 10 | const { drop, isOver } = useDropComponent(component.id, acceptedTypes) 11 | 12 | if (isOver) { 13 | props.bg = 'teal.50' 14 | } 15 | 16 | return ( 17 | 18 | {component.children.map((key: string) => ( 19 | 20 | ))} 21 | 22 | ) 23 | } 24 | 25 | export default BreadcrumbItemPreview 26 | -------------------------------------------------------------------------------- /src/components/editor/previews/BreadcrumbPreview.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useInteractive } from '~hooks/useInteractive' 3 | import { useDropComponent } from '~hooks/useDropComponent' 4 | import ComponentPreview from '~components/editor/ComponentPreview' 5 | import { Box, Breadcrumb } from '@chakra-ui/react' 6 | 7 | const BreadcrumbPreview: React.FC = ({ component }) => { 8 | const acceptedTypes = ['BreadcrumbItem', 'BreadcrumbLink'] as ComponentType[] 9 | const { props, ref } = useInteractive(component, false) 10 | const { drop, isOver } = useDropComponent(component.id, acceptedTypes) 11 | 12 | let boxProps: any = {} 13 | 14 | if (isOver) { 15 | props.bg = 'teal.50' 16 | } 17 | 18 | return ( 19 | 20 | 21 | {component.children.map((key: string) => ( 22 | 23 | ))} 24 | 25 | 26 | ) 27 | } 28 | 29 | export default BreadcrumbPreview 30 | -------------------------------------------------------------------------------- /src/components/editor/previews/ButtonPreview.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useDropComponent } from '~hooks/useDropComponent' 3 | import { useInteractive } from '~hooks/useInteractive' 4 | import { Button } from '@chakra-ui/react' 5 | import icons from '~iconsList' 6 | 7 | interface Props { 8 | component: IComponent 9 | } 10 | 11 | const ButtonPreview = ({ component }: Props) => { 12 | const { isOver } = useDropComponent(component.id) 13 | const { props, ref } = useInteractive(component, true) 14 | 15 | if (isOver) { 16 | props.bg = 'teal.50' 17 | } 18 | 19 | if (props.leftIcon) { 20 | if (Object.keys(icons).includes(props.leftIcon)) { 21 | const Icon = icons[props.leftIcon as keyof typeof icons] 22 | props.leftIcon = 23 | } else { 24 | props.leftIcon = undefined 25 | } 26 | } 27 | 28 | if (props.rightIcon) { 29 | if (Object.keys(icons).includes(props.rightIcon)) { 30 | const Icon = icons[props.rightIcon as keyof typeof icons] 31 | props.rightIcon = 32 | } else { 33 | props.rightIcon = undefined 34 | } 35 | } 36 | 37 | return 65 | 66 | 67 | 68 | ) 69 | } 70 | return this.props.children 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/components/errorBoundaries/EditorErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { connect } from 'react-redux' 3 | import { ActionCreators as UndoActionCreators } from 'redux-undo' 4 | import { Box, Flex, Stack, Button } from '@chakra-ui/react' 5 | import { CheckCircleIcon } from '@chakra-ui/icons' 6 | import { FaBomb } from 'react-icons/fa' 7 | import { gridStyles } from '~components/editor/Editor' 8 | import { bugsnagClient } from '~utils/bugsnag' 9 | 10 | type ErrorBoundaryState = { 11 | hasError: boolean 12 | } 13 | 14 | type ErrorBoundaryProps = { 15 | undo: () => void 16 | children?: React.ReactNode 17 | } 18 | 19 | class ErrorBoundary extends Component { 20 | constructor(props: ErrorBoundaryProps) { 21 | super(props) 22 | this.state = { hasError: false } 23 | } 24 | 25 | static getDerivedStateFromError(error: any) { 26 | bugsnagClient.notify(error) 27 | return { hasError: true } 28 | } 29 | 30 | render() { 31 | if (this.state.hasError) { 32 | this.props.undo() 33 | 34 | return ( 35 | 42 | 52 | 53 | 54 | Oups… 55 |
56 | Something went wrong! We have recovered the editor to its previous 57 | version. 58 | 70 |
71 |
72 |
73 | ) 74 | } 75 | return this.props.children 76 | } 77 | } 78 | 79 | const mapDispatchToProps = { 80 | undo: UndoActionCreators.undo, 81 | } 82 | 83 | export default connect(null, mapDispatchToProps)(ErrorBoundary) 84 | -------------------------------------------------------------------------------- /src/components/headerMenu/ExportMenuItem.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useSelector } from 'react-redux' 3 | import { MenuItem, Box } from '@chakra-ui/react' 4 | import { FaSave } from 'react-icons/fa' 5 | import { saveAsJSON } from '~utils/import' 6 | import { getComponents } from '~core/selectors/components' 7 | 8 | const ExportMenuItem = () => { 9 | const components = useSelector(getComponents) 10 | 11 | return ( 12 | saveAsJSON(components)}> 13 | 14 | Save components 15 | 16 | ) 17 | } 18 | 19 | export default ExportMenuItem 20 | -------------------------------------------------------------------------------- /src/components/headerMenu/HeaderMenu.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import dynamic from 'next/dynamic' 3 | import { 4 | Box, 5 | Button, 6 | LightMode, 7 | Menu, 8 | MenuButton, 9 | MenuList, 10 | MenuItem, 11 | MenuDivider, 12 | LinkProps, 13 | MenuItemProps, 14 | MenuButtonProps, 15 | ButtonProps, 16 | Portal, 17 | } from '@chakra-ui/react' 18 | import { ChevronDownIcon } from '@chakra-ui/icons' 19 | import { FaBomb } from 'react-icons/fa' 20 | import { GoRepo, GoArchive } from 'react-icons/go' 21 | 22 | type MenuItemLinkProps = MenuItemProps | LinkProps 23 | 24 | // Ignore because of AS typing issues 25 | // @ts-ignore 26 | const MenuItemLink: React.FC = React.forwardRef( 27 | (props, ref: React.Ref) => { 28 | // @ts-ignore 29 | return 30 | }, 31 | ) 32 | 33 | MenuItemLink.displayName = 'MenuItemLink' 34 | 35 | // @ts-ignore 36 | const CustomMenuButton: React.FC< 37 | MenuButtonProps | ButtonProps 38 | > = React.forwardRef((props, ref: React.Ref) => { 39 | // @ts-ignore 40 | return 41 | }) 42 | 43 | CustomMenuButton.displayName = 'CustomMenuButton' 44 | 45 | const ExportMenuItem = dynamic(() => import('./ExportMenuItem'), { ssr: false }) 46 | const ImportMenuItem = dynamic(() => import('./ImportMenuItem'), { ssr: false }) 47 | 48 | const HeaderMenu = () => { 49 | return ( 50 | 51 | } 53 | size="xs" 54 | variant="ghost" 55 | colorScheme="gray" 56 | > 57 | Editor 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 71 | 72 | Chakra UI Docs 73 | 74 | 75 | 76 | Report issue 77 | 78 | 79 | 80 | 81 | 82 | Chakra v0 Editor 83 | 84 | 85 | 86 | Chakra v1 Editor 87 | 88 | 89 | 90 | 91 | 92 | ) 93 | } 94 | 95 | export default memo(HeaderMenu) 96 | -------------------------------------------------------------------------------- /src/components/headerMenu/ImportMenuItem.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { MenuItem, Box } from '@chakra-ui/react' 3 | import { FiUpload } from 'react-icons/fi' 4 | import { loadFromJSON } from '~utils/import' 5 | import useDispatch from '~hooks/useDispatch' 6 | 7 | const ImportMenuItem = () => { 8 | const dispatch = useDispatch() 9 | 10 | return ( 11 | { 13 | const components = await loadFromJSON() 14 | dispatch.components.reset(components) 15 | }} 16 | > 17 | 18 | Import components 19 | 20 | ) 21 | } 22 | 23 | export default ImportMenuItem 24 | -------------------------------------------------------------------------------- /src/components/inspector/AccordionContainer.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode, memo } from 'react' 2 | import { 3 | AccordionItem, 4 | AccordionButton, 5 | AccordionIcon, 6 | AccordionPanel, 7 | Box, 8 | AccordionItemProps, 9 | } from '@chakra-ui/react' 10 | 11 | const AccordionContainer: React.FC<{ 12 | title: ReactNode 13 | defaultIsOpen?: boolean 14 | children: ReactNode 15 | } & AccordionItemProps> = ({ title, children, defaultIsOpen = true }) => { 16 | return ( 17 | 18 | {/* */} 19 | 20 | 21 | {title} 22 | 23 | 24 | 25 | 26 | {children} 27 | 28 | 29 | ) 30 | } 31 | 32 | export default memo(AccordionContainer) 33 | -------------------------------------------------------------------------------- /src/components/inspector/ActionButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | TooltipProps, 4 | IconButtonProps, 5 | Tooltip, 6 | IconButton, 7 | As, 8 | } from '@chakra-ui/react' 9 | 10 | interface Props 11 | extends Omit { 12 | icon: IconButtonProps['icon'] 13 | label: string 14 | as?: As 15 | isLoading?: boolean 16 | onClick?: IconButtonProps['onClick'] 17 | colorScheme?: IconButtonProps['colorScheme'] 18 | } 19 | 20 | const ActionButton = ({ 21 | icon, 22 | as, 23 | label, 24 | onClick, 25 | colorScheme, 26 | isLoading, 27 | ...props 28 | }: Props) => { 29 | return ( 30 | 31 | 41 | 42 | ) 43 | } 44 | 45 | export default ActionButton 46 | -------------------------------------------------------------------------------- /src/components/inspector/ChildrenInspector.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useSelector } from 'react-redux' 3 | import { getSelectedComponentChildren } from '~core/selectors/components' 4 | import ElementsList from '~components/inspector/elements-list/ElementsList' 5 | import useDispatch from '~hooks/useDispatch' 6 | 7 | const ChildrenInspector = () => { 8 | const childrenComponent = useSelector(getSelectedComponentChildren) 9 | const dispatch = useDispatch() 10 | 11 | const moveChildren = (fromIndex: number, toIndex: number) => { 12 | dispatch.components.moveSelectedComponentChildren({ 13 | fromIndex, 14 | toIndex, 15 | }) 16 | } 17 | 18 | const onSelectChild = (id: IComponent['id']) => { 19 | dispatch.components.select(id) 20 | } 21 | 22 | const onHoverChild = (id: IComponent['id']) => { 23 | dispatch.components.hover(id) 24 | } 25 | 26 | const onUnhoverChild = () => { 27 | dispatch.components.unhover() 28 | } 29 | 30 | return ( 31 | 38 | ) 39 | } 40 | 41 | export default ChildrenInspector 42 | -------------------------------------------------------------------------------- /src/components/inspector/ParentInspector.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useSelector } from 'react-redux' 3 | import { getSelectedComponentParent } from '~core/selectors/components' 4 | import ElementListItem from '~components/inspector/elements-list/ElementListItem' 5 | import useDispatch from '~hooks/useDispatch' 6 | 7 | const ParentInspector = () => { 8 | const parentComponent = useSelector(getSelectedComponentParent) 9 | const dispatch = useDispatch() 10 | 11 | const onSelect = () => { 12 | dispatch.components.select(parentComponent.id) 13 | } 14 | 15 | const onHover = () => { 16 | dispatch.components.hover(parentComponent.id) 17 | } 18 | 19 | const onUnhover = () => { 20 | dispatch.components.unhover() 21 | } 22 | 23 | return ( 24 | 30 | ) 31 | } 32 | 33 | export default ParentInspector 34 | -------------------------------------------------------------------------------- /src/components/inspector/controls/ChildrenControl.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect, KeyboardEvent } from 'react' 2 | import { Input } from '@chakra-ui/react' 3 | import useDispatch from '~hooks/useDispatch' 4 | import { useForm } from '~hooks/useForm' 5 | import usePropsSelector from '~hooks/usePropsSelector' 6 | import { useSelector } from 'react-redux' 7 | import { getInputTextFocused } from '~core/selectors/app' 8 | import FormControl from './FormControl' 9 | 10 | const ChildrenControl: React.FC = () => { 11 | const dispatch = useDispatch() 12 | const textInput = useRef(null) 13 | const focusInput = useSelector(getInputTextFocused) 14 | const { setValueFromEvent } = useForm() 15 | const children = usePropsSelector('children') 16 | const onKeyUp = (event: KeyboardEvent) => { 17 | if (event.keyCode === 13 && textInput.current) { 18 | textInput.current.blur() 19 | } 20 | } 21 | useEffect(() => { 22 | if (focusInput && textInput.current) { 23 | textInput.current.focus() 24 | } else if (focusInput === false && textInput.current) { 25 | textInput.current.blur() 26 | } 27 | }, [focusInput]) 28 | 29 | return ( 30 | 31 | { 41 | dispatch.app.toggleInputText(false) 42 | }} 43 | /> 44 | 45 | ) 46 | } 47 | 48 | export default ChildrenControl 49 | -------------------------------------------------------------------------------- /src/components/inspector/controls/ColorsControl.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode, memo } from 'react' 2 | import FormControl from './FormControl' 3 | import ColorPickerControl from './ColorPickerControl' 4 | 5 | type ColorControlPropsType = { 6 | name: string 7 | label: string | ReactNode 8 | enableHues?: boolean 9 | withFullColor?: boolean 10 | } 11 | 12 | const ColorsControl = (props: ColorControlPropsType) => { 13 | return ( 14 | 15 | 20 | 21 | ) 22 | } 23 | 24 | export default memo(ColorsControl) 25 | -------------------------------------------------------------------------------- /src/components/inspector/controls/FormControl.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode, memo } from 'react' 2 | import { 3 | FormLabel, 4 | FormControl as ChakraFormControl, 5 | Grid, 6 | Box, 7 | } from '@chakra-ui/react' 8 | 9 | type FormControlPropType = { 10 | label: ReactNode 11 | children: ReactNode 12 | htmlFor?: string 13 | hasColumn?: boolean 14 | } 15 | 16 | const FormControl: React.FC = ({ 17 | label, 18 | htmlFor, 19 | children, 20 | hasColumn, 21 | }) => ( 22 | 29 | 38 | {label} 39 | 40 | 46 | {children} 47 | 48 | 49 | ) 50 | 51 | export default memo(FormControl) 52 | -------------------------------------------------------------------------------- /src/components/inspector/controls/HuesPickerControl.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { 3 | Grid, 4 | SliderTrack, 5 | SliderFilledTrack, 6 | SliderThumb, 7 | Box, 8 | Slider, 9 | } from '@chakra-ui/react' 10 | 11 | type HuesPickerPropType = { 12 | name: string 13 | themeColors: any 14 | enableHues: boolean 15 | gradient: boolean 16 | index?: number 17 | setValue: (name: string, value: any) => void 18 | updateGradient?: (value: string, index: number) => void 19 | } 20 | 21 | const HuesPickerControl = (props: HuesPickerPropType) => { 22 | const [hue, setHue] = useState(500) 23 | 24 | return ( 25 | <> 26 | 27 | {Object.keys(props.themeColors).map(colorName => 28 | props.gradient ? ( 29 | { 36 | if (props.updateGradient) { 37 | props.updateGradient(`${colorName}.${hue}`, props.index!) 38 | } 39 | }} 40 | mt={2} 41 | borderRadius="full" 42 | height="30px" 43 | width="30px" 44 | /> 45 | ) : ( 46 | 53 | props.setValue( 54 | props.name, 55 | props.enableHues ? `${colorName}.${hue}` : colorName, 56 | ) 57 | } 58 | mt={2} 59 | borderRadius="full" 60 | height="30px" 61 | width="30px" 62 | /> 63 | ), 64 | )} 65 | 66 | 67 | {props.enableHues && ( 68 | <> 69 | { 71 | value = value === 0 ? 50 : value 72 | setHue(value) 73 | }} 74 | min={0} 75 | max={900} 76 | step={100} 77 | value={hue} 78 | > 79 | 80 | 81 | 82 | 83 | 84 | {hue} 85 | 86 | 87 | 88 | 89 | )} 90 | 91 | ) 92 | } 93 | 94 | export default HuesPickerControl 95 | -------------------------------------------------------------------------------- /src/components/inspector/controls/IconControl.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react' 2 | // import * as Chakra from '@chakra-ui/react' 3 | import FormControl from './FormControl' 4 | import { useForm } from '~hooks/useForm' 5 | import usePropsSelector from '~hooks/usePropsSelector' 6 | import InputSuggestion from '~components/inspector/inputs/InputSuggestion' 7 | import { ComboboxOption, ComboboxOptionText } from '@reach/combobox' 8 | import icons from '~iconsList' 9 | 10 | type IconControlProps = { 11 | name: string 12 | label: string | ReactNode 13 | } 14 | 15 | const IconControl: React.FC = ({ name, label }) => { 16 | const { setValueFromEvent } = useForm() 17 | const value = usePropsSelector(name) 18 | 19 | return ( 20 | 21 | 26 | {(Object.keys(icons) as Array) 27 | .filter(icon => icon.includes(value) || !value) 28 | .map((icon, index) => { 29 | const IconComponent = icons[icon] 30 | return ( 31 | 32 | 36 | 37 | 38 | ) 39 | })} 40 | 41 | 42 | ) 43 | } 44 | 45 | export default IconControl 46 | -------------------------------------------------------------------------------- /src/components/inspector/controls/NumberControl.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode, useCallback } from 'react' 2 | import { NumberInput, NumberInputProps } from '@chakra-ui/react' 3 | import FormControl from './FormControl' 4 | import { useForm } from '~hooks/useForm' 5 | import usePropsSelector from '~hooks/usePropsSelector' 6 | 7 | type NumberControlPropsType = NumberInputProps & { 8 | name: string 9 | label: string | ReactNode 10 | } 11 | 12 | const NumberControl: React.FC = ({ 13 | name, 14 | label, 15 | ...props 16 | }) => { 17 | const { setValue } = useForm() 18 | const value = usePropsSelector(name) 19 | 20 | const onChange = useCallback( 21 | (val: React.ReactText) => { 22 | setValue(name, val) 23 | }, 24 | [name, setValue], 25 | ) 26 | 27 | return ( 28 | 29 | 30 | 31 | ) 32 | } 33 | 34 | export default NumberControl 35 | -------------------------------------------------------------------------------- /src/components/inspector/controls/SizeControl.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react' 2 | import FormControl from './FormControl' 3 | import { Select } from '@chakra-ui/react' 4 | import { useForm } from '~hooks/useForm' 5 | 6 | export type Size = '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' 7 | type SizeControlPropsType = { 8 | name?: string 9 | label?: string | ReactNode 10 | value: string 11 | options?: Size[] 12 | } 13 | 14 | const options = ['xs', 'sm', 'md', 'lg'] 15 | 16 | const SizeControl = (props: SizeControlPropsType) => { 17 | const { setValueFromEvent } = useForm() 18 | const choices = props.options || options 19 | 20 | return ( 21 | 22 | 33 | 34 | ) 35 | } 36 | 37 | export default SizeControl 38 | -------------------------------------------------------------------------------- /src/components/inspector/controls/SwitchControl.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react' 2 | import { Switch } from '@chakra-ui/react' 3 | import FormControl from './FormControl' 4 | import { useForm } from '~hooks/useForm' 5 | import usePropsSelector from '~hooks/usePropsSelector' 6 | 7 | type SwitchControlPropsType = { 8 | name: string 9 | label: string | ReactNode 10 | } 11 | 12 | const SwitchControl: React.FC = ({ name, label }) => { 13 | const { setValue } = useForm() 14 | const value = usePropsSelector(name) 15 | 16 | return ( 17 | 18 | setValue(name, !value)} 24 | /> 25 | 26 | ) 27 | } 28 | 29 | export default SwitchControl 30 | -------------------------------------------------------------------------------- /src/components/inspector/controls/TextControl.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react' 2 | import { Input } from '@chakra-ui/react' 3 | import FormControl from './FormControl' 4 | import { useForm } from '~hooks/useForm' 5 | import usePropsSelector from '~hooks/usePropsSelector' 6 | 7 | type TextControlPropsType = { 8 | name: string 9 | label: string | ReactNode 10 | autoFocus?: boolean 11 | hasColumn?: boolean 12 | placeholder?: string 13 | } 14 | 15 | const TextControl: React.FC = ({ 16 | name, 17 | label, 18 | autoFocus = false, 19 | hasColumn = false, 20 | placeholder = '', 21 | }) => { 22 | const { setValueFromEvent } = useForm() 23 | const value = usePropsSelector(name) 24 | 25 | return ( 26 | 27 | 40 | 41 | ) 42 | } 43 | 44 | export default TextControl 45 | -------------------------------------------------------------------------------- /src/components/inspector/controls/VariantsControl.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react' 2 | import { Select } from '@chakra-ui/react' 3 | import FormControl from './FormControl' 4 | import { useForm } from '~hooks/useForm' 5 | 6 | type VariantsControlPropsType = { 7 | name: string 8 | label: string | ReactNode 9 | value: string 10 | } 11 | 12 | const VariantsControl = (props: VariantsControlPropsType) => { 13 | const { setValueFromEvent } = useForm() 14 | 15 | return ( 16 | 17 | 31 | 32 | ) 33 | } 34 | 35 | export default VariantsControl 36 | -------------------------------------------------------------------------------- /src/components/inspector/elements-list/ElementListItem.tsx: -------------------------------------------------------------------------------- 1 | import React, { forwardRef } from 'react' 2 | import { Text, Flex, BoxProps, Box } from '@chakra-ui/react' 3 | import { SettingsIcon, ArrowUpDownIcon } from '@chakra-ui/icons' 4 | import ActionButton from '~components/inspector/ActionButton' 5 | 6 | interface Props extends Pick { 7 | opacity?: number 8 | onSelect: () => void 9 | onMouseOver: BoxProps['onMouseOver'] 10 | onMouseOut: BoxProps['onMouseOut'] 11 | draggable?: boolean 12 | name?: string 13 | } 14 | 15 | const ElementListItem = forwardRef( 16 | ( 17 | { 18 | type, 19 | opacity = 1, 20 | onSelect, 21 | onMouseOut, 22 | onMouseOver, 23 | draggable, 24 | name, 25 | }: Props, 26 | ref: React.Ref, 27 | ) => { 28 | return ( 29 | 43 | 44 | 45 | {draggable && } 46 | 47 | {name || type} 48 | 49 | 50 | } 54 | colorScheme="blackAlpha" 55 | /> 56 | 57 | 58 | ) 59 | }, 60 | ) 61 | 62 | ElementListItem.displayName = 'ElementListItem' 63 | 64 | export default ElementListItem 65 | -------------------------------------------------------------------------------- /src/components/inspector/elements-list/ElementListItemDraggable.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react' 2 | import { XYCoord, useDrop, DragObjectWithType, useDrag } from 'react-dnd' 3 | import ElementListItem from './ElementListItem' 4 | 5 | interface Props extends Pick { 6 | index: number 7 | moveItem?: (dragIndex: number, hoverIndex: number) => void 8 | onSelect: (id: IComponent['id']) => void 9 | onHover: (id: IComponent['id']) => void 10 | onUnhover: () => void 11 | name?: string 12 | } 13 | 14 | const ITEM_TYPE = 'elementItem' 15 | 16 | const ElementListItemDraggable: React.FC = ({ 17 | type, 18 | id, 19 | onSelect, 20 | moveItem, 21 | index, 22 | onHover, 23 | onUnhover, 24 | name, 25 | }) => { 26 | const ref = useRef(null) 27 | const [, drop] = useDrop({ 28 | accept: ITEM_TYPE, 29 | hover(item: DragObjectWithType, monitor) { 30 | if (!ref.current) { 31 | return 32 | } 33 | // @ts-ignore 34 | const dragIndex = item.index 35 | const hoverIndex = index 36 | if (dragIndex === hoverIndex) { 37 | return 38 | } 39 | const hoverBoundingRect = ref.current.getBoundingClientRect() 40 | const hoverMiddleY = 41 | (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2 42 | const clientOffset = monitor.getClientOffset() 43 | const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top 44 | if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) { 45 | return 46 | } 47 | if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) { 48 | return 49 | } 50 | if (moveItem) { 51 | moveItem(dragIndex, hoverIndex) 52 | } 53 | // @ts-ignore 54 | item.index = hoverIndex 55 | }, 56 | }) 57 | const [{ isDragging }, drag] = useDrag({ 58 | item: { type: ITEM_TYPE, id, index }, 59 | collect: monitor => ({ 60 | isDragging: monitor.isDragging(), 61 | }), 62 | }) 63 | 64 | const opacity = isDragging ? 0 : 1 65 | 66 | drag(drop(ref)) 67 | 68 | const onSelectElement = () => { 69 | onSelect(id) 70 | } 71 | 72 | const onMouseOver = () => { 73 | onHover(id) 74 | } 75 | 76 | return ( 77 | 87 | ) 88 | } 89 | 90 | export default ElementListItemDraggable 91 | -------------------------------------------------------------------------------- /src/components/inspector/elements-list/ElementsList.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Box } from '@chakra-ui/react' 3 | import ElementListItem from './ElementListItemDraggable' 4 | 5 | interface Props { 6 | elements: IComponent[] 7 | moveItem: (fromIndex: number, toIndex: number) => void 8 | onSelect: (id: IComponent['id']) => void 9 | onHover: (id: IComponent['id']) => void 10 | onUnhover: () => void 11 | } 12 | 13 | const ElementsList: React.FC = ({ 14 | elements, 15 | moveItem, 16 | onSelect, 17 | onHover, 18 | onUnhover, 19 | }) => { 20 | return ( 21 | 22 | {elements.map( 23 | (element, index) => 24 | element && ( 25 | 36 | ), 37 | )} 38 | 39 | ) 40 | } 41 | 42 | export default ElementsList 43 | -------------------------------------------------------------------------------- /src/components/inspector/inputs/InputSuggestion.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, ReactNode } from 'react' 2 | import { 3 | Combobox, 4 | ComboboxInput, 5 | ComboboxPopover, 6 | ComboboxList, 7 | } from '@reach/combobox' 8 | import { Input } from '@chakra-ui/react' 9 | import { useForm } from '~hooks/useForm' 10 | 11 | type FormControlPropType = { 12 | handleChange: any 13 | value: any 14 | name: string 15 | children: ReactNode 16 | } 17 | 18 | const ltrim = (value: string) => { 19 | if (!value) return value 20 | return value.replace(/^\s+/g, '') 21 | } 22 | 23 | const InputSuggestion: React.FC = ({ 24 | handleChange, 25 | name, 26 | value, 27 | children, 28 | }) => { 29 | const { setValue } = useForm() 30 | const [isFocus, setIsFocus] = useState(false) 31 | 32 | return ( 33 | { 36 | setValue(name, item) 37 | }} 38 | > 39 | setIsFocus(true)} 41 | id={name} 42 | value={ltrim(value)} 43 | name={name} 44 | onChange={handleChange} 45 | as={Input} 46 | aria-labelledby={name} 47 | size="sm" 48 | autoComplete="off" 49 | /> 50 | 51 | {isFocus && ( 52 | 53 | 57 | {children} 58 | 59 | 60 | )} 61 | 62 | ) 63 | } 64 | 65 | export default InputSuggestion 66 | -------------------------------------------------------------------------------- /src/components/inspector/panels/CustomPropsPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo, useState, FormEvent, ChangeEvent, useRef } from 'react' 2 | import { useInspectorState } from '~contexts/inspector-context' 3 | import { getSelectedComponent } from '~core/selectors/components' 4 | import { useSelector } from 'react-redux' 5 | import { IoIosFlash } from 'react-icons/io' 6 | import { 7 | IconButton, 8 | Flex, 9 | Box, 10 | SimpleGrid, 11 | InputGroup, 12 | InputRightElement, 13 | Input, 14 | ButtonGroup, 15 | } from '@chakra-ui/react' 16 | import { EditIcon, SmallCloseIcon } from '@chakra-ui/icons' 17 | import useDispatch from '~hooks/useDispatch' 18 | import { useForm } from '~hooks/useForm' 19 | 20 | const SEPARATOR = '=' 21 | 22 | const CustomPropsPanel = () => { 23 | const dispatch = useDispatch() 24 | const inputRef = useRef(null) 25 | 26 | const activePropsRef = useInspectorState() 27 | const { props, id } = useSelector(getSelectedComponent) 28 | const { setValue } = useForm() 29 | 30 | const [quickProps, setQuickProps] = useState('') 31 | const [hasError, setError] = useState(false) 32 | 33 | const onDelete = (propsName: string) => { 34 | dispatch.components.deleteProps({ 35 | id, 36 | name: propsName, 37 | }) 38 | } 39 | 40 | const activeProps = activePropsRef || [] 41 | const customProps = Object.keys(props).filter( 42 | propsName => !activeProps.includes(propsName), 43 | ) 44 | 45 | return ( 46 | <> 47 |
{ 49 | event.preventDefault() 50 | 51 | const [name, value] = quickProps.split(SEPARATOR) 52 | 53 | if (name && value) { 54 | setValue(name, value) 55 | setQuickProps('') 56 | setError(false) 57 | } else { 58 | setError(true) 59 | } 60 | }} 61 | > 62 | 63 | 64 | 65 | 66 | ) => 72 | setQuickProps(event.target.value) 73 | } 74 | /> 75 | 76 |
77 | 78 | {customProps.map((propsName, i) => ( 79 | 87 | 88 | {propsName} 89 | {props[propsName]} 90 | 91 | 92 | 93 | { 95 | setQuickProps(`${propsName}=`) 96 | if (inputRef.current) { 97 | inputRef.current.focus() 98 | } 99 | }} 100 | variant="ghost" 101 | size="xs" 102 | aria-label="edit" 103 | icon={} 104 | /> 105 | onDelete(propsName)} 107 | variant="ghost" 108 | size="xs" 109 | aria-label="delete" 110 | icon={} 111 | /> 112 | 113 | 114 | ))} 115 | 116 | ) 117 | } 118 | 119 | export default memo(CustomPropsPanel) 120 | -------------------------------------------------------------------------------- /src/components/inspector/panels/StylesPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import { Accordion } from '@chakra-ui/react' 3 | import PaddingPanel from '~components/inspector/panels/styles/PaddingPanel' 4 | import DimensionPanel from '~components/inspector/panels/styles/DimensionPanel' 5 | import BorderPanel from '~components/inspector/panels/styles/BorderPanel' 6 | import DisplayPanel from '~components/inspector/panels/styles/DisplayPanel' 7 | import TextPanel from '~components/inspector/panels/styles/TextPanel' 8 | import AccordionContainer from '~components/inspector/AccordionContainer' 9 | import ColorsControl from '~components/inspector/controls/ColorsControl' 10 | import GradientControl from '~components/inspector/controls/GradientControl' 11 | import EffectsPanel from './styles/EffectsPanel' 12 | import ChildrenInspector from '~components/inspector/ChildrenInspector' 13 | import ParentInspector from '~components/inspector/ParentInspector' 14 | import CustomPropsPanel from './CustomPropsPanel' 15 | 16 | interface Props { 17 | isRoot: boolean 18 | showChildren: boolean 19 | parentIsRoot: boolean 20 | } 21 | 22 | const StylesPanel: React.FC = ({ 23 | isRoot, 24 | showChildren, 25 | parentIsRoot, 26 | }) => ( 27 | 28 | {!isRoot && ( 29 | 30 | 31 | 32 | )} 33 | 34 | {!isRoot && !parentIsRoot && ( 35 | 36 | 37 | 38 | )} 39 | 40 | {showChildren && ( 41 | 42 | 43 | 44 | )} 45 | 46 | {!isRoot && ( 47 | <> 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | )} 63 | 64 | 65 | 71 | {!isRoot && ( 72 | 88 | )} 89 | 90 | 91 | {!isRoot && ( 92 | <> 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | )} 102 | 103 | ) 104 | 105 | export default memo(StylesPanel) 106 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/AccordionItemPanel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import SwitchControl from '~components/inspector/controls/SwitchControl' 3 | 4 | const AccordionItemPanel = () => { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | ) 11 | } 12 | 13 | export default AccordionItemPanel 14 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/AccordionPanel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import SwitchControl from '~components/inspector/controls/SwitchControl' 3 | 4 | const AccordionPanel = () => { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | ) 11 | } 12 | 13 | export default AccordionPanel 14 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/AlertDescriptionPanel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ChildrenControl from '~components/inspector/controls/ChildrenControl' 3 | 4 | const AlertDescriptionPanel = () => { 5 | return 6 | } 7 | 8 | export default AlertDescriptionPanel 9 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/AlertIconPanel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import TextControl from '~components/inspector/controls/TextControl' 3 | 4 | const AlertIconPanel = () => { 5 | return 6 | } 7 | 8 | export default AlertIconPanel 9 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/AlertPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import { Select } from '@chakra-ui/react' 3 | import FormControl from '~components/inspector/controls/FormControl' 4 | import { useForm } from '~hooks/useForm' 5 | import usePropsSelector from '~hooks/usePropsSelector' 6 | 7 | const AlertPanel = () => { 8 | const { setValueFromEvent } = useForm() 9 | const variant = usePropsSelector('variant') 10 | const status = usePropsSelector('status') 11 | 12 | return ( 13 | <> 14 | 15 | 27 | 28 | 29 | 30 | 42 | 43 | 44 | ) 45 | } 46 | 47 | export default memo(AlertPanel) 48 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/AlertTitlePanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import SizeControl from '~components/inspector/controls/SizeControl' 3 | import ChildrenControl from '~components/inspector/controls/ChildrenControl' 4 | import usePropsSelector from '~hooks/usePropsSelector' 5 | 6 | const AlertTitlePanel = () => { 7 | const fontSize = usePropsSelector('fontSize') 8 | 9 | return ( 10 | <> 11 | 12 | 13 | 14 | ) 15 | } 16 | 17 | export default memo(AlertTitlePanel) 18 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/AspectRatioPanel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import NumberControl from '~components/inspector/controls/NumberControl' 3 | 4 | const AspectRatioPanel = () => { 5 | return ( 6 | <> 7 | 8 | 9 | ) 10 | } 11 | 12 | export default AspectRatioPanel 13 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/AvatarBadgePanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import ColorsControl from '~components/inspector/controls/ColorsControl' 3 | 4 | const AvatarPanel = () => ( 5 | <> 6 | 12 | 13 | 19 | 20 | ) 21 | 22 | export default memo(AvatarPanel) 23 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/AvatarGroupPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import FormControl from '~components/inspector/controls/FormControl' 3 | import { 4 | SliderTrack, 5 | SliderThumb, 6 | NumberInput, 7 | NumberInputField, 8 | NumberInputStepper, 9 | NumberIncrementStepper, 10 | NumberDecrementStepper, 11 | SliderFilledTrack, 12 | Select, 13 | Slider, 14 | } from '@chakra-ui/react' 15 | import { useForm } from '~hooks/useForm' 16 | import usePropsSelector from '~hooks/usePropsSelector' 17 | 18 | const AvatarGroupPanel = () => { 19 | const { setValue, setValueFromEvent } = useForm() 20 | 21 | const size = usePropsSelector('size') 22 | const spacing = usePropsSelector('spacing') 23 | const max = usePropsSelector('max') 24 | 25 | return ( 26 | <> 27 | 28 | 43 | 44 | 45 | 46 | setValue('spacing', value)} 48 | min={-3} 49 | max={6} 50 | step={1} 51 | defaultValue={spacing} 52 | > 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | setValue('max', value)} 64 | value={max} 65 | min={1} 66 | > 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | ) 76 | } 77 | 78 | export default memo(AvatarGroupPanel) 79 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/AvatarPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import { Select } from '@chakra-ui/react' 3 | import FormControl from '~components/inspector/controls/FormControl' 4 | import { useForm } from '~hooks/useForm' 5 | import usePropsSelector from '~hooks/usePropsSelector' 6 | import SwitchControl from '~components/inspector/controls/SwitchControl' 7 | import TextControl from '~components/inspector/controls/TextControl' 8 | 9 | const AvatarPanel = () => { 10 | const { setValueFromEvent } = useForm() 11 | const size = usePropsSelector('size') 12 | 13 | return ( 14 | <> 15 | 16 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | ) 38 | } 39 | 40 | export default memo(AvatarPanel) 41 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/BadgePanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import { Select } from '@chakra-ui/react' 3 | 4 | import ColorsControl from '~components/inspector/controls/ColorsControl' 5 | import ChildrenControl from '~components/inspector/controls/ChildrenControl' 6 | import FormControl from '~components/inspector/controls/FormControl' 7 | import { useForm } from '~hooks/useForm' 8 | import usePropsSelector from '~hooks/usePropsSelector' 9 | 10 | const BadgePanel = () => { 11 | const { setValueFromEvent } = useForm() 12 | const variant = usePropsSelector('variant') 13 | 14 | return ( 15 | <> 16 | 17 | 18 | 19 | 30 | 31 | 32 | 33 | 34 | ) 35 | } 36 | 37 | export default memo(BadgePanel) 38 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/BoxPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import ColorsControl from '~components/inspector/controls/ColorsControl' 3 | 4 | const BoxPanel = () => ( 5 | 11 | ) 12 | 13 | export default memo(BoxPanel) 14 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/BreadcrumbItemPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | 3 | import SwitchControl from '~components/inspector/controls/SwitchControl' 4 | 5 | const BreadcrumbItemPanel = () => { 6 | return ( 7 | <> 8 | 9 | 10 | ) 11 | } 12 | 13 | export default memo(BreadcrumbItemPanel) 14 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/BreadcrumbPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import TextControl from '~components/inspector/controls/TextControl' 3 | 4 | const BreadcrumbPanel = () => { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | ) 11 | } 12 | 13 | export default memo(BreadcrumbPanel) 14 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/ButtonPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import ColorsControl from '~components/inspector/controls/ColorsControl' 3 | import SizeControl from '~components/inspector/controls/SizeControl' 4 | import { Select } from '@chakra-ui/react' 5 | import ChildrenControl from '~components/inspector/controls/ChildrenControl' 6 | import FormControl from '~components/inspector/controls/FormControl' 7 | import { useForm } from '~hooks/useForm' 8 | import usePropsSelector from '~hooks/usePropsSelector' 9 | import IconControl from '~components/inspector/controls/IconControl' 10 | 11 | const ButtonPanel = () => { 12 | const { setValueFromEvent } = useForm() 13 | 14 | const size = usePropsSelector('size') 15 | const variant = usePropsSelector('variant') 16 | 17 | return ( 18 | <> 19 | 20 | 21 | 22 | 23 | 24 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | ) 44 | } 45 | 46 | export default memo(ButtonPanel) 47 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/CheckboxPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import ColorsControl from '~components/inspector/controls/ColorsControl' 3 | import ChildrenControl from '~components/inspector/controls/ChildrenControl' 4 | import { useForm } from '~hooks/useForm' 5 | import FormControl from '~components/inspector/controls/FormControl' 6 | import { Select } from '@chakra-ui/react' 7 | import usePropsSelector from '~hooks/usePropsSelector' 8 | import SwitchControl from '~components/inspector/controls/SwitchControl' 9 | 10 | const CheckboxPanel = () => { 11 | const { setValueFromEvent } = useForm() 12 | const size = usePropsSelector('size') 13 | 14 | return ( 15 | <> 16 | 17 | 18 | 19 | 20 | 31 | 32 | 33 | ) 34 | } 35 | 36 | export default memo(CheckboxPanel) 37 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/CircularProgressPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import { 3 | SliderTrack, 4 | SliderFilledTrack, 5 | Slider, 6 | SliderThumb, 7 | } from '@chakra-ui/react' 8 | import FormControl from '~components/inspector/controls/FormControl' 9 | import { useForm } from '~hooks/useForm' 10 | import ColorsControl from '~components/inspector/controls/ColorsControl' 11 | import usePropsSelector from '~hooks/usePropsSelector' 12 | import SwitchControl from '~components/inspector/controls/SwitchControl' 13 | import TextControl from '~components/inspector/controls/TextControl' 14 | 15 | const CircularProgressPanel = () => { 16 | const { setValue } = useForm() 17 | 18 | const value = usePropsSelector('value') 19 | const thickness = usePropsSelector('thickness') 20 | 21 | return ( 22 | <> 23 | 24 | setValue('value', value)} 26 | min={0} 27 | max={100} 28 | step={1} 29 | value={value || 100} 30 | > 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | setValue('thickness', value)} 43 | min={0.1} 44 | max={1} 45 | step={0.1} 46 | defaultValue={thickness} 47 | > 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | ) 59 | } 60 | 61 | export default memo(CircularProgressPanel) 62 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/CloseButtonPanel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Select } from '@chakra-ui/react' 3 | import ColorsControl from '~components/inspector/controls/ColorsControl' 4 | import FormControl from '~components/inspector/controls/FormControl' 5 | import { useForm } from '~hooks/useForm' 6 | import usePropsSelector from '~hooks/usePropsSelector' 7 | 8 | const CloseButtonPanel = () => { 9 | const { setValueFromEvent } = useForm() 10 | 11 | const size = usePropsSelector('size') 12 | 13 | return ( 14 | <> 15 | 16 | 27 | 28 | 29 | 30 | 31 | ) 32 | } 33 | 34 | export default CloseButtonPanel 35 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/CodePanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import ColorsControl from '~components/inspector/controls/ColorsControl' 3 | import ChildrenControl from '~components/inspector/controls/ChildrenControl' 4 | 5 | const CodePanel = () => { 6 | return ( 7 | <> 8 | 9 | 10 | 11 | ) 12 | } 13 | 14 | export default memo(CodePanel) 15 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/DividerPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import { Select } from '@chakra-ui/react' 3 | import FormControl from '~components/inspector/controls/FormControl' 4 | import { useForm } from '~hooks/useForm' 5 | import ColorsControl from '~components/inspector/controls/ColorsControl' 6 | import usePropsSelector from '~hooks/usePropsSelector' 7 | 8 | const DividerPanel = () => { 9 | const { setValueFromEvent } = useForm() 10 | const orientation = usePropsSelector('orientation') 11 | 12 | return ( 13 | <> 14 | 15 | 25 | 26 | 32 | 33 | ) 34 | } 35 | 36 | export default memo(DividerPanel) 37 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/FormControlPanel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import SwitchControl from '~components/inspector/controls/SwitchControl' 3 | 4 | const FormControlPanel = () => { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | 11 | ) 12 | } 13 | 14 | export default FormControlPanel 15 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/FormErrorMessagePanel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ChildrenControl from '~components/inspector/controls/ChildrenControl' 3 | 4 | const FormErrorMessagePanel = () => 5 | 6 | export default FormErrorMessagePanel 7 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/FormHelperTextPanel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ChildrenControl from '~components/inspector/controls/ChildrenControl' 3 | 4 | const FormHelperTextPanel = () => { 5 | return ( 6 | <> 7 | 8 | 9 | ) 10 | } 11 | 12 | export default FormHelperTextPanel 13 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/FormLabelPanel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ChildrenControl from '~components/inspector/controls/ChildrenControl' 3 | 4 | const FormLabelPanel = () => { 5 | return ( 6 | <> 7 | 8 | 9 | ) 10 | } 11 | 12 | export default FormLabelPanel 13 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/GridPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import TextControl from '~components/inspector/controls/TextControl' 3 | 4 | const GridPanel = () => { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ) 21 | } 22 | 23 | export default memo(GridPanel) 24 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/HeadingPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import { Select } from '@chakra-ui/react' 3 | import { useForm } from '~hooks/useForm' 4 | import FormControl from '~components/inspector/controls/FormControl' 5 | import ChildrenControl from '~components/inspector/controls/ChildrenControl' 6 | import usePropsSelector from '~hooks/usePropsSelector' 7 | import SwitchControl from '~components/inspector/controls/SwitchControl' 8 | 9 | const HeadingPanel = () => { 10 | const { setValueFromEvent } = useForm() 11 | 12 | const size = usePropsSelector('size') 13 | const as = usePropsSelector('as') 14 | 15 | return ( 16 | <> 17 | 18 | 19 | 33 | 34 | 35 | 48 | 49 | 50 | 51 | 52 | ) 53 | } 54 | 55 | export default memo(HeadingPanel) 56 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/HighlightPanel.tsx: -------------------------------------------------------------------------------- 1 | import { memo } from 'react' 2 | import ChildrenControl from '~components/inspector/controls/ChildrenControl' 3 | import TextControl from '~components/inspector/controls/TextControl' 4 | 5 | const HighlightPanel = () => ( 6 | <> 7 | 8 | 9 | 10 | ) 11 | 12 | export default memo(HighlightPanel) 13 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/IconButtonPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import ColorsControl from '~components/inspector/controls/ColorsControl' 3 | import VariantsControl from '~components/inspector/controls/VariantsControl' 4 | import SizeControl from '~components/inspector/controls/SizeControl' 5 | import usePropsSelector from '~hooks/usePropsSelector' 6 | import SwitchControl from '~components/inspector/controls/SwitchControl' 7 | import IconControl from '~components/inspector/controls/IconControl' 8 | 9 | const IconButtonPanel = () => { 10 | const size = usePropsSelector('size') 11 | const variant = usePropsSelector('variant') 12 | 13 | return ( 14 | <> 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ) 23 | } 24 | 25 | export default memo(IconButtonPanel) 26 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/IconPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import ColorsControl from '~components/inspector/controls/ColorsControl' 3 | import InputSuggestion from '~components/inspector/inputs/InputSuggestion' 4 | import theme from '@chakra-ui/theme' 5 | import { ComboboxOption } from '@reach/combobox' 6 | import FormControl from '~components/inspector/controls/FormControl' 7 | import { useForm } from '~hooks/useForm' 8 | import usePropsSelector from '~hooks/usePropsSelector' 9 | import IconControl from '~components/inspector/controls/IconControl' 10 | 11 | const IconPanel = () => { 12 | const { setValueFromEvent } = useForm() 13 | 14 | const boxSize = usePropsSelector('boxSize') 15 | 16 | return ( 17 | <> 18 | 19 | 20 | 21 | 26 | {Object.keys(theme.sizes).map((option, index) => ( 27 | 28 | ))} 29 | 30 | 31 | 32 | 33 | 34 | ) 35 | } 36 | 37 | export default memo(IconPanel) 38 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/ImagePanel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Input } from '@chakra-ui/react' 3 | import FormControl from '~components/inspector/controls/FormControl' 4 | import { useForm } from '~hooks/useForm' 5 | import usePropsSelector from '~hooks/usePropsSelector' 6 | 7 | const ImagePanel = () => { 8 | const { setValueFromEvent } = useForm() 9 | 10 | const src = usePropsSelector('src') 11 | const fallbackSrc = usePropsSelector('fallbackSrc') 12 | const alt = usePropsSelector('alt') 13 | const htmlHeight = usePropsSelector('htmlHeight') 14 | const htmlWidth = usePropsSelector('htmlWidth') 15 | 16 | return ( 17 | <> 18 | 19 | 26 | 27 | 28 | 29 | 36 | 37 | 38 | 39 | 45 | 46 | 47 | 48 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | ) 66 | } 67 | 68 | export default ImagePanel 69 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/InputPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import { useForm } from '~hooks/useForm' 3 | import { Select } from '@chakra-ui/react' 4 | import FormControl from '~components/inspector/controls/FormControl' 5 | import usePropsSelector from '~hooks/usePropsSelector' 6 | import SizeControl from '~components/inspector/controls/SizeControl' 7 | import SwitchControl from '~components/inspector/controls/SwitchControl' 8 | import TextControl from '~components/inspector/controls/TextControl' 9 | 10 | const InputPanel = () => { 11 | const { setValueFromEvent } = useForm() 12 | 13 | const size = usePropsSelector('size') 14 | const variant = usePropsSelector('variant') 15 | 16 | return ( 17 | <> 18 | 19 | 20 | 21 | 22 | 23 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | ) 42 | } 43 | 44 | export default memo(InputPanel) 45 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/KbdPanel.tsx: -------------------------------------------------------------------------------- 1 | import { memo } from 'react' 2 | import ChildrenControl from '~components/inspector/controls/ChildrenControl' 3 | 4 | const KdbPanel = () => ( 5 | <> 6 | 7 | 8 | ) 9 | 10 | export default memo(KdbPanel) 11 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/LinkPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import SwitchControl from '~components/inspector/controls/SwitchControl' 3 | import TextControl from '~components/inspector/controls/TextControl' 4 | import ChildrenControl from '~components/inspector/controls/ChildrenControl' 5 | 6 | const LinkPanel = () => { 7 | return ( 8 | <> 9 | 10 | 11 | 12 | 13 | ) 14 | } 15 | 16 | export default memo(LinkPanel) 17 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/ListIconPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import ColorsControl from '~components/inspector/controls/ColorsControl' 3 | import IconControl from '~components/inspector/controls/IconControl' 4 | 5 | const ListIconPanel = () => { 6 | return ( 7 | <> 8 | 9 | 10 | 11 | ) 12 | } 13 | 14 | export default memo(ListIconPanel) 15 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/ListItemPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import ChildrenControl from '~components/inspector/controls/ChildrenControl' 3 | 4 | const ListItemPanel = () => { 5 | return ( 6 | <> 7 | 8 | 9 | ) 10 | } 11 | 12 | export default memo(ListItemPanel) 13 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/ListPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import FormControl from '~components/inspector/controls/FormControl' 3 | import { Select } from '@chakra-ui/react' 4 | import { useForm } from '~hooks/useForm' 5 | import usePropsSelector from '~hooks/usePropsSelector' 6 | 7 | const CodePanel = () => { 8 | const { setValueFromEvent } = useForm() 9 | const styleType = usePropsSelector('styleType') 10 | 11 | return ( 12 | <> 13 | 14 | 30 | 31 | 32 | ) 33 | } 34 | 35 | export default memo(CodePanel) 36 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/NumberInputPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import usePropsSelector from '~hooks/usePropsSelector' 3 | import SizeControl from '~components/inspector/controls/SizeControl' 4 | import SwitchControl from '~components/inspector/controls/SwitchControl' 5 | import TextControl from '~components/inspector/controls/TextControl' 6 | import NumberControl from '~components/inspector/controls/NumberControl' 7 | 8 | const NumberInputPanel = () => { 9 | const size = usePropsSelector('size') 10 | 11 | return ( 12 | <> 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ) 23 | } 24 | 25 | export default memo(NumberInputPanel) 26 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/ProgressPanel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | SliderTrack, 4 | SliderFilledTrack, 5 | Slider, 6 | SliderThumb, 7 | } from '@chakra-ui/react' 8 | import ColorsControl from '~components/inspector/controls/ColorsControl' 9 | import FormControl from '~components/inspector/controls/FormControl' 10 | import { useForm } from '~hooks/useForm' 11 | import SizeControl from '~components/inspector/controls/SizeControl' 12 | import usePropsSelector from '~hooks/usePropsSelector' 13 | import SwitchControl from '~components/inspector/controls/SwitchControl' 14 | 15 | const ProgressPanel = () => { 16 | const { setValue } = useForm() 17 | 18 | const value = usePropsSelector('value') 19 | const size = usePropsSelector('size') 20 | 21 | return ( 22 | <> 23 | 24 | setValue('value', value)} 26 | min={0} 27 | max={100} 28 | step={1} 29 | defaultValue={value} 30 | > 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | ) 46 | } 47 | 48 | export default ProgressPanel 49 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/RadioGroupPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import usePropsSelector from '~hooks/usePropsSelector' 3 | import SwitchControl from '~components/inspector/controls/SwitchControl' 4 | import { Input } from '@chakra-ui/react' 5 | import { useForm } from '~hooks/useForm' 6 | import FormControl from '~components/inspector/controls/FormControl' 7 | 8 | const RadioGroupPanel = () => { 9 | const { setValueFromEvent } = useForm() 10 | const spacing = usePropsSelector('spacing') 11 | 12 | return ( 13 | <> 14 | 15 | 21 | 22 | 23 | 24 | ) 25 | } 26 | 27 | export default memo(RadioGroupPanel) 28 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/RadioPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import usePropsSelector from '~hooks/usePropsSelector' 3 | import SizeControl from '~components/inspector/controls/SizeControl' 4 | import ColorsControl from '~components/inspector/controls/ColorsControl' 5 | import SwitchControl from '~components/inspector/controls/SwitchControl' 6 | 7 | const RadioPanel = () => { 8 | const size = usePropsSelector('size') 9 | 10 | return ( 11 | <> 12 | 13 | 14 | 15 | 16 | 17 | 18 | ) 19 | } 20 | 21 | export default memo(RadioPanel) 22 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/SelectPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import { Select } from '@chakra-ui/react' 3 | import FormControl from '~components/inspector/controls/FormControl' 4 | import { useForm } from '~hooks/useForm' 5 | import usePropsSelector from '~hooks/usePropsSelector' 6 | import SwitchControl from '~components/inspector/controls/SwitchControl' 7 | import TextControl from '~components/inspector/controls/TextControl' 8 | import IconControl from '~components/inspector/controls/IconControl' 9 | 10 | const SelectPanel = () => { 11 | const { setValueFromEvent } = useForm() 12 | 13 | const size = usePropsSelector('size') 14 | const variant = usePropsSelector('variant') 15 | 16 | return ( 17 | <> 18 | 19 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 48 | 49 | 50 | 51 | 52 | 53 | ) 54 | } 55 | 56 | export default memo(SelectPanel) 57 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/SimpleGridPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import TextControl from '~components/inspector/controls/TextControl' 3 | 4 | const SimpleGridPanel = () => { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | 11 | 12 | ) 13 | } 14 | 15 | export default memo(SimpleGridPanel) 16 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/SkeletonPanel.tsx: -------------------------------------------------------------------------------- 1 | import { memo } from 'react' 2 | import ColorPickerControl from '~components/inspector/controls/ColorPickerControl' 3 | import ColorsControl from '~components/inspector/controls/ColorsControl' 4 | import SwitchControl from '~components/inspector/controls/SwitchControl' 5 | import TextControl from '~components/inspector/controls/TextControl' 6 | import usePropsSelector from '~hooks/usePropsSelector' 7 | 8 | interface SkeletonPanelProps { 9 | isSkeletonText?: boolean 10 | isSkeletonCircle?: boolean 11 | } 12 | 13 | const SkeletonPanel = ({ 14 | isSkeletonText, 15 | isSkeletonCircle, 16 | }: SkeletonPanelProps) => { 17 | return ( 18 | <> 19 | 25 | 26 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | {isSkeletonText && ( 39 | <> 40 | 41 | 42 | 43 | )} 44 | 45 | {isSkeletonCircle && } 46 | 47 | ) 48 | } 49 | 50 | export default memo(SkeletonPanel) 51 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/SpinnerPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import { Select } from '@chakra-ui/react' 3 | import ColorsControl from '~components/inspector/controls/ColorsControl' 4 | import { useForm } from '~hooks/useForm' 5 | import FormControl from '~components/inspector/controls/FormControl' 6 | import usePropsSelector from '~hooks/usePropsSelector' 7 | import TextControl from '~components/inspector/controls/TextControl' 8 | 9 | const SpinnerPanel = () => { 10 | const { setValueFromEvent } = useForm() 11 | 12 | const size = usePropsSelector('size') 13 | 14 | return ( 15 | <> 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 36 | 37 | 38 | 39 | 40 | 41 | ) 42 | } 43 | 44 | export default memo(SpinnerPanel) 45 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/StackPanel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import SwitchControl from '~components/inspector/controls/SwitchControl' 3 | import TextControl from '~components/inspector/controls/TextControl' 4 | import FormControl from '~components/inspector/controls/FormControl' 5 | import { Select } from '@chakra-ui/react' 6 | import usePropsSelector from '~hooks/usePropsSelector' 7 | import { useForm } from '~hooks/useForm' 8 | 9 | const StackPanel = () => { 10 | const { setValueFromEvent } = useForm() 11 | 12 | const alignItems = usePropsSelector('alignItems') 13 | const justifyContent = usePropsSelector('justifyContent') 14 | 15 | return ( 16 | <> 17 | 18 | 19 | 20 | 21 | 33 | 34 | 35 | 48 | {' '} 49 | 50 | ) 51 | } 52 | 53 | export default StackPanel 54 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/StatArrowPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import { Select } from '@chakra-ui/react' 3 | import FormControl from '~components/inspector/controls/FormControl' 4 | import { useForm } from '~hooks/useForm' 5 | import usePropsSelector from '~hooks/usePropsSelector' 6 | 7 | const StatArrowPanel = () => { 8 | const { setValueFromEvent } = useForm() 9 | const type = usePropsSelector('type') 10 | 11 | return ( 12 | <> 13 | 14 | 24 | 25 | 26 | ) 27 | } 28 | 29 | export default memo(StatArrowPanel) 30 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/StatLabelPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import ChildrenControl from '~components/inspector/controls/ChildrenControl' 3 | 4 | const StatLabelPanel = () => { 5 | return ( 6 | <> 7 | 8 | 9 | ) 10 | } 11 | 12 | export default memo(StatLabelPanel) 13 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/SwitchPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import { Select } from '@chakra-ui/react' 3 | import ColorsControl from '~components/inspector/controls/ColorsControl' 4 | import FormControl from '~components/inspector/controls/FormControl' 5 | import { useForm } from '~hooks/useForm' 6 | import usePropsSelector from '~hooks/usePropsSelector' 7 | import SwitchControl from '~components/inspector/controls/SwitchControl' 8 | 9 | const SwitchPanel = () => { 10 | const { setValueFromEvent } = useForm() 11 | const size = usePropsSelector('size') 12 | 13 | return ( 14 | <> 15 | 16 | 17 | 18 | 29 | 30 | 31 | 32 | 33 | ) 34 | } 35 | 36 | export default memo(SwitchPanel) 37 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/TabPanel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import SwitchControl from '~components/inspector/controls/SwitchControl' 3 | import TextControl from '~components/inspector/controls/TextControl' 4 | 5 | const TabPanel = () => { 6 | return ( 7 | <> 8 | 9 | 10 | 11 | 12 | ) 13 | } 14 | 15 | export default TabPanel 16 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/TabsPanel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Select } from '@chakra-ui/react' 3 | import FormControl from '~components/inspector/controls/FormControl' 4 | import { useForm } from '~hooks/useForm' 5 | import ColorsControl from '~components/inspector/controls/ColorsControl' 6 | import usePropsSelector from '~hooks/usePropsSelector' 7 | import SwitchControl from '~components/inspector/controls/SwitchControl' 8 | 9 | const TabsPanel = () => { 10 | const { setValueFromEvent } = useForm() 11 | 12 | const variant = usePropsSelector('variant') 13 | const orientation = usePropsSelector('orientation') 14 | const size = usePropsSelector('size') 15 | const align = usePropsSelector('align') 16 | 17 | return ( 18 | <> 19 | 20 | 21 | 22 | 23 | 24 | 38 | 39 | 40 | 41 | 52 | 53 | 54 | 55 | 65 | 66 | 67 | 68 | 79 | 80 | 81 | 82 | ) 83 | } 84 | 85 | export default TabsPanel 86 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/TagPanel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Select } from '@chakra-ui/react' 3 | import { useForm } from '~hooks/useForm' 4 | import SizeControl from '~components/inspector/controls/SizeControl' 5 | import ChildrenControl from '~components/inspector/controls/ChildrenControl' 6 | import ColorsControl from '~components/inspector/controls/ColorsControl' 7 | import FormControl from '~components/inspector/controls/FormControl' 8 | import usePropsSelector from '~hooks/usePropsSelector' 9 | import SwitchControl from '~components/inspector/controls/SwitchControl' 10 | 11 | const TagPanel = () => { 12 | const { setValueFromEvent } = useForm() 13 | 14 | const size = usePropsSelector('size') 15 | const variant = usePropsSelector('variant') 16 | const rounded = usePropsSelector('rounded') 17 | 18 | return ( 19 | <> 20 | 21 | 27 | 28 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | ) 48 | } 49 | 50 | export default TagPanel 51 | -------------------------------------------------------------------------------- /src/components/inspector/panels/components/TextareaPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import { Input, Select } from '@chakra-ui/react' 3 | import FormControl from '~components/inspector/controls/FormControl' 4 | import { useForm } from '~hooks/useForm' 5 | import SizeControl, { Size } from '~components/inspector/controls/SizeControl' 6 | import usePropsSelector from '~hooks/usePropsSelector' 7 | 8 | const options = ['sm', 'md', 'lg'] as Size[] 9 | 10 | const TextareaPanel = () => { 11 | const { setValueFromEvent } = useForm() 12 | 13 | const placeholder = usePropsSelector('placeholder') 14 | const size = usePropsSelector('size') 15 | const resize = usePropsSelector('resize') 16 | 17 | return ( 18 | <> 19 | 20 | 27 | 28 | 29 | 30 | 41 | 42 | 43 | ) 44 | } 45 | 46 | export default memo(TextareaPanel) 47 | -------------------------------------------------------------------------------- /src/components/inspector/panels/styles/BorderPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import TextControl from '~components/inspector/controls/TextControl' 3 | 4 | const BorderPanel = () => { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | ) 11 | } 12 | 13 | export default memo(BorderPanel) 14 | -------------------------------------------------------------------------------- /src/components/inspector/panels/styles/DimensionPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import { SimpleGrid, Select } from '@chakra-ui/react' 3 | import FormControl from '~components/inspector/controls/FormControl' 4 | import usePropsSelector from '~hooks/usePropsSelector' 5 | import { useForm } from '~hooks/useForm' 6 | import TextControl from '~components/inspector/controls/TextControl' 7 | 8 | const DimensionPanel = () => { 9 | const { setValueFromEvent } = useForm() 10 | const overflow = usePropsSelector('overflow') 11 | 12 | return ( 13 | <> 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 38 | 39 | 40 | ) 41 | } 42 | 43 | export default memo(DimensionPanel) 44 | -------------------------------------------------------------------------------- /src/components/inspector/panels/styles/DisplayPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import { Select } from '@chakra-ui/react' 3 | import FormControl from '~components/inspector/controls/FormControl' 4 | import { useForm } from '~hooks/useForm' 5 | import usePropsSelector from '~hooks/usePropsSelector' 6 | import FlexPanel from './FlexPanel' 7 | 8 | const DisplayPanel = () => { 9 | const { setValueFromEvent } = useForm() 10 | const display = usePropsSelector('display') 11 | 12 | return ( 13 | <> 14 | 15 | 27 | 28 | 29 | {display === 'flex' && } 30 | 31 | ) 32 | } 33 | 34 | export default memo(DisplayPanel) 35 | -------------------------------------------------------------------------------- /src/components/inspector/panels/styles/EffectsPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo, useMemo } from 'react' 2 | import FormControl from '~components/inspector/controls/FormControl' 3 | import { useForm } from '~hooks/useForm' 4 | import usePropsSelector from '~hooks/usePropsSelector' 5 | import { 6 | SliderTrack, 7 | SliderFilledTrack, 8 | Slider, 9 | SliderThumb, 10 | } from '@chakra-ui/react' 11 | import TextControl from '~components/inspector/controls/TextControl' 12 | 13 | const EffectsPanel = () => { 14 | const { setValue } = useForm() 15 | const opacity = usePropsSelector('opacity') 16 | 17 | const normalizedOpacity = useMemo(() => { 18 | return opacity * 100 || 100 19 | }, [opacity]) 20 | 21 | return ( 22 | <> 23 | 24 | setValue('opacity', value / 100)} 27 | value={normalizedOpacity} 28 | mr={2} 29 | > 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ) 40 | } 41 | 42 | export default memo(EffectsPanel) 43 | -------------------------------------------------------------------------------- /src/components/inspector/panels/styles/FlexPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import { Select } from '@chakra-ui/react' 3 | import FormControl from '~components/inspector/controls/FormControl' 4 | import { useForm } from '~hooks/useForm' 5 | import usePropsSelector from '~hooks/usePropsSelector' 6 | 7 | const FlexPanel = () => { 8 | const { setValueFromEvent } = useForm() 9 | 10 | const alignItems = usePropsSelector('alignItems') 11 | const flexDirection = usePropsSelector('flexDirection') 12 | const justifyContent = usePropsSelector('justifyContent') 13 | 14 | return ( 15 | <> 16 | 17 | 28 | 29 | 30 | 31 | 43 | 44 | 45 | 46 | 59 | 60 | 61 | ) 62 | } 63 | 64 | export default memo(FlexPanel) 65 | -------------------------------------------------------------------------------- /src/components/inspector/panels/styles/PaddingPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import { 3 | FormControl, 4 | FormLabel, 5 | Input, 6 | SimpleGrid, 7 | InputGroup, 8 | InputLeftElement, 9 | Box, 10 | } from '@chakra-ui/react' 11 | import { 12 | ArrowBackIcon, 13 | ArrowForwardIcon, 14 | ArrowUpIcon, 15 | ChevronDownIcon, 16 | } from '@chakra-ui/icons' 17 | import { useForm } from '~hooks/useForm' 18 | import usePropsSelector from '~hooks/usePropsSelector' 19 | 20 | type PaddingPanelPropsType = { 21 | type: 'margin' | 'padding' 22 | } 23 | 24 | const ATTRIBUTES = { 25 | margin: { 26 | all: 'm', 27 | left: 'ml', 28 | right: 'mr', 29 | bottom: 'mb', 30 | top: 'mt', 31 | }, 32 | padding: { 33 | all: 'p', 34 | left: 'pl', 35 | right: 'pr', 36 | bottom: 'pb', 37 | top: 'pt', 38 | }, 39 | } 40 | 41 | const PaddingPanel = ({ type }: PaddingPanelPropsType) => { 42 | const { setValueFromEvent } = useForm() 43 | 44 | const all = usePropsSelector(ATTRIBUTES[type].all) 45 | const left = usePropsSelector(ATTRIBUTES[type].left) 46 | const right = usePropsSelector(ATTRIBUTES[type].right) 47 | const bottom = usePropsSelector(ATTRIBUTES[type].bottom) 48 | const top = usePropsSelector(ATTRIBUTES[type].top) 49 | 50 | return ( 51 | 52 | 53 | 54 | {type} 55 | 56 | 57 | 58 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 128 | 129 | 130 | 131 | 132 | ) 133 | } 134 | 135 | export default memo(PaddingPanel) 136 | -------------------------------------------------------------------------------- /src/components/sidebar/DragItem.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useDrag } from 'react-dnd' 3 | import { Text, Box } from '@chakra-ui/react' 4 | import { DragHandleIcon } from '@chakra-ui/icons' 5 | 6 | const DragItem: React.FC = ({ 7 | type, 8 | soon, 9 | label, 10 | isMeta, 11 | isChild, 12 | rootParentType, 13 | }) => { 14 | const [, drag] = useDrag({ 15 | item: { id: type, type, isMeta, rootParentType }, 16 | }) 17 | 18 | let boxProps: any = { 19 | cursor: 'no-drop', 20 | color: 'whiteAlpha.600', 21 | } 22 | 23 | if (!soon) { 24 | boxProps = { 25 | ref: drag, 26 | color: 'whiteAlpha.800', 27 | cursor: 'move', 28 | _hover: { 29 | ml: -1, 30 | mr: 1, 31 | bg: 'teal.100', 32 | boxShadow: 'sm', 33 | color: 'teal.800', 34 | }, 35 | } 36 | } 37 | 38 | if (isChild) { 39 | boxProps = { ...boxProps, ml: 4 } 40 | } 41 | 42 | return ( 43 | 53 | 54 | 55 | {label} 56 | 57 | {isMeta && ( 58 | 67 | preset 68 | 69 | )} 70 | {soon && ( 71 | 80 | soon 81 | 82 | )} 83 | 84 | ) 85 | } 86 | 87 | export default DragItem 88 | -------------------------------------------------------------------------------- /src/components/sidebar/Sidebar.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, ChangeEvent, memo } from 'react' 2 | import { 3 | Box, 4 | Input, 5 | InputGroup, 6 | InputRightElement, 7 | DarkMode, 8 | IconButton, 9 | Flex, 10 | } from '@chakra-ui/react' 11 | import { CloseIcon, SearchIcon } from '@chakra-ui/icons' 12 | import DragItem from './DragItem' 13 | import { menuItems, MenuItem } from '~componentsList' 14 | 15 | const Menu = () => { 16 | const [searchTerm, setSearchTerm] = useState('') 17 | 18 | return ( 19 | 20 | 32 | 33 | 34 | ) => 39 | setSearchTerm(event.target.value) 40 | } 41 | borderColor="rgba(255, 255, 255, 0.04)" 42 | bg="rgba(255, 255, 255, 0.06)" 43 | _hover={{ 44 | borderColor: 'rgba(255, 255, 255, 0.08)', 45 | }} 46 | zIndex={0} 47 | /> 48 | 49 | {searchTerm ? ( 50 | } 54 | size="xs" 55 | onClick={() => setSearchTerm('')} 56 | /> 57 | ) : ( 58 | 59 | )} 60 | 61 | 62 | 63 | 64 | {(Object.keys(menuItems) as ComponentType[]) 65 | .filter(c => c.toLowerCase().includes(searchTerm.toLowerCase())) 66 | .map(name => { 67 | const { children, soon } = menuItems[name] as MenuItem 68 | 69 | if (children) { 70 | const elements = Object.keys(children).map(childName => ( 71 | 79 | {childName} 80 | 81 | )) 82 | 83 | return [ 84 | 93 | {name} 94 | , 95 | ...elements, 96 | ] 97 | } 98 | 99 | return ( 100 | 108 | {name} 109 | 110 | ) 111 | })} 112 | 113 | 114 | 115 | ) 116 | } 117 | 118 | export default memo(Menu) 119 | -------------------------------------------------------------------------------- /src/contexts/inspector-context.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | useContext, 3 | createContext, 4 | useState, 5 | useCallback, 6 | useMemo, 7 | } from 'react' 8 | 9 | type UpdateProps = { 10 | addActiveProps: (propsName: string) => void 11 | clearActiveProps: () => void 12 | } 13 | 14 | type InspectorProviderProps = { children: React.ReactNode } 15 | 16 | const InspectorStateContext = createContext([]) 17 | const InspectorUpdateContext = createContext({ 18 | addActiveProps: () => {}, 19 | clearActiveProps: () => {}, 20 | }) 21 | 22 | function InspectorProvider({ children }: InspectorProviderProps) { 23 | const [activeProps, setActiveProps] = useState([]) 24 | 25 | const addActiveProps = useCallback((propsName: string) => { 26 | setActiveProps(prevActiveProps => [...prevActiveProps, propsName]) 27 | }, []) 28 | 29 | const clearActiveProps = useCallback(() => { 30 | setActiveProps([]) 31 | }, []) 32 | 33 | const values = useMemo(() => { 34 | return { clearActiveProps, addActiveProps } 35 | }, [addActiveProps, clearActiveProps]) 36 | 37 | return ( 38 | 39 | 40 | {children} 41 | 42 | 43 | ) 44 | } 45 | 46 | function useInspectorState() { 47 | return useContext(InspectorStateContext) 48 | } 49 | 50 | function useInspectorUpdate() { 51 | return useContext(InspectorUpdateContext) 52 | } 53 | 54 | export { InspectorProvider, useInspectorState, useInspectorUpdate } 55 | -------------------------------------------------------------------------------- /src/core/models/app.ts: -------------------------------------------------------------------------------- 1 | import { createModel } from '@rematch/core' 2 | 3 | type Overlay = undefined | { rect: DOMRect; id: string; type: ComponentType } 4 | 5 | export type AppState = { 6 | showLayout: boolean 7 | showCode: boolean 8 | inputTextFocused: boolean 9 | overlay: undefined | Overlay 10 | } 11 | 12 | const app = createModel({ 13 | state: { 14 | showLayout: true, 15 | showCode: false, 16 | inputTextFocused: false, 17 | overlay: undefined, 18 | } as AppState, 19 | reducers: { 20 | toggleBuilderMode(state: AppState): AppState { 21 | return { 22 | ...state, 23 | showLayout: !state.showLayout, 24 | } 25 | }, 26 | toggleCodePanel(state: AppState): AppState { 27 | return { 28 | ...state, 29 | showCode: !state.showCode, 30 | } 31 | }, 32 | toggleInputText(state: AppState): AppState { 33 | return { 34 | ...state, 35 | inputTextFocused: !state.inputTextFocused, 36 | } 37 | }, 38 | setOverlay(state: AppState, overlay: Overlay | undefined): AppState { 39 | return { 40 | ...state, 41 | overlay, 42 | } 43 | }, 44 | 'components/deleteComponent': (state: AppState): AppState => { 45 | return { 46 | ...state, 47 | overlay: undefined, 48 | } 49 | }, 50 | '@@redux-undo/UNDO': (state: AppState): AppState => { 51 | return { 52 | ...state, 53 | overlay: undefined, 54 | } 55 | }, 56 | }, 57 | }) 58 | 59 | export default app 60 | -------------------------------------------------------------------------------- /src/core/models/composer/composer.ts: -------------------------------------------------------------------------------- 1 | import { DEFAULT_PROPS } from '~utils/defaultProps' 2 | import { generateId } from '~utils/generateId' 3 | 4 | type AddNode = { 5 | type: ComponentType 6 | parent?: string 7 | props?: any 8 | rootParentType?: ComponentType 9 | } 10 | 11 | class Composer { 12 | components: IComponents = {} 13 | 14 | rootComponentType: ComponentType | undefined = undefined 15 | 16 | constructor(name?: ComponentType) { 17 | if (name) { 18 | this.rootComponentType = name 19 | } 20 | } 21 | 22 | addNode = ({ 23 | type, 24 | parent = 'root', 25 | props = {}, 26 | rootParentType, 27 | }: AddNode): string => { 28 | const id = generateId() 29 | 30 | if (parent === 'root' && !this.rootComponentType) { 31 | this.rootComponentType = type 32 | } 33 | const localRootParentType = rootParentType || this.rootComponentType 34 | 35 | const { form, ...defaultProps } = DEFAULT_PROPS[type] || {} 36 | 37 | this.components = { 38 | ...this.components, 39 | [id]: { 40 | children: [], 41 | type, 42 | parent, 43 | id, 44 | props: { ...defaultProps, ...props }, 45 | rootParentType: localRootParentType, 46 | }, 47 | } 48 | 49 | if (parent !== 'root' && this.components[parent]) { 50 | this.components[parent].children.push(id) 51 | } 52 | 53 | return id 54 | } 55 | 56 | getComponents() { 57 | return this.components 58 | } 59 | } 60 | 61 | export default Composer 62 | -------------------------------------------------------------------------------- /src/core/models/index.ts: -------------------------------------------------------------------------------- 1 | import app from './app' 2 | import components from './components' 3 | 4 | const index = { app, components } 5 | 6 | export default index 7 | -------------------------------------------------------------------------------- /src/core/selectors/app.ts: -------------------------------------------------------------------------------- 1 | import { RootState } from '~core/store' 2 | 3 | export const getShowLayout = (state: RootState) => state.app.showLayout 4 | 5 | export const getShowCode = (state: RootState) => state.app.showCode 6 | 7 | export const getFocusedComponent = (id: IComponent['id']) => ( 8 | state: RootState, 9 | ) => state.app.inputTextFocused && state.components.present.selectedId === id 10 | 11 | export const getInputTextFocused = (state: RootState) => 12 | state.app.inputTextFocused 13 | -------------------------------------------------------------------------------- /src/core/selectors/components.ts: -------------------------------------------------------------------------------- 1 | import map from 'lodash/map' 2 | import { RootState } from '~core/store' 3 | 4 | export const getComponents = (state: RootState) => 5 | state.components.present.components 6 | 7 | export const getComponentBy = (nameOrId: string | IComponent['id']) => ( 8 | state: RootState, 9 | ) => state.components.present.components[nameOrId] 10 | 11 | export const getSelectedComponent = (state: RootState) => 12 | state.components.present.components[state.components.present.selectedId] 13 | 14 | export const getPropsForSelectedComponent = ( 15 | state: RootState, 16 | propsName: string, 17 | ) => 18 | state.components.present.components[state.components.present.selectedId] 19 | .props[propsName] 20 | 21 | export const getSelectedComponentId = (state: RootState) => 22 | state.components.present.selectedId 23 | 24 | export const getIsSelectedComponent = (componentId: IComponent['id']) => ( 25 | state: RootState, 26 | ) => state.components.present.selectedId === componentId 27 | 28 | export const getSelectedComponentChildren = (state: RootState) => { 29 | return getSelectedComponent(state).children.map(child => 30 | getComponentBy(child)(state), 31 | ) 32 | } 33 | 34 | export const getSelectedComponentParent = (state: RootState) => 35 | state.components.present.components[getSelectedComponent(state).parent] 36 | 37 | export const getHoveredId = (state: RootState) => 38 | state.components.present.hoveredId 39 | 40 | export const getIsHovered = (id: IComponent['id']) => (state: RootState) => 41 | getHoveredId(state) === id 42 | 43 | export const getComponentNames = (state: RootState) => { 44 | const names = map( 45 | state.components.present.components, 46 | comp => comp.componentName, 47 | ).filter(comp => !!comp) 48 | 49 | return Array.from(new Set(names)) 50 | } 51 | -------------------------------------------------------------------------------- /src/core/store.ts: -------------------------------------------------------------------------------- 1 | import { init } from '@rematch/core' 2 | import { combineReducers } from 'redux' 3 | import undoable from 'redux-undo' 4 | import { persistReducer, persistStore } from 'redux-persist' 5 | import storage from 'redux-persist/lib/storage' 6 | import { createWrapper, MakeStore } from 'next-redux-wrapper' 7 | 8 | import { ComponentsStateWithUndo } from './models/components' 9 | import { AppState } from './models/app' 10 | import models from './models' 11 | import filterUndoableActions from '~utils/undo' 12 | 13 | export type RootState = { 14 | app: AppState 15 | components: ComponentsStateWithUndo 16 | } 17 | 18 | const version = parseInt(process.env.NEXT_PUBLIC_VERSION || '1', 10) 19 | 20 | const persistConfig = { 21 | key: `openchakra_v${version}`, 22 | storage, 23 | whitelist: ['present'], 24 | version, 25 | throttle: 500, 26 | } 27 | 28 | const persistPlugin = { 29 | onStoreCreated(store: any) { 30 | if (process.browser) { 31 | persistStore(store) 32 | } 33 | }, 34 | } 35 | 36 | export const storeConfig = { 37 | models, 38 | redux: { 39 | // @ts-ignore 40 | combineReducers: reducers => { 41 | return combineReducers({ 42 | ...reducers, 43 | components: persistReducer( 44 | persistConfig, 45 | undoable(reducers.components, { 46 | limit: 10, 47 | filter: filterUndoableActions, 48 | }), 49 | ), 50 | }) 51 | }, 52 | }, 53 | plugins: [persistPlugin], 54 | } 55 | 56 | // @ts-ignore 57 | export const makeStore: MakeStore = () => init(storeConfig) 58 | 59 | export const wrapper = createWrapper(makeStore) 60 | -------------------------------------------------------------------------------- /src/hooks/useClipboard.ts: -------------------------------------------------------------------------------- 1 | import { useState, useRef, useEffect } from 'react' 2 | import copyToClipboard from 'copy-to-clipboard' 3 | 4 | const useClipboard = () => { 5 | const [hasCopied, setHasCopied] = useState(false) 6 | const timeoutRef = useRef(null) 7 | 8 | useEffect(() => { 9 | return () => { 10 | if (timeoutRef.current) { 11 | clearTimeout(timeoutRef.current) 12 | } 13 | } 14 | }, []) 15 | 16 | const copy = (value: string) => { 17 | copyToClipboard(value) 18 | setHasCopied(true) 19 | timeoutRef.current = setTimeout(() => setHasCopied(false), 1500) 20 | } 21 | 22 | return { 23 | onCopy: copy, 24 | hasCopied, 25 | } 26 | } 27 | 28 | export default useClipboard 29 | -------------------------------------------------------------------------------- /src/hooks/useDispatch.ts: -------------------------------------------------------------------------------- 1 | import { useDispatch as useReduxDispatch } from 'react-redux' 2 | import { RematchDispatch } from '@rematch/core' 3 | import models from '~core/models' 4 | 5 | const useDispatch = () => { 6 | return useReduxDispatch() as RematchDispatch 7 | } 8 | 9 | export default useDispatch 10 | -------------------------------------------------------------------------------- /src/hooks/useDropComponent.ts: -------------------------------------------------------------------------------- 1 | import { useDrop, DropTargetMonitor } from 'react-dnd' 2 | import { rootComponents } from '~utils/editor' 3 | import useDispatch from './useDispatch' 4 | import builder from '~core/models/composer/builder' 5 | 6 | export const useDropComponent = ( 7 | componentId: string, 8 | accept: (ComponentType | MetaComponentType)[] = rootComponents, 9 | canDrop: boolean = true, 10 | ) => { 11 | const dispatch = useDispatch() 12 | 13 | const [{ isOver }, drop] = useDrop({ 14 | accept, 15 | collect: monitor => ({ 16 | isOver: monitor.isOver({ shallow: true }) && monitor.canDrop(), 17 | }), 18 | drop: (item: ComponentItemProps, monitor: DropTargetMonitor) => { 19 | if (!monitor.isOver()) { 20 | return 21 | } 22 | 23 | if (item.isMoved) { 24 | dispatch.components.moveComponent({ 25 | parentId: componentId, 26 | componentId: item.id, 27 | }) 28 | } else if (item.isMeta) { 29 | dispatch.components.addMetaComponent(builder[item.type](componentId)) 30 | } else { 31 | dispatch.components.addComponent({ 32 | parentName: componentId, 33 | type: item.type, 34 | rootParentType: item.rootParentType, 35 | }) 36 | } 37 | }, 38 | canDrop: () => canDrop, 39 | }) 40 | 41 | return { drop, isOver } 42 | } 43 | -------------------------------------------------------------------------------- /src/hooks/useForm.ts: -------------------------------------------------------------------------------- 1 | import { ChangeEvent, useCallback } from 'react' 2 | import { useSelector } from 'react-redux' 3 | import useDispatch from './useDispatch' 4 | import { getSelectedComponentId } from '~core/selectors/components' 5 | 6 | export const useForm = () => { 7 | const dispatch = useDispatch() 8 | const componentId = useSelector(getSelectedComponentId) 9 | 10 | const setValueFromEvent = ({ 11 | target: { name, value }, 12 | }: ChangeEvent) => { 13 | setValue(name, value) 14 | } 15 | 16 | const setValue = useCallback( 17 | (name: string, value: any) => { 18 | dispatch.components.updateProps({ 19 | id: componentId, 20 | name, 21 | value, 22 | }) 23 | }, 24 | [componentId, dispatch.components], 25 | ) 26 | 27 | return { setValue, setValueFromEvent } 28 | } 29 | -------------------------------------------------------------------------------- /src/hooks/useInteractive.ts: -------------------------------------------------------------------------------- 1 | import { useRef, MouseEvent } from 'react' 2 | import { useSelector } from 'react-redux' 3 | import useDispatch from './useDispatch' 4 | import { useDrag } from 'react-dnd' 5 | import { 6 | getIsSelectedComponent, 7 | getIsHovered, 8 | } from '../core/selectors/components' 9 | import { getShowLayout, getFocusedComponent } from '../core/selectors/app' 10 | 11 | export const useInteractive = ( 12 | component: IComponent, 13 | enableVisualHelper = false, 14 | withoutComponentProps = false, 15 | ) => { 16 | const dispatch = useDispatch() 17 | const showLayout = useSelector(getShowLayout) 18 | const isComponentSelected = useSelector(getIsSelectedComponent(component.id)) 19 | const isHovered = useSelector(getIsHovered(component.id)) 20 | const focusInput = useSelector(getFocusedComponent(component.id)) 21 | 22 | const [, drag] = useDrag({ 23 | item: { id: component.id, type: component.type, isMoved: true }, 24 | }) 25 | 26 | const ref = useRef(null) 27 | let props = { 28 | ...(withoutComponentProps ? {} : component.props), 29 | onMouseOver: (event: MouseEvent) => { 30 | event.stopPropagation() 31 | dispatch.components.hover(component.id) 32 | }, 33 | onMouseOut: () => { 34 | dispatch.components.unhover() 35 | }, 36 | onClick: (event: MouseEvent) => { 37 | event.preventDefault() 38 | event.stopPropagation() 39 | dispatch.components.select(component.id) 40 | }, 41 | onDoubleClick: (event: MouseEvent) => { 42 | event.preventDefault() 43 | event.stopPropagation() 44 | if (focusInput === false) { 45 | dispatch.app.toggleInputText() 46 | } 47 | }, 48 | } 49 | 50 | if (showLayout && enableVisualHelper) { 51 | props = { 52 | ...props, 53 | border: `1px dashed #718096`, 54 | padding: props.p || props.padding ? props.p || props.padding : 4, 55 | } 56 | } 57 | 58 | if (isHovered || isComponentSelected) { 59 | props = { 60 | ...props, 61 | boxShadow: `${focusInput ? '#ffc4c7' : '#4FD1C5'} 0px 0px 0px 2px inset`, 62 | } 63 | } 64 | 65 | return { props, ref: drag(ref), drag } 66 | } 67 | -------------------------------------------------------------------------------- /src/hooks/usePropsSelector.ts: -------------------------------------------------------------------------------- 1 | import { useSelector } from 'react-redux' 2 | import { RootState } from '~core/store' 3 | import { getDefaultFormProps } from '~utils/defaultProps' 4 | import { useInspectorUpdate } from '~contexts/inspector-context' 5 | import { useEffect } from 'react' 6 | 7 | const usePropsSelector = (propsName: string) => { 8 | const { addActiveProps } = useInspectorUpdate() 9 | 10 | useEffect(() => { 11 | // Register form props name for custom props panel 12 | addActiveProps(propsName) 13 | }, [addActiveProps, propsName]) 14 | 15 | const value = useSelector((state: RootState) => { 16 | const component = 17 | state.components.present.components[state.components.present.selectedId] 18 | const propsValue = component.props[propsName] 19 | 20 | if (propsValue !== undefined) { 21 | return propsValue 22 | } 23 | 24 | if (getDefaultFormProps(component.type)[propsName] !== undefined) { 25 | return getDefaultFormProps(component.type)[propsName] 26 | } 27 | 28 | return '' 29 | }) 30 | 31 | return value 32 | } 33 | 34 | export default usePropsSelector 35 | -------------------------------------------------------------------------------- /src/hooks/useShortcuts.ts: -------------------------------------------------------------------------------- 1 | import useDispatch from './useDispatch' 2 | import { useSelector } from 'react-redux' 3 | import { ActionCreators as UndoActionCreators } from 'redux-undo' 4 | import { getSelectedComponent } from '~core/selectors/components' 5 | import { useHotkeys } from 'react-hotkeys-hook' 6 | 7 | const keyMap = { 8 | DELETE_NODE: 'Backspace, del', 9 | TOGGLE_BUILDER_MODE: 'b', 10 | TOGGLE_CODE_PANEL: 'c', 11 | UNDO: 'ctrl+z, command+z', 12 | REDO: 'ctrl+y, cmd+y', 13 | UNSELECT: 'esc', 14 | PARENT: 'p', 15 | DUPLICATE: 'ctrl+d, command+d', 16 | KONAMI_CODE: 17 | 'up up down down left right left right b a, up up down down left right left right B A', 18 | } 19 | 20 | const hasNoSpecialKeyPressed = (event: KeyboardEvent | undefined) => 21 | !event?.metaKey && !event?.shiftKey && !event?.ctrlKey && !event?.altKey 22 | 23 | const useShortcuts = () => { 24 | const dispatch = useDispatch() 25 | const selected = useSelector(getSelectedComponent) 26 | 27 | const deleteNode = (event: KeyboardEvent | undefined) => { 28 | if (event) { 29 | event.preventDefault() 30 | } 31 | dispatch.components.deleteComponent(selected.id) 32 | } 33 | 34 | const toggleBuilderMode = (event: KeyboardEvent | undefined) => { 35 | if (event && hasNoSpecialKeyPressed(event)) { 36 | event.preventDefault() 37 | dispatch.app.toggleBuilderMode() 38 | } 39 | } 40 | 41 | const toggleCodePanel = (event: KeyboardEvent | undefined) => { 42 | if (event && hasNoSpecialKeyPressed(event)) { 43 | event.preventDefault() 44 | dispatch.app.toggleCodePanel() 45 | } 46 | } 47 | 48 | const undo = (event: KeyboardEvent | undefined) => { 49 | if (event) { 50 | event.preventDefault() 51 | } 52 | 53 | dispatch(UndoActionCreators.undo()) 54 | } 55 | 56 | const redo = (event: KeyboardEvent | undefined) => { 57 | if (event) { 58 | event.preventDefault() 59 | } 60 | 61 | dispatch(UndoActionCreators.redo()) 62 | } 63 | 64 | const onUnselect = () => { 65 | dispatch.components.unselect() 66 | } 67 | 68 | const onSelectParent = (event: KeyboardEvent | undefined) => { 69 | if (event && hasNoSpecialKeyPressed(event)) { 70 | event.preventDefault() 71 | dispatch.components.selectParent() 72 | } 73 | } 74 | 75 | const onDuplicate = (event: KeyboardEvent | undefined) => { 76 | if (event) { 77 | event.preventDefault() 78 | } 79 | 80 | dispatch.components.duplicate() 81 | } 82 | 83 | const onKonamiCode = () => { 84 | dispatch.components.loadDemo('secretchakra') 85 | } 86 | 87 | useHotkeys(keyMap.DELETE_NODE, deleteNode, {}, [selected.id]) 88 | useHotkeys(keyMap.TOGGLE_BUILDER_MODE, toggleBuilderMode) 89 | useHotkeys(keyMap.TOGGLE_CODE_PANEL, toggleCodePanel) 90 | useHotkeys(keyMap.UNDO, undo) 91 | useHotkeys(keyMap.REDO, redo) 92 | useHotkeys(keyMap.UNSELECT, onUnselect) 93 | useHotkeys(keyMap.PARENT, onSelectParent) 94 | useHotkeys(keyMap.DUPLICATE, onDuplicate) 95 | useHotkeys(keyMap.KONAMI_CODE, onKonamiCode) 96 | } 97 | 98 | export default useShortcuts 99 | -------------------------------------------------------------------------------- /src/iconsList.ts: -------------------------------------------------------------------------------- 1 | import * as Icons from '@chakra-ui/icons' 2 | 3 | const { createIcon, ...iconsList } = Icons 4 | 5 | export default iconsList 6 | -------------------------------------------------------------------------------- /src/models/doc.ts: -------------------------------------------------------------------------------- 1 | interface IDoc { 2 | [name: string]: string; 3 | } 4 | 5 | const docs: IDoc = { 6 | Button: 7 | "Button component is used to trigger an action or event, such as submitting a form, opening a Dialog, canceling an action, or performing a delete operation.", 8 | Badge: "Badges are used to highlight an item's status for quick recognition.", 9 | Box: 10 | "Box is the most abstract component on top of which all other Chakra UI components are built. By default, it renders a div element", 11 | Image: 12 | "The Image component is used to display images. Image composes Box so you can use all the style props and add responsive styles as well.", 13 | Icon: 14 | "Use the Icon component to easily render svg icons. Chakra UI provides basic interface icons, to add your icons, read the guide.", 15 | Text: 16 | "Text is the used to render text and paragraphs within an interface. It renders a p tag by default." 17 | }; 18 | 19 | export default docs; 20 | -------------------------------------------------------------------------------- /src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { ChakraProvider } from '@chakra-ui/react' 3 | import theme from '@chakra-ui/theme' 4 | import 'react-color-picker/index.css' 5 | import '@reach/combobox/styles.css' 6 | 7 | import { wrapper } from '~core/store' 8 | import { ErrorBoundary as BugsnagErrorBoundary } from '~utils/bugsnag' 9 | import AppErrorBoundary from '~components/errorBoundaries/AppErrorBoundary' 10 | import { AppProps } from 'next/app' 11 | 12 | const Main = ({ Component, pageProps }: AppProps) => ( 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ) 21 | 22 | export default wrapper.withRedux(Main) 23 | -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Flex, Box } from '@chakra-ui/react' 3 | import { DndProvider } from 'react-dnd' 4 | import { HTML5Backend } from 'react-dnd-html5-backend' 5 | import { Global } from '@emotion/react' 6 | import Metadata from '~components/Metadata' 7 | import useShortcuts from '~hooks/useShortcuts' 8 | import Header from '~components/Header' 9 | import Sidebar from '~components/sidebar/Sidebar' 10 | import EditorErrorBoundary from '~components/errorBoundaries/EditorErrorBoundary' 11 | import Editor from '~components/editor/Editor' 12 | import { InspectorProvider } from '~contexts/inspector-context' 13 | import Inspector from '~components/inspector/Inspector' 14 | 15 | const App = () => { 16 | useShortcuts() 17 | 18 | return ( 19 | <> 20 | ({ 22 | html: { minWidth: '860px', backgroundColor: '#1a202c' }, 23 | })} 24 | /> 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | ) 52 | } 53 | 54 | export default App 55 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// ; 2 | declare module 'prettier/standalone' 3 | declare module 'coloreact' 4 | declare module 'browser-nativefs' 5 | 6 | type ComponentType = 7 | | 'Accordion' 8 | | 'AccordionItem' 9 | | 'AccordionButton' 10 | | 'AccordionPanel' 11 | | 'AccordionIcon' 12 | | 'Alert' 13 | | 'AlertIcon' 14 | | 'AlertTitle' 15 | | 'AlertDescription' 16 | | 'AspectRatio' 17 | | 'AvatarBadge' 18 | | 'AvatarGroup' 19 | | 'Avatar' 20 | | 'Badge' 21 | | 'Box' 22 | | 'Breadcrumb' 23 | | 'BreadcrumbItem' 24 | | 'BreadcrumbLink' 25 | | 'Button' 26 | | 'Center' 27 | | 'Checkbox' 28 | | 'CircularProgress' 29 | | 'CloseButton' 30 | | 'Code' 31 | | 'Container' 32 | | 'Divider' 33 | | 'Editable' 34 | | 'Flex' 35 | | 'FormControl' 36 | | 'FormLabel' 37 | | 'FormHelperText' 38 | | 'FormErrorMessage' 39 | | 'Grid' 40 | | 'Heading' 41 | | 'Highlight' 42 | | 'Icon' 43 | | 'IconButton' 44 | | 'Image' 45 | | 'Input' 46 | | 'InputGroup' 47 | | 'InputLeftAddon' 48 | | 'InputRightAddon' 49 | | 'InputLeftElement' 50 | | 'InputRightElement' 51 | | 'Link' 52 | | 'List' 53 | | 'ListItem' 54 | | 'ListIcon' 55 | | 'Kbd' 56 | | 'Menu' 57 | | 'NumberInput' 58 | | 'Progress' 59 | | 'Radio' 60 | | 'RadioGroup' 61 | | 'Select' 62 | | 'SimpleGrid' 63 | | 'Spinner' 64 | | 'Skeleton' 65 | | 'SkeletonCircle' 66 | | 'SkeletonText' 67 | | 'Stack' 68 | | 'Stat' 69 | | 'StatLabel' 70 | | 'StatNumber' 71 | | 'StatHelpText' 72 | | 'StatArrow' 73 | | 'StatGroup' 74 | | 'Switch' 75 | | 'Tab' 76 | | 'Tabs' 77 | | 'TabList' 78 | | 'TabPanel' 79 | | 'TabPanels' 80 | | 'Tag' 81 | | 'Text' 82 | | 'Textarea' 83 | 84 | type MetaComponentType = 85 | | 'FormControlMeta' 86 | | 'AccordionMeta' 87 | | 'ListMeta' 88 | | 'AlertMeta' 89 | | 'InputGroupMeta' 90 | | 'BreadcrumbMeta' 91 | | 'TabsMeta' 92 | | 'StatMeta' 93 | 94 | interface IComponent { 95 | children: string[] 96 | type: ComponentType 97 | parent: string 98 | id: string 99 | props: any 100 | rootParentType?: ComponentType 101 | componentName?: string 102 | } 103 | 104 | interface IComponents { 105 | [name: string]: IComponent 106 | } 107 | 108 | interface IPreviewProps { 109 | component: IComponent 110 | } 111 | 112 | interface ComponentItemProps { 113 | id: string 114 | label: string 115 | type: ComponentType 116 | isMoved?: boolean 117 | isChild?: boolean 118 | isMeta?: boolean 119 | soon?: boolean 120 | rootParentType?: ComponentType 121 | children?: React.ReactNode 122 | } 123 | -------------------------------------------------------------------------------- /src/templates/index.ts: -------------------------------------------------------------------------------- 1 | import { onboarding } from './onboarding' 2 | import { productHunt } from './producthunt' 3 | import { secretchakra } from './secretchakra' 4 | 5 | export type TemplateType = 'onboarding' | 'ph' | 'secretchakra' 6 | 7 | const templates: { 8 | [id in TemplateType]: IComponents 9 | } = { 10 | ph: productHunt, 11 | onboarding, 12 | secretchakra, 13 | } 14 | 15 | export default templates 16 | -------------------------------------------------------------------------------- /src/utils/bugsnag.ts: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import bugsnag from '@bugsnag/js' 3 | import bugsnagReact from '@bugsnag/plugin-react' 4 | 5 | export const bugsnagClient = bugsnag({ 6 | apiKey: process.env.NEXT_PUBLIC_BUGSNAG_API_KEY as string, 7 | releaseStage: process.env.NODE_ENV, 8 | notifyReleaseStages: ['production'], 9 | }) 10 | bugsnagClient.use(bugsnagReact, React) 11 | 12 | export const ErrorBoundary = bugsnagClient.getPlugin('react') 13 | -------------------------------------------------------------------------------- /src/utils/code.test.ts: -------------------------------------------------------------------------------- 1 | import { generateComponentCode, generateCode, formatCode } from './code' 2 | 3 | const componentFixtures: IComponents = { 4 | root: { 5 | id: 'root', 6 | parent: 'root', 7 | type: 'Box', 8 | children: ['comp-1'], 9 | props: {}, 10 | }, 11 | 'comp-1': { 12 | id: 'comp-1', 13 | props: { 14 | bg: 'whatsapp.500', 15 | }, 16 | children: ['comp-2'], 17 | type: 'Box', 18 | parent: 'root', 19 | rootParentType: 'Box', 20 | componentName: 'MyBox', 21 | }, 22 | 'comp-2': { 23 | id: 'comp-2', 24 | props: { 25 | children: 'Lorem Ipsum', 26 | }, 27 | children: [], 28 | type: 'Text', 29 | parent: 'comp-1', 30 | rootParentType: 'Text', 31 | }, 32 | } 33 | 34 | const componentFixturesWithButtonIcon: IComponents = { 35 | root: { 36 | id: 'root', 37 | parent: 'root', 38 | type: 'Box', 39 | children: ['comp-1'], 40 | props: {}, 41 | }, 42 | 'comp-1': { 43 | id: 'comp-1', 44 | props: { 45 | bg: 'whatsapp.500', 46 | }, 47 | children: ['comp-2'], 48 | type: 'Box', 49 | parent: 'root', 50 | rootParentType: 'Box', 51 | componentName: 'MyBox', 52 | }, 53 | 'comp-2': { 54 | id: 'comp-2', 55 | props: { 56 | leftIcon: 'PhoneIcon', 57 | }, 58 | children: [], 59 | type: 'Button', 60 | parent: 'comp-1', 61 | rootParentType: 'Button', 62 | }, 63 | } 64 | 65 | describe('Code utils', () => { 66 | it('should generate component code', async () => { 67 | const code = await generateComponentCode({ 68 | component: componentFixtures['root'], 69 | components: componentFixtures, 70 | componentName: 'MyBox', 71 | forceBuildBlock: true, 72 | }) 73 | 74 | expect(await formatCode(code)).toEqual(`const MyBox = () => ( 75 | 76 | Lorem Ipsum 77 | 78 | ) 79 | `) 80 | }) 81 | 82 | it('should generate whole tree code', async () => { 83 | const code = await generateCode(componentFixtures) 84 | 85 | expect(code).toEqual(`import React from 'react' 86 | import { ChakraProvider, Box, Text } from '@chakra-ui/react' 87 | 88 | const MyBox = () => ( 89 | 90 | Lorem Ipsum 91 | 92 | ) 93 | 94 | const App = () => ( 95 | 96 | 97 | 98 | ) 99 | 100 | export default App 101 | `) 102 | }) 103 | 104 | it('should generate icons imports and icon instanciation', async () => { 105 | const code = await generateCode(componentFixturesWithButtonIcon) 106 | 107 | expect(code).toEqual(`import React from 'react' 108 | import { ChakraProvider, Box, Button } from '@chakra-ui/react' 109 | import { PhoneIcon } from '@chakra-ui/icons' 110 | 111 | const MyBox = () => ( 112 | 113 |