From b1a88dd8449a62b33f638d25740f40f916dbe64a Mon Sep 17 00:00:00 2001 From: Ghazwan Date: Mon, 21 Sep 2026 09:30:20 +0700 Subject: [PATCH 1/3] [CCUBE-2283][GZ] change web form builder docs to a simple schema playground --- .storybook/preview.tsx | 2 +- package-lock.json | 341 ------------------ package.json | 1 - .../components/error-boundary.tsx | 36 ++ .../components/form-builder-tool.styles.ts | 122 ------- .../components/form-builder-tool.tsx | 132 ------- .../components/schema-playground.styles.ts | 64 ++++ .../components/schema-playground.tsx | 90 +++++ .../form-builder/components/schema-view.tsx | 112 ------ src/stories/1-intro/form-builder/docs.mdx | 7 +- 10 files changed, 194 insertions(+), 713 deletions(-) create mode 100644 src/stories/1-intro/form-builder/components/error-boundary.tsx delete mode 100644 src/stories/1-intro/form-builder/components/form-builder-tool.styles.ts delete mode 100644 src/stories/1-intro/form-builder/components/form-builder-tool.tsx create mode 100644 src/stories/1-intro/form-builder/components/schema-playground.styles.ts create mode 100644 src/stories/1-intro/form-builder/components/schema-playground.tsx delete mode 100644 src/stories/1-intro/form-builder/components/schema-view.tsx diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 944469a8e..a1dfac2e8 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -18,7 +18,7 @@ const preview: Preview = { storySort: { order: [ "Introduction", - ["Getting Started", "Form Builder"], + ["Getting Started", "Schema Playground"], "Form", ["Frontend Engine", "Validation Schema", "Conditional Rendering"], ], diff --git a/package-lock.json b/package-lock.json index bed20d136..63cf63550 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,7 +42,6 @@ "@floating-ui/react": "^0.26.23", "@lifesg/react-design-system": "^4.0.0-alpha.6", "@lifesg/react-icons": "^1.9.0", - "@lifesg/web-form-builder": "^1.0.0-alpha.4", "@linaria/babel-preset": "^5.0.4", "@linaria/core": "^7.0.0", "@mihkeleidast/storybook-addon-source": "^1.0.1", @@ -2288,63 +2287,6 @@ "node": ">=20.19.0" } }, - "node_modules/@dnd-kit/accessibility": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", - "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0" - } - }, - "node_modules/@dnd-kit/core": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", - "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@dnd-kit/accessibility": "^3.1.1", - "@dnd-kit/utilities": "^3.2.2", - "tslib": "^2.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@dnd-kit/sortable": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-7.0.2.tgz", - "integrity": "sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@dnd-kit/utilities": "^3.2.0", - "tslib": "^2.0.0" - }, - "peerDependencies": { - "@dnd-kit/core": "^6.0.7", - "react": ">=16.8.0" - } - }, - "node_modules/@dnd-kit/utilities": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", - "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0" - } - }, "node_modules/@emnapi/core": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", @@ -2379,25 +2321,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@emotion/is-prop-valid": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", - "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@emotion/memoize": "^0.9.0" - } - }, - "node_modules/@emotion/memoize": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", - "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/@erase2d/fabric": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@erase2d/fabric/-/fabric-1.2.1.tgz", @@ -4353,179 +4276,6 @@ "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0" } }, - "node_modules/@lifesg/web-form-builder": { - "version": "1.0.0-alpha.4", - "resolved": "https://registry.npmjs.org/@lifesg/web-form-builder/-/web-form-builder-1.0.0-alpha.4.tgz", - "integrity": "sha512-uUtmE0eTud4yG2iBBkYgOALXcmSJBtE8Tc90OxCrMkQCWs28RP7LbjoD9uzzT5SsTeglbhPIZRomaGJ+jBtSKQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "@dnd-kit/core": "^6.0.8", - "@dnd-kit/sortable": "^7.0.2", - "@hookform/resolvers": "^3.3.4", - "@lifesg/react-design-system": "^3.0.0-alpha.20", - "@lifesg/web-frontend-engine": "^2.0.0-alpha.8", - "react-hook-form": "^7.54.2", - "use-immer": "^0.9.0", - "yup": "^1.4.0" - }, - "peerDependencies": { - "@floating-ui/dom": "^1.6.10", - "@floating-ui/react": "^0.26.23", - "@lifesg/react-icons": "^1.10.0", - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0", - "styled-components": "^6.1.19" - } - }, - "node_modules/@lifesg/web-form-builder/node_modules/@hookform/resolvers": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz", - "integrity": "sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "react-hook-form": "^7.0.0" - } - }, - "node_modules/@lifesg/web-form-builder/node_modules/@lifesg/react-design-system": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@lifesg/react-design-system/-/react-design-system-3.4.0.tgz", - "integrity": "sha512-Fti/1kPz44S4wocHSnI7oYx3IthoaNx3EvGOH8+MIwfAarlfEKlJr26Tvff6a71uy7snzatH+QfLA0nEfBWBDw==", - "dev": true, - "license": "ISC", - "dependencies": { - "@react-aria/live-announcer": "^3.4.4", - "@react-spring/web": "^10.0.1", - "fabric": "^7.2.0", - "immer": "^10.0.2", - "react-slider": "^2.0.6", - "react-virtuoso": "^4.12.5", - "react-zoom-pan-pinch": "^3.3.0" - }, - "bin": { - "lifesg-react-design-system": "codemods/run-codemod.js" - }, - "engines": { - "node": ">=22.0.0", - "npm": ">=11.10.0" - }, - "peerDependencies": { - "@floating-ui/react": ">=0.26.23 <1.0.0", - "@lifesg/react-icons": "^1.5.0", - "react": "^17.0.2 || ^18.0.0 || ^19.0.0", - "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0", - "styled-components": "^6.1.19" - } - }, - "node_modules/@lifesg/web-form-builder/node_modules/@lifesg/web-frontend-engine": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@lifesg/web-frontend-engine/-/web-frontend-engine-2.2.4.tgz", - "integrity": "sha512-gN1qwnTPYyyHRbZRuCKgICffY28mK4CbDGDieKOuDA1ND9dA+d9UUMVXNcOGqryEAx0HY7WAEpoRuaL3RvYDzA==", - "dev": true, - "license": "ISC", - "dependencies": { - "@erase2d/fabric": "^1.1.7", - "@hookform/resolvers": "^2.9.8", - "@js-joda/core": "^5.4.2", - "@js-joda/locale_en-us": "^4.8.6", - "@js-joda/timezone": "^2.15.0", - "@rollup/plugin-terser": "^1.0.0", - "@use-gesture/react": "^10.3.1", - "axios": "^1.12.2", - "country-code-lookup": "^0.0.20", - "exifreader": "^4.23.7", - "fabric": "7.2.0", - "file-type": "^21.3.1", - "heic-to": "^1.3.0", - "leaflet": "^1.9.4", - "libphonenumber-js": "^1.10.14", - "lodash": "^4.17.21", - "react-dropzone": "^14.2.3", - "react-hook-form": "7.54.2", - "react-infinite-scroll-hook": "^4.1.1", - "sanitize-html": "^2.8.1", - "use-deep-compare-effect": "^1.8.1", - "yup": "^0.32.11" - }, - "peerDependencies": { - "@floating-ui/react": ">=0.26.23 <1.0.0", - "@lifesg/react-design-system": "^3.4.0-canary.1", - "@lifesg/react-icons": "^1.9.0", - "react": "^17.0.2 || ^18.0.0 || ^19.0.0", - "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0", - "styled-components": "^6.1.19" - } - }, - "node_modules/@lifesg/web-form-builder/node_modules/@lifesg/web-frontend-engine/node_modules/@hookform/resolvers": { - "version": "2.9.11", - "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-2.9.11.tgz", - "integrity": "sha512-bA3aZ79UgcHj7tFV7RlgThzwSSHZgvfbt2wprldRkYBcMopdMvHyO17Wwp/twcJasNFischFfS7oz8Katz8DdQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "react-hook-form": "^7.0.0" - } - }, - "node_modules/@lifesg/web-form-builder/node_modules/@lifesg/web-frontend-engine/node_modules/yup": { - "version": "0.32.11", - "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz", - "integrity": "sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.15.4", - "@types/lodash": "^4.14.175", - "lodash": "^4.17.21", - "lodash-es": "^4.17.21", - "nanoclone": "^0.2.1", - "property-expr": "^2.0.4", - "toposort": "^2.0.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@lifesg/web-form-builder/node_modules/fabric": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/fabric/-/fabric-7.2.0.tgz", - "integrity": "sha512-XSYmSqSMrlbCg+/j7/uU/PFeZuA5hHRDp7sGbDlMvz/T6BHt2MQSOYtz/AIdr+kmReA1s5jTzHJ8AjHwYUcmfQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.0.0" - }, - "optionalDependencies": { - "canvas": "^3.2.0", - "jsdom": "^26.1.0" - } - }, - "node_modules/@lifesg/web-form-builder/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@lifesg/web-form-builder/node_modules/yup": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/yup/-/yup-1.7.1.tgz", - "integrity": "sha512-GKHFX2nXul2/4Dtfxhozv701jLQHdf6J34YDh2cEkpqoo8le5Mg6/LrdseVLrFarmFygZTlfIhHx/QKfb/QWXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "property-expr": "^2.0.5", - "tiny-case": "^1.0.3", - "toposort": "^2.0.2", - "type-fest": "^2.19.0" - } - }, "node_modules/@linaria/babel-preset": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@linaria/babel-preset/-/babel-preset-5.0.4.tgz", @@ -8597,17 +8347,6 @@ "node": ">=6" } }, - "node_modules/camelize": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", - "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", - "dev": true, - "license": "MIT", - "peer": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -9074,17 +8813,6 @@ "node": ">= 8" } }, - "node_modules/css-color-keywords": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", - "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", - "dev": true, - "license": "ISC", - "peer": true, - "engines": { - "node": ">=4" - } - }, "node_modules/css-declaration-sorter": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", @@ -9227,19 +8955,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/css-to-react-native": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", - "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "camelize": "^1.0.0", - "css-color-keywords": "^1.0.0", - "postcss-value-parser": "^4.0.2" - } - }, "node_modules/css-tree": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", @@ -19483,44 +19198,6 @@ "webpack": "^5.0.0" } }, - "node_modules/styled-components": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.4.1.tgz", - "integrity": "sha512-ADu2dF53esUzzM4I0ewxhxFtsDd6v4V6dNkg3vG0iFKhnt06sJneTZnRvujAosZwW0XD58IKgGMQoqri4wHRqg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@emotion/is-prop-valid": "1.4.0", - "css-to-react-native": "3.2.0", - "csstype": "3.2.3", - "stylis": "4.3.6" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/styled-components" - }, - "peerDependencies": { - "css-to-react-native": ">= 3.2.0", - "react": ">= 16.8.0", - "react-dom": ">= 16.8.0", - "react-native": ">= 0.68.0" - }, - "peerDependenciesMeta": { - "css-to-react-native": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, "node_modules/stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", @@ -19910,13 +19587,6 @@ "dev": true, "license": "MIT" }, - "node_modules/tiny-case": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", - "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==", - "dev": true, - "license": "MIT" - }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", @@ -20496,17 +20166,6 @@ "react": ">=16.13" } }, - "node_modules/use-immer": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/use-immer/-/use-immer-0.9.0.tgz", - "integrity": "sha512-/L+enLi0nvuZ6j4WlyK0US9/ECUtV5v9RUbtxnn5+WbtaXYUaOBoKHDNL9I5AETdurQ4rIFIj/s+Z5X80ATyKw==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "immer": ">=2.0.0", - "react": "^16.8.0 || ^17.0.1 || ^18.0.0" - } - }, "node_modules/use-sync-external-store": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", diff --git a/package.json b/package.json index 58779d593..0e8b3dcb1 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,6 @@ "@floating-ui/react": "^0.26.23", "@lifesg/react-design-system": "^4.0.0-alpha.6", "@lifesg/react-icons": "^1.9.0", - "@lifesg/web-form-builder": "^1.0.0-alpha.4", "@linaria/babel-preset": "^5.0.4", "@linaria/core": "^7.0.0", "@mihkeleidast/storybook-addon-source": "^1.0.1", diff --git a/src/stories/1-intro/form-builder/components/error-boundary.tsx b/src/stories/1-intro/form-builder/components/error-boundary.tsx new file mode 100644 index 000000000..75a794b2f --- /dev/null +++ b/src/stories/1-intro/form-builder/components/error-boundary.tsx @@ -0,0 +1,36 @@ +import { Button } from "@lifesg/react-design-system/button"; +import { Typography } from "@lifesg/react-design-system/typography"; +import { Component, PropsWithChildren, ReactNode } from "react"; +import * as styles from "./schema-playground.styles"; + +interface IState { + hasError: boolean; + error: Error | null; +} + +export class ErrorBoundary extends Component { + state: IState = { hasError: false, error: null }; + + static getDerivedStateFromError(error: Error): IState { + return { hasError: true, error }; + } + + render(): ReactNode { + if (this.state.hasError) { + return ( +
+ Something went wrong +
{this.state.error?.message}
+ +
+ ); + } + return this.props.children; + } +} diff --git a/src/stories/1-intro/form-builder/components/form-builder-tool.styles.ts b/src/stories/1-intro/form-builder/components/form-builder-tool.styles.ts deleted file mode 100644 index 0d92f927c..000000000 --- a/src/stories/1-intro/form-builder/components/form-builder-tool.styles.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { css } from "@linaria/core"; -import { Border, Colour, Radius, Spacing } from "@lifesg/react-design-system/theme"; - -// ============================================================================= -// STYLING -// ============================================================================= -export const wrapper = css` - position: absolute; - z-index: 1; - top: 0; - left: 0; - width: 100%; - height: 100%; - display: flex; - flex-direction: column; -`; - -export const contentWrapper = css` - display: block; - width: 100%; - flex: 1; - overflow-y: auto; -`; - -export const contentWrapperHidden = css` - display: none; -`; - -export const contentWrapperFlexbox = css` - display: flex; - flex-direction: column; - align-items: center; - justify-content: flex-start; - gap: ${Spacing["spacing-32"]}; - padding: ${Spacing["spacing-32"]}; -`; - -export const toolbar = css` - position: relative; - display: flex; - width: 100%; - height: 5rem; - justify-content: flex-end; - z-index: 1; - gap: ${Spacing["spacing-32"]}; - padding: ${Spacing["spacing-16"]}; - background-color: ${Colour["bg-primary-subtlest"]}; - box-sizing: border-box; -`; - -export const modeButton = css` - background: transparent; - color: ${Colour["icon-primary"]}; - display: grid; - cursor: pointer; - border: none; - border-radius: ${Radius.sm}; - padding: ${Spacing["spacing-16"]}; - align-self: center; - - &:hover { - background: ${Colour["bg-selected-hover"]}; - } - - &:focus { - outline-color: ${Colour["border-focus"]}; - } - - svg { - width: 1.625rem; - height: 1.625rem; - } -`; - -export const modeButtonActive = css` - background: ${Colour["bg-primary-subtlest-selected"]}; -`; - -export const frontendEnginePreview = css` - width: 100%; - margin-bottom: ${Spacing["spacing-32"]}; -`; - -export const schemaEditorWrapper = css` - display: flex; - flex-direction: column; - width: 100%; - flex: 1; - - > div { - flex: 1; - } -`; - -export const schemaEditor = css` - flex: 1; - width: 100%; - overflow: auto; - padding: ${Spacing["spacing-16"]}; - border: ${Border["width-010"]} ${Border.solid} ${Colour.border}; - border-radius: ${Radius.sm}; -`; - -export const saveButton = css` - width: 10rem; - margin-left: auto; -`; - -export const actionWrapper = css` - display: flex; - width: 100%; - gap: 2rem; -`; - -export const alertWrapper = css` - flex-grow: 1; -`; - -export const refreshButton = css` - display: inline; - padding: 0; -`; diff --git a/src/stories/1-intro/form-builder/components/form-builder-tool.tsx b/src/stories/1-intro/form-builder/components/form-builder-tool.tsx deleted file mode 100644 index a245c00be..000000000 --- a/src/stories/1-intro/form-builder/components/form-builder-tool.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import { Typography } from "@lifesg/react-design-system/typography"; -import { EyeIcon } from "@lifesg/react-icons/eye"; -import { PencilIcon } from "@lifesg/react-icons/pencil"; -import { TabletIcon } from "@lifesg/react-icons/tablet"; -import { FormBuilder, IFormBuilderMethods } from "@lifesg/web-form-builder"; -import { ISchemaProps } from "@lifesg/web-form-builder/translator"; -import { Unstyled } from "@storybook/addon-docs/blocks"; -import clsx from "clsx"; -import { useRef, useState } from "react"; -import * as styles from "./form-builder-tool.styles"; -import { SchemaView } from "./schema-view"; -import { IFrontendEngineData } from "../../../../components"; -import { FrontendEngine } from "../../../common"; -import { ThemeProvider } from "@lifesg/react-design-system/theme"; - -export type TFormBuilderMode = "form-builder" | "preview" | "schema"; - -export const FormBuilderTool = () => { - // ========================================================================= - // CONST, STATE, REFS - // ========================================================================= - const formBuilderRef = useRef(null); - const [formBuilderMode, setFormBuilderMode] = useState("form-builder"); - const [formBuilderOutput, setFormBuilderOutput] = useState(null); - // // ========================================================================= - // // EVENT HANDLERS - // // ========================================================================= - const handleClickToolbarButton = (mode: TFormBuilderMode) => { - switch (mode) { - case "form-builder": - if (formBuilderRef.current && formBuilderOutput) { - formBuilderRef.current.parseSchema(formBuilderOutput); - } - break; - case "preview": - if (formBuilderMode === "form-builder") { - const generatedSchema = formBuilderRef.current.generateSchema(); - console.log(generatedSchema); - setFormBuilderOutput(generatedSchema); - } else if (formBuilderMode === "schema") { - formBuilderRef.current.parseSchema(formBuilderOutput); - } - break; - case "schema": { - setFormBuilderOutput(formBuilderRef.current.generateSchema()); - break; - } - } - setFormBuilderMode(mode); - }; - // ========================================================================= - // RENDER FUNCTIONS - // ========================================================================= - const getContentWrapperClassName = (hidden?: boolean, flexbox?: boolean) => - clsx(styles.contentWrapper, hidden && styles.contentWrapperHidden, flexbox && styles.contentWrapperFlexbox); - - const renderToolbar = () => ( -
- - - -
- ); - - const renderPreview = () => { - if (formBuilderMode !== "preview") return; - return ( -
- Generate Form - {formBuilderOutput && ( - - )} -
- ); - }; - - const renderSchemaPreview = () => { - if (formBuilderMode !== "schema") return; - return ( -
- -
- ); - }; - - return ( - - -
- {renderToolbar()} -
- -
- {renderPreview()} - {renderSchemaPreview()} -
-
-
- ); -}; diff --git a/src/stories/1-intro/form-builder/components/schema-playground.styles.ts b/src/stories/1-intro/form-builder/components/schema-playground.styles.ts new file mode 100644 index 000000000..534760710 --- /dev/null +++ b/src/stories/1-intro/form-builder/components/schema-playground.styles.ts @@ -0,0 +1,64 @@ +import { css } from "@linaria/core"; +import { Border, Colour, Radius, Spacing } from "@lifesg/react-design-system/theme"; + +export const wrapper = css` + display: grid; + grid-template-columns: 1fr 1fr; + gap: ${Spacing["spacing-32"]}; + width: 100%; + height: 80vh; + box-sizing: border-box; +`; + +export const editorPanel = css` + display: flex; + flex-direction: column; + gap: ${Spacing["spacing-16"]}; + min-height: 0; +`; + +export const textarea = css` + flex: 1; + width: 100%; + resize: none; + padding: ${Spacing["spacing-16"]}; + border: ${Border["width-010"]} ${Border.solid} ${Colour.border}; + border-radius: ${Radius.sm}; + font-family: monospace; + font-size: 0.875rem; + line-height: 1.5; + overflow: auto; + box-sizing: border-box; +`; + +export const previewPanel = css` + overflow-y: auto; + padding: ${Spacing["spacing-16"]}; + border: ${Border["width-010"]} ${Border.solid} ${Colour.border}; + border-radius: ${Radius.sm}; +`; + +export const parseError = css` + color: ${Colour["text-error"]}; + font-family: monospace; + font-size: 0.875rem; + white-space: pre-wrap; + margin: 0; +`; + +export const errorBoundaryFallback = css` + display: flex; + flex-direction: column; + gap: ${Spacing["spacing-16"]}; +`; + +export const errorDetails = css` + color: ${Colour["text-error"]}; + font-size: 0.875rem; + white-space: pre-wrap; + margin: 0; + padding: ${Spacing["spacing-16"]}; + background: ${Colour["bg-primary-subtlest"]}; + border-radius: ${Radius.sm}; + overflow: auto; +`; diff --git a/src/stories/1-intro/form-builder/components/schema-playground.tsx b/src/stories/1-intro/form-builder/components/schema-playground.tsx new file mode 100644 index 000000000..72f88f048 --- /dev/null +++ b/src/stories/1-intro/form-builder/components/schema-playground.tsx @@ -0,0 +1,90 @@ +import { Button } from "@lifesg/react-design-system/button"; +import { ThemeProvider } from "@lifesg/react-design-system/theme"; +import { Unstyled } from "@storybook/addon-docs/blocks"; +import { useState } from "react"; +import { IFrontendEngineData } from "../../../../components"; +import { FrontendEngine } from "../../../common"; +import { ErrorBoundary } from "./error-boundary"; +import * as styles from "./schema-playground.styles"; + +const SAMPLE_SCHEMA: IFrontendEngineData = { + sections: { + section: { + uiType: "section", + children: { + name: { + uiType: "text-field", + label: "Name", + }, + fruit: { + uiType: "select", + label: "Favourite fruit", + options: [ + { label: "Apple", value: "apple" }, + { label: "Banana", value: "banana" }, + { label: "Cherry", value: "cherry" }, + ], + }, + agree: { + uiType: "checkbox", + label: "Preferences", + options: [ + { label: "Subscribe to newsletter", value: "newsletter" }, + { label: "I agree to the terms", value: "terms" }, + ], + }, + submit: { + uiType: "submit", + label: "Submit", + }, + }, + }, + }, +}; + +export const SchemaPlayground = () => { + const [schemaText, setSchemaText] = useState(JSON.stringify(SAMPLE_SCHEMA, null, 2)); + const [parsedData, setParsedData] = useState(SAMPLE_SCHEMA); + const [parseError, setParseError] = useState(null); + const [renderKey, setRenderKey] = useState(0); + + const handleRender = () => { + try { + const data = JSON.parse(schemaText) as IFrontendEngineData; + setParsedData(data); + setParseError(null); + setRenderKey((k) => k + 1); + } catch (e) { + setParseError((e as Error).message); + } + }; + + return ( + + +
+
+