diff --git a/example/package-lock.json b/example/package-lock.json index 5a26f864d..40092a46a 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -11,8 +11,10 @@ "dependencies": { "@remoteoss/remote-flows": "file://..", "axios": "1.15.2", + "dompurify": "3.4.1", "dotenv": "17.4.2", "express": "5.2.1", + "html-react-parser": "6.0.1", "jsonwebtoken": "9.0.3", "react": "18.3.1", "react-dom": "18.3.1", @@ -22,6 +24,7 @@ }, "devDependencies": { "@playwright/test": "1.59.1", + "@types/dompurify": "3.0.5", "@types/node": "24.12.2", "@types/react-dom": "19.2.3", "@types/react-syntax-highlighter": "15.5.13", @@ -92,7 +95,7 @@ "glob": "13.0.6", "gzip-size": "7.0.0", "jsdom": "29.0.2", - "msw": "2.13.4", + "msw": "2.13.5", "oxfmt": "0.46.0", "oxlint": "1.61.0", "tsup": "8.5.1", @@ -306,9 +309,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -326,9 +326,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -346,9 +343,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -366,9 +360,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -386,9 +377,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -406,9 +394,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -426,9 +411,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -446,9 +428,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -639,9 +618,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -659,9 +635,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -679,9 +652,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -699,9 +669,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -719,9 +686,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -739,9 +703,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -839,6 +800,16 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/dompurify": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", + "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/trusted-types": "*" + } + }, "node_modules/@types/hast": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", @@ -868,7 +839,7 @@ "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -893,6 +864,13 @@ "@types/react": "*" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "devOptional": true, + "license": "MIT" + }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -1237,7 +1215,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/debug": { @@ -1297,6 +1275,82 @@ "node": ">=8" } }, + "node_modules/dom-serializer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-3.0.0.tgz", + "integrity": "sha512-x+9D6nkC8tdXOQUS32egtZpZFLP90+HBZmWjuT920srbJvD/zPgFB9t4k3pEhlw5BQrXStQtRc1Y1zuriXk+Nw==", + "license": "MIT", + "dependencies": { + "domelementtype": "^3.0.0", + "domhandler": "^6.0.0", + "entities": "^8.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-3.0.0.tgz", + "integrity": "sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/domhandler": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-6.0.1.tgz", + "integrity": "sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^3.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/dompurify": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.1.tgz", + "integrity": "sha512-JahakDAIg1gyOm7dlgWSDjV4n7Ip2PKR55NIT6jrMfIgLFgWo81vdr1/QGqWtFNRqXP9UV71oVePtjqS2ebnPw==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/domutils": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-4.0.2.tgz", + "integrity": "sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^3.0.0", + "domelementtype": "^3.0.0", + "domhandler": "^6.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dotenv": { "version": "17.4.2", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", @@ -1344,6 +1398,18 @@ "node": ">= 0.8" } }, + "node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -1739,6 +1805,75 @@ "resolved": "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz", "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==" }, + "node_modules/html-dom-parser": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/html-dom-parser/-/html-dom-parser-7.0.1.tgz", + "integrity": "sha512-loRBDTCY/05/jAC63J1X9ID+xjRucmpLkIcQO0IRbOubBo5ucnpUpyXXob9UMXOskMZlu7KPsDP/2KOMelzJNA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/remarkablemark" + } + ], + "license": "MIT", + "dependencies": { + "domhandler": "6.0.1", + "htmlparser2": "12.0.0" + } + }, + "node_modules/html-react-parser": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-6.0.1.tgz", + "integrity": "sha512-tIie2HSIk2Ct1tdupjd/DhBjskxN/NL5J4ncbUnk2smBr5UIfpPpitUo0imGfBM0BlOL7ac8RcqEwne1jXTcsQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/remarkablemark" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/html-react-parser" + } + ], + "license": "MIT", + "dependencies": { + "domhandler": "6.0.1", + "html-dom-parser": "7.0.1", + "react-property": "2.0.2", + "style-to-js": "1.1.21" + }, + "peerDependencies": { + "@types/react": "0.14 || 15 || 16 || 17 || 18 || 19", + "react": "0.14 || 15 || 16 || 17 || 18 || 19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/htmlparser2": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-12.0.0.tgz", + "integrity": "sha512-Tz7u1i95/g2x2jz81+x0FBVhBhY5aRTvD3tXXdFaljuNdzDLJ8UGNRrTcj2cgQvAg3iW/h77Fz15nLW0L0CrZw==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^3.0.0", + "domhandler": "^6.0.0", + "domutils": "^4.0.2", + "entities": "^8.0.0" + }, + "engines": { + "node": ">=20.19.0" + } + }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -1786,6 +1921,12 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -2787,6 +2928,12 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/react-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/react-property/-/react-property-2.0.2.tgz", + "integrity": "sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==", + "license": "MIT" + }, "node_modules/react-syntax-highlighter": { "version": "16.1.1", "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-16.1.1.tgz", @@ -3124,6 +3271,24 @@ "node": ">= 0.8" } }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, "node_modules/tinyglobby": { "version": "0.2.16", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", diff --git a/example/package.json b/example/package.json index ca8ae68aa..b1c1b7ba4 100644 --- a/example/package.json +++ b/example/package.json @@ -17,8 +17,10 @@ "dependencies": { "@remoteoss/remote-flows": "file://..", "axios": "1.15.2", + "dompurify": "3.4.1", "dotenv": "17.4.2", "express": "5.2.1", + "html-react-parser": "6.0.1", "jsonwebtoken": "9.0.3", "react": "18.3.1", "react-dom": "18.3.1", @@ -28,6 +30,7 @@ }, "devDependencies": { "@playwright/test": "1.59.1", + "@types/dompurify": "3.0.5", "@types/node": "24.12.2", "@types/react-dom": "19.2.3", "@types/react-syntax-highlighter": "15.5.13", diff --git a/example/src/Components.tsx b/example/src/Components.tsx index b087ae4e0..d1cc067f1 100644 --- a/example/src/Components.tsx +++ b/example/src/Components.tsx @@ -9,6 +9,21 @@ import type { import { FileUploader } from '@remoteoss/remote-flows/internals'; //import { ZendeskDialog } from './ZendeskDialog'; +const renderDescription = ( + desc?: React.ReactNode | string, + transformHtml?: (html: string) => React.ReactNode, +) => { + if (!desc) { + return null; + } + + if (typeof desc === 'string' && transformHtml) { + return transformHtml(desc); + } + + return
{desc}
; +}; + // you can define HTML button attributes or event props that exist in your Button like variant, size, etc. const Button = ({ children, @@ -57,9 +72,7 @@ const Input = ({ field, fieldData, fieldState }: FieldComponentProps) => { /> )} - {fieldData.description && ( -{fieldData.description}
- )} + {renderDescription(fieldData.description, fieldData.transformHtml)} {fieldState.error && ({fieldState.error.message}
)} @@ -111,9 +124,7 @@ const Select = ({ field, fieldData, fieldState }: FieldComponentProps) => { - {fieldData.description && ( -{fieldData.description}
- )} + {renderDescription(fieldData.description, fieldData.transformHtml)} {fieldState.error && ({fieldState.error.message}
@@ -134,9 +145,7 @@ const Textarea = ({ field, fieldData, fieldState }: FieldComponentProps) => { maxLength={fieldData.maxLength} {...field} /> - {fieldData.description && ( -{fieldData.description}
- )} + {renderDescription(fieldData.description, fieldData.transformHtml)} {fieldState.error && ({fieldState.error.message}
)} @@ -166,9 +175,7 @@ const Radio = ({ field, fieldData, fieldState }: FieldComponentProps) => { ); })} - {fieldData.description && ( -{fieldData.description}
- )} + {renderDescription(fieldData.description, fieldData.transformHtml)} {hasError &&{fieldState.error?.message}
} ); @@ -183,9 +190,7 @@ const Checkbox = ({ field, fieldData, fieldState }: FieldComponentProps) => { - {fieldData.description && ( -{fieldData.description}
- )} + {renderDescription(fieldData.description, fieldData.transformHtml)} {hasError &&{fieldState.error?.message}
} ); @@ -236,9 +241,7 @@ export const Countries = ({ - {fieldData.description && ( -{fieldData.description}
- )} + {renderDescription(fieldData.description, fieldData.transformHtml)} {fieldState.error && ({fieldState.error.message}
@@ -283,9 +286,7 @@ const FileUploadField = ({ accept={fieldData.accept} multiple={fieldData.multiple} /> - {fieldData.description && ( -{fieldData.description}
- )} + {renderDescription(fieldData.description, fieldData.transformHtml)} {fieldState.error && ({fieldState.error.message}
)} @@ -309,9 +310,7 @@ const DatePickerInput = ({ field?.onChange?.(e.target.value); }} /> - {fieldData.description && ( -{fieldData.description}
- )} + {renderDescription(fieldData.description, fieldData.transformHtml)} {fieldState.error && ({fieldState.error.message}
)} diff --git a/example/src/Onboarding.tsx b/example/src/Onboarding.tsx index 7f3ea2ebc..7b91c89d7 100644 --- a/example/src/Onboarding.tsx +++ b/example/src/Onboarding.tsx @@ -19,6 +19,7 @@ import { ReviewOnboardingStep } from './ReviewOnboardingStep'; import { OnboardingAlertStatuses } from './OnboardingAlertStatuses'; import { RemoteFlows } from './RemoteFlows'; import { AlertError } from './AlertError'; +import { transformHtmlToComponents } from './utils/transformHtml'; import './css/main.css'; export const InviteSection = ({ @@ -355,7 +356,10 @@ const OnboardingWithProps = ({ employmentId, externalId, }: OnboardingFormData) => ( -