@qwickapps/react-framework 1.3.5 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1681 -2
- package/dist/__tests__/schemas/transformers/MockSerializableComponent.d.ts +66 -0
- package/dist/__tests__/schemas/transformers/MockSerializableComponent.d.ts.map +1 -0
- package/dist/components/ErrorBoundary.d.ts +7 -0
- package/dist/components/ErrorBoundary.d.ts.map +1 -1
- package/dist/components/Html.d.ts +28 -18
- package/dist/components/Html.d.ts.map +1 -1
- package/dist/components/Logo.d.ts +12 -35
- package/dist/components/Logo.d.ts.map +1 -1
- package/dist/components/Markdown.d.ts +18 -13
- package/dist/components/Markdown.d.ts.map +1 -1
- package/dist/components/QwickApp.d.ts +16 -3
- package/dist/components/QwickApp.d.ts.map +1 -1
- package/dist/components/QwickIcon.d.ts +23 -0
- package/dist/components/QwickIcon.d.ts.map +1 -0
- package/dist/components/SafeSpan.d.ts +12 -5
- package/dist/components/SafeSpan.d.ts.map +1 -1
- package/dist/components/Scaffold.d.ts.map +1 -1
- package/dist/components/base/ModelView.d.ts +101 -0
- package/dist/components/base/ModelView.d.ts.map +1 -0
- package/dist/components/base/index.d.ts +11 -0
- package/dist/components/base/index.d.ts.map +1 -0
- package/dist/components/blocks/Article.d.ts +12 -2
- package/dist/components/blocks/Article.d.ts.map +1 -1
- package/dist/components/blocks/Code.d.ts +13 -2
- package/dist/components/blocks/Code.d.ts.map +1 -1
- package/dist/components/blocks/CoverImageHeader.d.ts.map +1 -1
- package/dist/components/blocks/FeatureCard.d.ts.map +1 -1
- package/dist/components/blocks/FeatureGrid.d.ts.map +1 -1
- package/dist/components/blocks/Footer.d.ts.map +1 -1
- package/dist/components/blocks/HeroBlock.d.ts +27 -13
- package/dist/components/blocks/HeroBlock.d.ts.map +1 -1
- package/dist/components/blocks/Image.d.ts +41 -0
- package/dist/components/blocks/Image.d.ts.map +1 -0
- package/dist/components/blocks/PageBannerHeader.d.ts.map +1 -1
- package/dist/components/blocks/Section.d.ts +16 -2
- package/dist/components/blocks/Section.d.ts.map +1 -1
- package/dist/components/blocks/Text.d.ts +41 -0
- package/dist/components/blocks/Text.d.ts.map +1 -0
- package/dist/components/blocks/index.d.ts +4 -0
- package/dist/components/blocks/index.d.ts.map +1 -1
- package/dist/components/buttons/Button.d.ts +23 -7
- package/dist/components/buttons/Button.d.ts.map +1 -1
- package/dist/components/forms/FormBlock.d.ts +19 -13
- package/dist/components/forms/FormBlock.d.ts.map +1 -1
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/input/ChoiceInputField.d.ts +17 -11
- package/dist/components/input/ChoiceInputField.d.ts.map +1 -1
- package/dist/components/input/HtmlInputField.d.ts +17 -11
- package/dist/components/input/HtmlInputField.d.ts.map +1 -1
- package/dist/components/input/SelectInputField.d.ts +16 -10
- package/dist/components/input/SelectInputField.d.ts.map +1 -1
- package/dist/components/input/SwitchInputField.d.ts +16 -10
- package/dist/components/input/SwitchInputField.d.ts.map +1 -1
- package/dist/components/input/TextField.d.ts.map +1 -1
- package/dist/components/input/TextInputField.d.ts +16 -11
- package/dist/components/input/TextInputField.d.ts.map +1 -1
- package/dist/components/layout/GridCell.d.ts +23 -6
- package/dist/components/layout/GridCell.d.ts.map +1 -1
- package/dist/components/layout/GridLayout.d.ts +24 -23
- package/dist/components/layout/GridLayout.d.ts.map +1 -1
- package/dist/components/pages/FormPage.d.ts.map +1 -1
- package/dist/components/pages/Page.d.ts +49 -87
- package/dist/components/pages/Page.d.ts.map +1 -1
- package/dist/components/pages/index.d.ts +2 -2
- package/dist/components/pages/index.d.ts.map +1 -1
- package/dist/config/AppConfig.d.ts +49 -0
- package/dist/config/AppConfig.d.ts.map +1 -0
- package/dist/config/AppConfigBuilder.d.ts +75 -0
- package/dist/config/AppConfigBuilder.d.ts.map +1 -0
- package/dist/config/index.d.ts +13 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/types.d.ts +130 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config.d.ts +15 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.esm.js +451 -0
- package/dist/config.js +455 -0
- package/dist/contexts/PrintModeContext.d.ts +27 -0
- package/dist/contexts/PrintModeContext.d.ts.map +1 -0
- package/dist/contexts/QwickAppContext.d.ts +2 -2
- package/dist/contexts/QwickAppContext.d.ts.map +1 -1
- package/dist/contexts/index.d.ts +2 -0
- package/dist/contexts/index.d.ts.map +1 -1
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/usePrintMode.d.ts +39 -0
- package/dist/hooks/usePrintMode.d.ts.map +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.js +10951 -6238
- package/dist/index.js +11014 -6287
- package/dist/schemas/CodeSchema.d.ts +2 -1
- package/dist/schemas/CodeSchema.d.ts.map +1 -1
- package/dist/schemas/CollapsibleLayoutSchema.d.ts +2 -1
- package/dist/schemas/CollapsibleLayoutSchema.d.ts.map +1 -1
- package/dist/schemas/ContentSchema.d.ts +2 -1
- package/dist/schemas/ContentSchema.d.ts.map +1 -1
- package/dist/schemas/GridCellSchema.d.ts +25 -0
- package/dist/schemas/GridCellSchema.d.ts.map +1 -0
- package/dist/schemas/GridLayoutSchema.d.ts +23 -0
- package/dist/schemas/GridLayoutSchema.d.ts.map +1 -0
- package/dist/schemas/HtmlSchema.d.ts +14 -0
- package/dist/schemas/HtmlSchema.d.ts.map +1 -0
- package/dist/schemas/ImageSchema.d.ts +32 -0
- package/dist/schemas/ImageSchema.d.ts.map +1 -0
- package/dist/schemas/LogoSchema.d.ts +35 -0
- package/dist/schemas/LogoSchema.d.ts.map +1 -0
- package/dist/schemas/MarkdownSchema.d.ts +14 -0
- package/dist/schemas/MarkdownSchema.d.ts.map +1 -0
- package/dist/schemas/PageTemplateSchema.d.ts +31 -0
- package/dist/schemas/PageTemplateSchema.d.ts.map +1 -0
- package/dist/schemas/PrintConfigSchema.d.ts +31 -0
- package/dist/schemas/PrintConfigSchema.d.ts.map +1 -0
- package/dist/schemas/SectionSchema.d.ts +2 -1
- package/dist/schemas/SectionSchema.d.ts.map +1 -1
- package/dist/schemas/TextSchema.d.ts +37 -0
- package/dist/schemas/TextSchema.d.ts.map +1 -0
- package/dist/schemas/ViewModelSchema.d.ts +23 -0
- package/dist/schemas/ViewModelSchema.d.ts.map +1 -0
- package/dist/schemas/index.d.ts +15 -1
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/transformers/ComponentTransformer.d.ts +116 -0
- package/dist/schemas/transformers/ComponentTransformer.d.ts.map +1 -0
- package/dist/schemas/transformers/ReactNodeTransformer.d.ts +53 -0
- package/dist/schemas/transformers/ReactNodeTransformer.d.ts.map +1 -0
- package/dist/schemas/transformers/__tests__/MockSerializableComponent.d.ts +66 -0
- package/dist/schemas/transformers/__tests__/MockSerializableComponent.d.ts.map +1 -0
- package/dist/schemas/transformers/registry.d.ts +15 -0
- package/dist/schemas/transformers/registry.d.ts.map +1 -0
- package/dist/schemas/types/Serializable.d.ts +46 -0
- package/dist/schemas/types/Serializable.d.ts.map +1 -0
- package/dist/utils/htmlTransform.d.ts.map +1 -1
- package/dist/utils/reactUtils.d.ts +12 -3
- package/dist/utils/reactUtils.d.ts.map +1 -1
- package/package.json +17 -3
- package/src/{components/__tests__ → __tests__/components}/AccessibilityProvider.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/Article.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/Breadcrumbs.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/Button.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/CardListGrid.test.tsx +2 -2
- package/src/{components/__tests__ → __tests__/components}/ChoiceInputField.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/Code.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/Content.integration.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/Content.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/CoverImageHeader.test.tsx +2 -2
- package/src/{components/__tests__ → __tests__/components}/ErrorBoundary.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/FeatureCard.integration.test.tsx +2 -2
- package/src/{components/__tests__ → __tests__/components}/FeatureGrid.integration.test.tsx +2 -2
- package/src/{components/__tests__ → __tests__/components}/FeatureGrid.test.tsx +2 -2
- package/src/{components/__tests__ → __tests__/components}/Footer.test.tsx +4 -4
- package/src/{components/__tests__ → __tests__/components}/FormBlock.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/HeroBlock.integration.test.tsx +2 -2
- package/src/{components/__tests__ → __tests__/components}/HeroBlock.test.tsx +233 -7
- package/src/{components/__tests__ → __tests__/components}/Html.test.tsx +11 -2
- package/src/{components/__tests__ → __tests__/components}/HtmlInputField.test.tsx +3 -3
- package/src/__tests__/components/Logo.test.js +3 -3
- package/src/{components/__tests__ → __tests__/components}/Markdown.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/PageBannerHeader.test.tsx +3 -3
- package/src/{components/__tests__ → __tests__/components}/PaletteSwitcher.test.tsx +3 -3
- package/src/{components/__tests__ → __tests__/components}/ProductCard.test.tsx +4 -4
- package/src/{components/__tests__ → __tests__/components}/SafeSpan.integration.test.tsx +2 -2
- package/src/{components/__tests__ → __tests__/components}/SafeSpan.simple.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/SafeSpan.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/Section.integration.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/Section.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/SelectInputField.test.tsx +1 -1
- package/src/{components/__tests__ → __tests__/components}/TextInputField.test.tsx +3 -3
- package/src/{components/__tests__ → __tests__/components}/ThemeSwitcher.test.tsx +3 -3
- package/src/__tests__/components/base/ModelView.test.tsx +220 -0
- package/src/__tests__/components/blocks/Code.performance.test.tsx +625 -0
- package/src/__tests__/components/blocks/Code.serialization.test.tsx +507 -0
- package/src/__tests__/components/blocks/HeroBlock.serialization.test.tsx +414 -0
- package/src/__tests__/components/blocks/Image.serialization.test.tsx +257 -0
- package/src/__tests__/components/blocks/Section.serialization.test.tsx +553 -0
- package/src/__tests__/components/blocks/Text.performance.test.tsx +442 -0
- package/src/__tests__/components/blocks/Text.serialization.test.tsx +491 -0
- package/src/__tests__/components/buttons/Button.serialization.test.tsx +443 -0
- package/src/__tests__/components/input/FormComponents.serialization.test.tsx +482 -0
- package/src/__tests__/components/input/SelectInputField.serialization.test.tsx +439 -0
- package/src/__tests__/components/input/TextInputField.serialization.test.tsx +359 -0
- package/src/{components/layout/CollapsibleLayout/__tests__ → __tests__/components/layout}/CollapsibleLayout.test.tsx +4 -4
- package/src/__tests__/components/layout/GridCell.serialization.test.tsx +403 -0
- package/src/__tests__/components/layout/GridLayout.serialization.test.tsx +311 -0
- package/src/__tests__/hooks/usePrintMode.test.ts +89 -0
- package/src/__tests__/schemas/PageTemplateSchema.test.ts +161 -0
- package/src/__tests__/schemas/PrintConfigSchema.test.ts +127 -0
- package/src/__tests__/schemas/ViewModelSchema.test.ts +80 -0
- package/src/__tests__/schemas/transformers/ComponentSerializationPatterns.test.tsx +602 -0
- package/src/__tests__/schemas/transformers/ComponentTransformer.htmlPatterns.test.ts +301 -0
- package/src/__tests__/schemas/transformers/ComponentTransformer.test.ts +521 -0
- package/src/__tests__/schemas/transformers/CrossBrowserCompatibility.test.ts +586 -0
- package/src/__tests__/schemas/transformers/MockSerializableComponent.ts +103 -0
- package/src/__tests__/schemas/transformers/RealWorldScenarios.test.tsx +1165 -0
- package/src/__tests__/schemas/transformers/SerializationErrorHandling.test.ts +602 -0
- package/src/__tests__/schemas/transformers/SerializationIntegration.test.tsx +691 -0
- package/src/__tests__/schemas/transformers/SerializationPerformance.test.ts +460 -0
- package/src/__tests__/schemas/transformers/TestAutomation.test.ts +597 -0
- package/src/{utils/__tests__ → __tests__/utils}/nested-dom-fix.test.tsx +1 -1
- package/src/components/ErrorBoundary.tsx +8 -8
- package/src/components/Html.tsx +147 -44
- package/src/components/Logo.tsx +198 -100
- package/src/components/Markdown.tsx +125 -16
- package/src/components/QwickApp.tsx +64 -31
- package/src/components/QwickIcon.tsx +59 -0
- package/src/components/SafeSpan.tsx +65 -10
- package/src/components/Scaffold.tsx +2 -8
- package/src/components/base/ModelView.tsx +199 -0
- package/src/components/base/index.ts +11 -0
- package/src/components/blocks/Article.tsx +57 -18
- package/src/components/blocks/Code.md +529 -0
- package/src/components/blocks/Code.tsx +102 -15
- package/src/components/blocks/CoverImageHeader.tsx +9 -4
- package/src/components/blocks/FeatureCard.tsx +1 -2
- package/src/components/blocks/FeatureGrid.tsx +19 -1
- package/src/components/blocks/Footer.tsx +13 -1
- package/src/components/blocks/HeroBlock.tsx +87 -20
- package/src/components/blocks/Image.tsx +395 -0
- package/src/components/blocks/PageBannerHeader.tsx +14 -12
- package/src/components/blocks/ProductCard.tsx +1 -1
- package/src/components/blocks/Section.tsx +113 -8
- package/src/components/blocks/Text.tsx +285 -0
- package/src/components/blocks/index.ts +4 -0
- package/src/components/buttons/Button.tsx +184 -15
- package/src/components/forms/FormBlock.tsx +70 -17
- package/src/components/index.ts +5 -0
- package/src/components/input/ChoiceInputField.tsx +48 -18
- package/src/components/input/HtmlInputField.tsx +48 -18
- package/src/components/input/SelectInputField.tsx +48 -16
- package/src/components/input/SwitchInputField.tsx +48 -17
- package/src/components/input/TextField.tsx +41 -1
- package/src/components/input/TextInputField.tsx +52 -18
- package/src/components/layout/GridCell.tsx +118 -9
- package/src/components/layout/GridLayout.tsx +125 -24
- package/src/components/pages/FormPage.tsx +0 -1
- package/src/components/pages/Page.css +304 -332
- package/src/components/pages/Page.tsx +307 -255
- package/src/components/pages/index.ts +2 -2
- package/src/config/AppConfig.ts +133 -0
- package/src/config/AppConfigBuilder.ts +421 -0
- package/src/config/__tests__/AppConfig.test.ts +385 -0
- package/src/config/__tests__/AppConfigBuilder.test.ts +432 -0
- package/src/config/index.ts +24 -0
- package/src/config/types.ts +170 -0
- package/src/config.ts +25 -0
- package/src/contexts/PrintModeContext.tsx +332 -0
- package/src/contexts/QwickAppContext.tsx +2 -2
- package/src/contexts/index.ts +2 -0
- package/src/hooks/index.ts +5 -1
- package/src/hooks/usePrintMode.ts +73 -0
- package/src/index.ts +3 -0
- package/src/schemas/CodeSchema.ts +3 -3
- package/src/schemas/CollapsibleLayoutSchema.ts +2 -1
- package/src/schemas/ContentSchema.ts +2 -1
- package/src/schemas/GridCellSchema.ts +164 -0
- package/src/schemas/GridLayoutSchema.ts +133 -0
- package/src/schemas/HtmlSchema.ts +47 -0
- package/src/schemas/ImageSchema.ts +235 -0
- package/src/schemas/LogoSchema.ts +241 -0
- package/src/schemas/MarkdownSchema.ts +47 -0
- package/src/schemas/PageTemplateSchema.ts +186 -0
- package/src/schemas/PrintConfigSchema.ts +207 -0
- package/src/schemas/README.md +661 -0
- package/src/schemas/SectionSchema.ts +2 -1
- package/src/schemas/TextSchema.ts +329 -0
- package/src/schemas/ViewModelSchema.ts +115 -0
- package/src/schemas/index.ts +21 -2
- package/src/schemas/transformers/ComponentTransformer.ts +403 -0
- package/src/schemas/transformers/ReactNodeTransformer.ts +236 -0
- package/src/schemas/transformers/registry.ts +72 -0
- package/src/schemas/types/Serializable.ts +51 -0
- package/src/stories/AccessibilityProvider.stories.tsx +253 -253
- package/src/stories/Article.stories.tsx +433 -433
- package/src/stories/Button.stories.tsx +1 -1
- package/src/stories/CardListGrid.stories.tsx +451 -451
- package/src/stories/ChoiceInputField.stories.tsx +503 -503
- package/src/stories/Code.stories.tsx +1 -1
- package/src/stories/CollapsibleLayout.stories.tsx +1414 -1414
- package/src/stories/Content.stories.tsx +393 -393
- package/src/stories/CoverImageHeader.stories.tsx +701 -701
- package/src/stories/DataBinding.advanced.stories.tsx +432 -432
- package/src/stories/DataProvider.stories.tsx +1192 -1192
- package/src/stories/FeatureCard.stories.tsx +557 -557
- package/src/stories/FeatureGrid.stories.tsx +594 -594
- package/src/stories/Footer.stories.tsx +640 -640
- package/src/stories/FormBlock.stories.tsx +760 -760
- package/src/stories/FormComponents.stories.tsx +349 -541
- package/src/stories/GridCell.stories.tsx +417 -0
- package/src/stories/GridLayout.stories.tsx +353 -0
- package/src/stories/HeroBlock.stories.tsx +862 -373
- package/src/stories/HtmlInputField.stories.tsx +474 -474
- package/src/stories/Image.stories.tsx +819 -0
- package/src/stories/Introduction.stories.tsx +667 -667
- package/src/stories/LayoutBlocks.stories.tsx +324 -324
- package/src/stories/Logo.stories.tsx +165 -6
- package/src/stories/Markdown.stories.tsx +137 -137
- package/src/stories/ModelView.stories.tsx +477 -0
- package/src/stories/Page.stories.tsx +688 -688
- package/src/stories/PageBannerHeader.stories.tsx +864 -864
- package/src/stories/PaletteSwitcher.stories.tsx +119 -119
- package/src/stories/ProductCard.stories.tsx +424 -424
- package/src/stories/QwickApp.stories.tsx +368 -368
- package/src/stories/ResponsiveMenu.stories.tsx +249 -249
- package/src/stories/SafeSpan.stories.tsx +531 -531
- package/src/stories/Section.stories.tsx +90 -2
- package/src/stories/SelectInputField.stories.tsx +524 -524
- package/src/stories/Text.stories.tsx +560 -0
- package/src/stories/TextInputField.stories.tsx +443 -443
- package/src/stories/ThemeSwitcher.stories.tsx +123 -123
- package/src/utils/htmlTransform.tsx +74 -53
- package/src/utils/reactUtils.tsx +57 -6
- package/dist/index.bundled.css +0 -12
- /package/src/{hooks/__tests__ → __tests__/hooks}/useDataBinding.test.tsx.disabled +0 -0
- /package/src/{schemas/__tests__ → __tests__/schemas}/builders.test.ts +0 -0
- /package/src/{utils/__tests__ → __tests__/utils}/createDataDrivenComponent.test.tsx.disabled +0 -0
- /package/src/{utils/__tests__ → __tests__/utils}/htmlTransform.test.tsx +0 -0
- /package/src/{utils/__tests__ → __tests__/utils}/optional-logging.test.ts +0 -0
|
@@ -5,12 +5,12 @@ import { GridCell, GridLayout } from '../components/layout';
|
|
|
5
5
|
import SafeSpan from '../components/SafeSpan';
|
|
6
6
|
|
|
7
7
|
const meta: Meta<typeof SafeSpan> = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
title: 'Components/SafeSpan',
|
|
9
|
+
component: SafeSpan,
|
|
10
|
+
parameters: {
|
|
11
|
+
docs: {
|
|
12
|
+
description: {
|
|
13
|
+
component: `SafeSpan safely renders HTML content with automatic sanitization, protecting against XSS attacks while preserving formatting.
|
|
14
14
|
|
|
15
15
|
**Key Features:**
|
|
16
16
|
- **XSS Protection**: Automatically sanitizes dangerous HTML elements and attributes
|
|
@@ -29,9 +29,9 @@ const meta: Meta<typeof SafeSpan> = {
|
|
|
29
29
|
- Third-party content integration
|
|
30
30
|
- CMS and blog content display
|
|
31
31
|
- Any scenario requiring safe HTML rendering`,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
export default meta;
|
|
@@ -39,574 +39,574 @@ type Story = StoryObj<typeof meta>;
|
|
|
39
39
|
|
|
40
40
|
// Sample HTML content for demonstrations
|
|
41
41
|
const safeHtml = `
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
<p>This is <strong>safe HTML</strong> content with <em>emphasis</em> and a <a href="#safe-link">safe link</a>.</p>
|
|
43
|
+
<ul>
|
|
44
|
+
<li>List item with <code>code formatting</code></li>
|
|
45
|
+
<li>Another item with <mark>highlighted text</mark></li>
|
|
46
|
+
</ul>
|
|
47
47
|
`;
|
|
48
48
|
|
|
49
49
|
const unsafeHtml = `
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
<p>This content contains <strong>potentially dangerous</strong> elements:</p>
|
|
51
|
+
<script>alert('XSS Attack!');</script>
|
|
52
|
+
<img src="x" onerror="alert('Image XSS')">
|
|
53
|
+
<p onclick="alert('Click XSS')">This paragraph has an onclick handler</p>
|
|
54
|
+
<iframe src="javascript:alert('Iframe XSS')"></iframe>
|
|
55
|
+
<style>body { display: none; }</style>
|
|
56
56
|
`;
|
|
57
57
|
|
|
58
58
|
const richContentHtml = `
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
59
|
+
<div>
|
|
60
|
+
<h3>Rich Content Example</h3>
|
|
61
|
+
<p>This is a paragraph with <strong>bold text</strong>, <em>italic text</em>, and <u>underlined text</u>.</p>
|
|
62
|
+
<blockquote>This is a blockquote with important information.</blockquote>
|
|
63
|
+
<ul>
|
|
64
|
+
<li>First item</li>
|
|
65
|
+
<li>Second item with <code>inline code</code></li>
|
|
66
|
+
<li>Third item</li>
|
|
67
|
+
</ul>
|
|
68
|
+
<p>Links: <a href="https://example.com" target="_blank">External Link</a> | <a href="#internal">Internal Link</a></p>
|
|
69
|
+
</div>
|
|
70
70
|
`;
|
|
71
71
|
|
|
72
72
|
export const BasicUsage: Story = {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
73
|
+
render: () => (
|
|
74
|
+
<Section padding="large">
|
|
75
|
+
<Content title="Basic SafeSpan Usage" centered maxWidth="large">
|
|
76
|
+
<GridLayout columns={2} spacing="large">
|
|
77
|
+
<GridCell>
|
|
78
|
+
<Content variant="outlined" spacing='comfortable'>
|
|
79
|
+
<h3>Safe HTML Content</h3>
|
|
80
|
+
<div style={{
|
|
81
|
+
background: 'var(--theme-surface-variant)',
|
|
82
|
+
padding: '1rem',
|
|
83
|
+
borderRadius: '8px',
|
|
84
|
+
marginBottom: '1rem'
|
|
85
|
+
}}>
|
|
86
|
+
<SafeSpan html={safeHtml} />
|
|
87
|
+
</div>
|
|
88
|
+
<p><strong>Note:</strong> All HTML is safely rendered with proper sanitization.</p>
|
|
89
|
+
</Content>
|
|
90
|
+
</GridCell>
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
92
|
+
<GridCell>
|
|
93
|
+
<Content variant="outlined" spacing='comfortable'>
|
|
94
|
+
<h3>With Placeholder</h3>
|
|
95
|
+
<div style={{
|
|
96
|
+
background: 'var(--theme-surface-variant)',
|
|
97
|
+
padding: '1rem',
|
|
98
|
+
borderRadius: '8px',
|
|
99
|
+
marginBottom: '1rem'
|
|
100
|
+
}}>
|
|
101
|
+
<SafeSpan html="" placeholder="No content available" />
|
|
102
|
+
</div>
|
|
103
|
+
<p><strong>Note:</strong> When no HTML is provided, the placeholder text is shown.</p>
|
|
104
|
+
</Content>
|
|
105
|
+
</GridCell>
|
|
106
|
+
</GridLayout>
|
|
107
|
+
</Content>
|
|
108
|
+
</Section>
|
|
109
|
+
),
|
|
110
110
|
};
|
|
111
111
|
|
|
112
112
|
export const SecurityDemonstration: Story = {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
113
|
+
render: () => (
|
|
114
|
+
<Section padding="large">
|
|
115
|
+
<Content title="Security Features" centered maxWidth="large">
|
|
116
|
+
<div style={{
|
|
117
|
+
background: 'var(--theme-error, #dc2626)',
|
|
118
|
+
color: 'white',
|
|
119
|
+
padding: '1rem',
|
|
120
|
+
borderRadius: '8px',
|
|
121
|
+
marginBottom: '2rem',
|
|
122
|
+
textAlign: 'center'
|
|
123
|
+
}}>
|
|
124
|
+
<strong> Security Demo:</strong> The content below contains malicious scripts that are automatically sanitized.
|
|
125
|
+
</div>
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
127
|
+
<GridLayout columns={1} spacing="large">
|
|
128
|
+
<GridCell>
|
|
129
|
+
<Content variant="elevated" spacing='comfortable'>
|
|
130
|
+
<h3>Dangerous HTML Input (Sanitized Output)</h3>
|
|
131
|
+
<div style={{
|
|
132
|
+
background: 'var(--theme-surface-variant)',
|
|
133
|
+
padding: '1rem',
|
|
134
|
+
borderRadius: '8px',
|
|
135
|
+
marginBottom: '1rem',
|
|
136
|
+
border: '2px solid var(--theme-primary)'
|
|
137
|
+
}}>
|
|
138
|
+
<SafeSpan html={unsafeHtml} />
|
|
139
|
+
</div>
|
|
140
|
+
<p><strong>✅ SafeSpan automatically removed:</strong></p>
|
|
141
|
+
<ul>
|
|
142
|
+
<li><code><script></code> tags</li>
|
|
143
|
+
<li><code>onerror</code> and <code>onclick</code> event handlers</li>
|
|
144
|
+
<li><code><iframe></code> with javascript: protocol</li>
|
|
145
|
+
<li><code><style></code> tags that could hide content</li>
|
|
146
|
+
</ul>
|
|
147
|
+
<p>Only safe HTML elements and attributes are preserved.</p>
|
|
148
|
+
</Content>
|
|
149
|
+
</GridCell>
|
|
150
|
+
</GridLayout>
|
|
151
|
+
</Content>
|
|
152
|
+
</Section>
|
|
153
|
+
),
|
|
154
154
|
};
|
|
155
155
|
|
|
156
156
|
export const RichContentExample: Story = {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
157
|
+
render: () => (
|
|
158
|
+
<Section padding="large">
|
|
159
|
+
<Content title="Rich Content Support" centered maxWidth="large">
|
|
160
|
+
<GridLayout columns={1} spacing="large">
|
|
161
|
+
<GridCell>
|
|
162
|
+
<Content variant="filled" spacing='comfortable'>
|
|
163
|
+
<div style={{
|
|
164
|
+
background: 'var(--theme-surface)',
|
|
165
|
+
padding: '2rem',
|
|
166
|
+
borderRadius: '12px',
|
|
167
|
+
boxShadow: 'var(--theme-elevation-1, 0 2px 4px rgba(0, 0, 0, 0.1))'
|
|
168
|
+
}}>
|
|
169
|
+
<SafeSpan html={richContentHtml} />
|
|
170
|
+
</div>
|
|
171
|
+
</Content>
|
|
172
|
+
</GridCell>
|
|
173
|
+
</GridLayout>
|
|
174
174
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
175
|
+
<Content spacing='comfortable' centered>
|
|
176
|
+
<p><strong>Supported HTML Elements:</strong></p>
|
|
177
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '1rem', marginTop: '1rem' }}>
|
|
178
|
+
<div>
|
|
179
|
+
<strong>Text Formatting:</strong>
|
|
180
|
+
<ul>
|
|
181
|
+
<li>p, div, span</li>
|
|
182
|
+
<li>strong, b, em, i</li>
|
|
183
|
+
<li>u, mark, code</li>
|
|
184
|
+
</ul>
|
|
185
|
+
</div>
|
|
186
|
+
<div>
|
|
187
|
+
<strong>Structure:</strong>
|
|
188
|
+
<ul>
|
|
189
|
+
<li>h1, h2, h3, h4, h5, h6</li>
|
|
190
|
+
<li>ul, ol, li</li>
|
|
191
|
+
<li>blockquote</li>
|
|
192
|
+
</ul>
|
|
193
|
+
</div>
|
|
194
|
+
<div>
|
|
195
|
+
<strong>Links & Media:</strong>
|
|
196
|
+
<ul>
|
|
197
|
+
<li>a (with safe href)</li>
|
|
198
|
+
<li>img (with safe src)</li>
|
|
199
|
+
<li>br, hr</li>
|
|
200
|
+
</ul>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
</Content>
|
|
204
|
+
</Content>
|
|
205
|
+
</Section>
|
|
206
|
+
),
|
|
207
207
|
};
|
|
208
208
|
|
|
209
209
|
export const CustomStyling: Story = {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
210
|
+
render: () => (
|
|
211
|
+
<Section padding="large">
|
|
212
|
+
<Content title="Custom Styling" centered maxWidth="large">
|
|
213
|
+
<GridLayout columns={2} spacing="large">
|
|
214
|
+
<GridCell>
|
|
215
|
+
<Content variant="outlined" spacing='comfortable'>
|
|
216
|
+
<h3>Custom CSS Class</h3>
|
|
217
|
+
<div style={{ marginBottom: '1rem' }}>
|
|
218
|
+
<SafeSpan
|
|
219
|
+
html="<p>This content has <strong>custom styling</strong> applied.</p>"
|
|
220
|
+
className="custom-safe-span"
|
|
221
|
+
style={{
|
|
222
|
+
background: 'linear-gradient(45deg, #667eea, #764ba2)',
|
|
223
|
+
padding: '1rem',
|
|
224
|
+
borderRadius: '8px',
|
|
225
|
+
color: 'white'
|
|
226
|
+
}}
|
|
227
|
+
/>
|
|
228
|
+
</div>
|
|
229
|
+
<p>You can apply custom CSS classes and inline styles to SafeSpan.</p>
|
|
230
|
+
</Content>
|
|
231
|
+
</GridCell>
|
|
232
232
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
233
|
+
<GridCell>
|
|
234
|
+
<Content variant="outlined" spacing='comfortable'>
|
|
235
|
+
<h3>Themed Content</h3>
|
|
236
|
+
<div style={{
|
|
237
|
+
background: 'var(--theme-primary)',
|
|
238
|
+
color: 'var(--theme-on-primary)',
|
|
239
|
+
padding: '1rem',
|
|
240
|
+
borderRadius: '8px',
|
|
241
|
+
marginBottom: '1rem'
|
|
242
|
+
}}>
|
|
243
|
+
<SafeSpan
|
|
244
|
+
html="<p>This content uses <em>theme variables</em> for consistent styling.</p>"
|
|
245
|
+
style={{ color: 'inherit' }}
|
|
246
|
+
/>
|
|
247
|
+
</div>
|
|
248
|
+
<p>SafeSpan works seamlessly with the QwickApps theme system.</p>
|
|
249
|
+
</Content>
|
|
250
|
+
</GridCell>
|
|
251
|
+
</GridLayout>
|
|
252
|
+
</Content>
|
|
253
|
+
</Section>
|
|
254
|
+
),
|
|
255
255
|
};
|
|
256
256
|
|
|
257
257
|
export const EmptyStates: Story = {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
258
|
+
render: () => (
|
|
259
|
+
<Section padding="large">
|
|
260
|
+
<Content title="Empty States and Fallbacks" centered maxWidth="large">
|
|
261
|
+
<GridLayout columns={3} spacing="large">
|
|
262
|
+
<GridCell>
|
|
263
|
+
<Content variant="outlined" spacing='comfortable'>
|
|
264
|
+
<h4>No Content, No Placeholder</h4>
|
|
265
|
+
<div style={{
|
|
266
|
+
minHeight: '60px',
|
|
267
|
+
background: 'var(--theme-surface-variant)',
|
|
268
|
+
padding: '1rem',
|
|
269
|
+
borderRadius: '8px',
|
|
270
|
+
marginBottom: '1rem',
|
|
271
|
+
border: '1px dashed var(--theme-outline)'
|
|
272
|
+
}}>
|
|
273
|
+
<SafeSpan html="" />
|
|
274
|
+
<em style={{ opacity: 0.6 }}>(Component returns null)</em>
|
|
275
|
+
</div>
|
|
276
|
+
</Content>
|
|
277
|
+
</GridCell>
|
|
278
278
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
279
|
+
<GridCell>
|
|
280
|
+
<Content variant="outlined" spacing='comfortable'>
|
|
281
|
+
<h4>Empty with Placeholder</h4>
|
|
282
|
+
<div style={{
|
|
283
|
+
background: 'var(--theme-surface-variant)',
|
|
284
|
+
padding: '1rem',
|
|
285
|
+
borderRadius: '8px',
|
|
286
|
+
marginBottom: '1rem'
|
|
287
|
+
}}>
|
|
288
|
+
<SafeSpan html="" placeholder="No content to display" />
|
|
289
|
+
</div>
|
|
290
|
+
</Content>
|
|
291
|
+
</GridCell>
|
|
292
292
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
293
|
+
<GridCell>
|
|
294
|
+
<Content variant="outlined" spacing='comfortable'>
|
|
295
|
+
<h4>Undefined with Placeholder</h4>
|
|
296
|
+
<div style={{
|
|
297
|
+
background: 'var(--theme-surface-variant)',
|
|
298
|
+
padding: '1rem',
|
|
299
|
+
borderRadius: '8px',
|
|
300
|
+
marginBottom: '1rem'
|
|
301
|
+
}}>
|
|
302
|
+
<SafeSpan placeholder="Loading content..." />
|
|
303
|
+
</div>
|
|
304
|
+
</Content>
|
|
305
|
+
</GridCell>
|
|
306
|
+
</GridLayout>
|
|
307
|
+
</Content>
|
|
308
|
+
</Section>
|
|
309
|
+
),
|
|
310
310
|
};
|
|
311
311
|
|
|
312
312
|
export const DataBindingExample: Story = {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
313
|
+
render: () => (
|
|
314
|
+
<Section padding="large">
|
|
315
|
+
<Content title="Data Binding with SafeSpan" centered maxWidth="large">
|
|
316
|
+
<GridLayout columns={2} spacing="large">
|
|
317
|
+
<GridCell>
|
|
318
|
+
<Content variant="outlined" spacing='comfortable'>
|
|
319
|
+
<h3>Traditional Props Usage</h3>
|
|
320
|
+
<div style={{
|
|
321
|
+
background: 'var(--theme-surface-variant)',
|
|
322
|
+
padding: '1rem',
|
|
323
|
+
borderRadius: '8px',
|
|
324
|
+
marginBottom: '1rem'
|
|
325
|
+
}}>
|
|
326
|
+
<SafeSpan
|
|
327
|
+
html="<p>This content is passed as <strong>traditional props</strong>.</p>"
|
|
328
|
+
placeholder="Loading traditional content..."
|
|
329
|
+
/>
|
|
330
|
+
</div>
|
|
331
|
+
<p><strong>Usage:</strong> Direct props without dataSource</p>
|
|
332
|
+
</Content>
|
|
333
|
+
</GridCell>
|
|
334
334
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
335
|
+
<GridCell>
|
|
336
|
+
<Content variant="outlined" spacing='comfortable'>
|
|
337
|
+
<h3>Data Binding Usage</h3>
|
|
338
|
+
<div style={{
|
|
339
|
+
background: 'var(--theme-surface-variant)',
|
|
340
|
+
padding: '1rem',
|
|
341
|
+
borderRadius: '8px',
|
|
342
|
+
marginBottom: '1rem'
|
|
343
|
+
}}>
|
|
344
|
+
<SafeSpan
|
|
345
|
+
dataSource="company.description"
|
|
346
|
+
bindingOptions={{
|
|
347
|
+
fallback: {
|
|
348
|
+
html: "<p>Fallback content when data binding fails</p>",
|
|
349
|
+
placeholder: "Loading from data source..."
|
|
350
|
+
}
|
|
351
|
+
}}
|
|
352
|
+
/>
|
|
353
|
+
</div>
|
|
354
|
+
<p><strong>Usage:</strong> Data-driven with dataSource prop</p>
|
|
355
|
+
</Content>
|
|
356
|
+
</GridCell>
|
|
357
|
+
</GridLayout>
|
|
358
358
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
359
|
+
<Content spacing='comfortable'>
|
|
360
|
+
<div style={{
|
|
361
|
+
background: 'var(--theme-info, #0ea5e9)',
|
|
362
|
+
color: 'white',
|
|
363
|
+
padding: '1rem',
|
|
364
|
+
borderRadius: '8px',
|
|
365
|
+
textAlign: 'center'
|
|
366
|
+
}}>
|
|
367
|
+
<strong>💡 Data Structure Expected:</strong>
|
|
368
|
+
<pre style={{
|
|
369
|
+
background: 'rgba(0,0,0,0.2)',
|
|
370
|
+
padding: '1rem',
|
|
371
|
+
borderRadius: '6px',
|
|
372
|
+
marginTop: '1rem',
|
|
373
|
+
textAlign: 'left',
|
|
374
|
+
overflow: 'auto'
|
|
375
|
+
}}>
|
|
376
376
|
{`{
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
377
|
+
"safeSpans": {
|
|
378
|
+
"richText": {
|
|
379
|
+
"html": "<p>Rich HTML <strong>content</strong> here</p>",
|
|
380
|
+
"placeholder": "Loading rich content..."
|
|
381
|
+
},
|
|
382
|
+
"basicText": {
|
|
383
|
+
"html": "<span>Basic HTML content</span>",
|
|
384
|
+
"placeholder": "Loading..."
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
387
|
}`}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
388
|
+
</pre>
|
|
389
|
+
</div>
|
|
390
|
+
</Content>
|
|
391
|
+
</Content>
|
|
392
|
+
</Section>
|
|
393
|
+
),
|
|
394
394
|
};
|
|
395
395
|
|
|
396
396
|
export const SecurityTestingExample: Story = {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
397
|
+
render: () => (
|
|
398
|
+
<Section padding="large">
|
|
399
|
+
<Content title="Advanced Security Testing" centered maxWidth="large">
|
|
400
|
+
<GridLayout columns={1} spacing="large">
|
|
401
|
+
<GridCell>
|
|
402
|
+
<Content variant="elevated" spacing='comfortable'>
|
|
403
|
+
<h3>XSS Attack Vector Testing</h3>
|
|
404
|
+
|
|
405
|
+
{/* Test various XSS attack patterns */}
|
|
406
|
+
<div style={{ display: 'grid', gridTemplateColumns: '1fr', gap: '1rem' }}>
|
|
407
|
+
|
|
408
|
+
<div style={{
|
|
409
|
+
background: 'var(--theme-surface-variant)',
|
|
410
|
+
padding: '1rem',
|
|
411
|
+
borderRadius: '8px',
|
|
412
|
+
border: '2px solid var(--theme-warning, #f59e0b)'
|
|
413
|
+
}}>
|
|
414
|
+
<h4>Script Injection Test</h4>
|
|
415
|
+
<SafeSpan
|
|
416
|
+
html={`<p>Normal content followed by: <script>alert('XSS')</script></p>`}
|
|
417
|
+
placeholder="Testing script injection..."
|
|
418
|
+
/>
|
|
419
|
+
<p><small>✅ Script tags should be completely removed</small></p>
|
|
420
|
+
</div>
|
|
421
421
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
422
|
+
<div style={{
|
|
423
|
+
background: 'var(--theme-surface-variant)',
|
|
424
|
+
padding: '1rem',
|
|
425
|
+
borderRadius: '8px',
|
|
426
|
+
border: '2px solid var(--theme-warning, #f59e0b)'
|
|
427
|
+
}}>
|
|
428
|
+
<h4>Event Handler Test</h4>
|
|
429
|
+
<SafeSpan
|
|
430
|
+
html={`<p onclick="alert('Click XSS')" onmouseover="alert('Hover XSS')">Hover or click this text</p>`}
|
|
431
|
+
placeholder="Testing event handlers..."
|
|
432
|
+
/>
|
|
433
|
+
<p><small>✅ Event handlers should be stripped</small></p>
|
|
434
|
+
</div>
|
|
435
435
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
436
|
+
<div style={{
|
|
437
|
+
background: 'var(--theme-surface-variant)',
|
|
438
|
+
padding: '1rem',
|
|
439
|
+
borderRadius: '8px',
|
|
440
|
+
border: '2px solid var(--theme-warning, #f59e0b)'
|
|
441
|
+
}}>
|
|
442
|
+
<h4>JavaScript URL Test</h4>
|
|
443
|
+
<SafeSpan
|
|
444
|
+
html={`<a href="javascript:alert('Link XSS')">Malicious Link</a>`}
|
|
445
|
+
placeholder="Testing javascript URLs..."
|
|
446
|
+
/>
|
|
447
|
+
<p><small>✅ javascript: URLs should be blocked</small></p>
|
|
448
|
+
</div>
|
|
449
449
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
450
|
+
<div style={{
|
|
451
|
+
background: 'var(--theme-surface-variant)',
|
|
452
|
+
padding: '1rem',
|
|
453
|
+
borderRadius: '8px',
|
|
454
|
+
border: '2px solid var(--theme-warning, #f59e0b)'
|
|
455
|
+
}}>
|
|
456
|
+
<h4>Data URL Test</h4>
|
|
457
|
+
<SafeSpan
|
|
458
|
+
html={`<img src="data:text/html,<script>alert('Data URL XSS')</script>" alt="test">`}
|
|
459
|
+
placeholder="Testing data URLs..."
|
|
460
|
+
/>
|
|
461
|
+
<p><small>✅ Malicious data URLs should be blocked</small></p>
|
|
462
|
+
</div>
|
|
463
463
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
464
|
+
<div style={{
|
|
465
|
+
background: 'var(--theme-success, #10b981)',
|
|
466
|
+
color: 'white',
|
|
467
|
+
padding: '1rem',
|
|
468
|
+
borderRadius: '8px',
|
|
469
|
+
}}>
|
|
470
|
+
<h4>✅ Safe Content (Should Render)</h4>
|
|
471
|
+
<SafeSpan
|
|
472
|
+
html={`<p>This is <strong>safe content</strong> with <a href="https://example.com" target="_blank" rel="noopener noreferrer">external link</a></p>`}
|
|
473
|
+
placeholder="Loading safe content..."
|
|
474
|
+
/>
|
|
475
|
+
<p><small>✅ Safe HTML should render with security attributes added</small></p>
|
|
476
|
+
</div>
|
|
477
477
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
478
|
+
</div>
|
|
479
|
+
</Content>
|
|
480
|
+
</GridCell>
|
|
481
|
+
</GridLayout>
|
|
482
|
+
</Content>
|
|
483
|
+
</Section>
|
|
484
|
+
),
|
|
485
485
|
};
|
|
486
486
|
|
|
487
487
|
export const InteractiveExample: Story = {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
488
|
+
render: () => {
|
|
489
|
+
const [htmlContent, setHtmlContent] = useState(safeHtml);
|
|
490
|
+
const [placeholder, setPlaceholder] = useState('Enter some HTML content...');
|
|
491
491
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
492
|
+
return (
|
|
493
|
+
<Section padding="large">
|
|
494
|
+
<Content title="Interactive SafeSpan Demo" centered maxWidth="large">
|
|
495
|
+
<GridLayout columns={2} spacing="large">
|
|
496
|
+
<GridCell>
|
|
497
|
+
<Content variant="outlined" spacing='comfortable'>
|
|
498
|
+
<h3>HTML Input</h3>
|
|
499
|
+
<textarea
|
|
500
|
+
value={htmlContent}
|
|
501
|
+
onChange={(e) => setHtmlContent(e.target.value)}
|
|
502
|
+
placeholder="Enter HTML content to test..."
|
|
503
|
+
style={{
|
|
504
|
+
width: '100%',
|
|
505
|
+
minHeight: '150px',
|
|
506
|
+
padding: '1rem',
|
|
507
|
+
border: '1px solid var(--theme-outline)',
|
|
508
|
+
borderRadius: '8px',
|
|
509
|
+
background: 'var(--theme-surface)',
|
|
510
|
+
color: 'var(--theme-on-surface)',
|
|
511
|
+
fontFamily: 'monospace',
|
|
512
|
+
fontSize: '0.9rem',
|
|
513
|
+
marginBottom: '1rem'
|
|
514
|
+
}}
|
|
515
|
+
/>
|
|
516
|
+
<input
|
|
517
|
+
type="text"
|
|
518
|
+
value={placeholder}
|
|
519
|
+
onChange={(e) => setPlaceholder(e.target.value)}
|
|
520
|
+
placeholder="Placeholder text..."
|
|
521
|
+
style={{
|
|
522
|
+
width: '100%',
|
|
523
|
+
padding: '0.75rem',
|
|
524
|
+
border: '1px solid var(--theme-outline)',
|
|
525
|
+
borderRadius: '8px',
|
|
526
|
+
background: 'var(--theme-surface)',
|
|
527
|
+
color: 'var(--theme-on-surface)'
|
|
528
|
+
}}
|
|
529
|
+
/>
|
|
530
|
+
</Content>
|
|
531
|
+
</GridCell>
|
|
532
532
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
533
|
+
<GridCell>
|
|
534
|
+
<Content variant="elevated" spacing='comfortable'>
|
|
535
|
+
<h3>SafeSpan Output</h3>
|
|
536
|
+
<div style={{
|
|
537
|
+
background: 'var(--theme-surface-variant)',
|
|
538
|
+
padding: '1rem',
|
|
539
|
+
borderRadius: '8px',
|
|
540
|
+
minHeight: '200px',
|
|
541
|
+
border: '1px solid var(--theme-outline)'
|
|
542
|
+
}}>
|
|
543
|
+
<SafeSpan html={htmlContent} placeholder={placeholder} />
|
|
544
|
+
</div>
|
|
545
|
+
<p style={{ marginTop: '1rem', fontSize: '0.9rem', opacity: 0.8 }}>
|
|
546
|
+
<strong>Try entering:</strong> HTML with scripts, event handlers, or other potentially dangerous content to see how it's automatically sanitized.
|
|
547
|
+
</p>
|
|
548
|
+
</Content>
|
|
549
|
+
</GridCell>
|
|
550
|
+
</GridLayout>
|
|
551
551
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
552
|
+
<Content spacing='comfortable'>
|
|
553
|
+
<div style={{ display: 'flex', gap: '1rem', justifyContent: 'center', flexWrap: 'wrap' }}>
|
|
554
|
+
<button
|
|
555
|
+
onClick={() => setHtmlContent(safeHtml)}
|
|
556
|
+
style={{
|
|
557
|
+
padding: '0.75rem 1.5rem',
|
|
558
|
+
background: 'var(--theme-primary)',
|
|
559
|
+
color: 'var(--theme-on-primary)',
|
|
560
|
+
border: 'none',
|
|
561
|
+
borderRadius: '6px',
|
|
562
|
+
cursor: 'pointer'
|
|
563
|
+
}}
|
|
564
|
+
>
|
|
565
|
+
Load Safe HTML
|
|
566
|
+
</button>
|
|
567
|
+
<button
|
|
568
|
+
onClick={() => setHtmlContent(unsafeHtml)}
|
|
569
|
+
style={{
|
|
570
|
+
padding: '0.75rem 1.5rem',
|
|
571
|
+
background: 'var(--theme-error, #dc2626)',
|
|
572
|
+
color: 'white',
|
|
573
|
+
border: 'none',
|
|
574
|
+
borderRadius: '6px',
|
|
575
|
+
cursor: 'pointer'
|
|
576
|
+
}}
|
|
577
|
+
>
|
|
578
|
+
Load Unsafe HTML
|
|
579
|
+
</button>
|
|
580
|
+
<button
|
|
581
|
+
onClick={() => setHtmlContent(richContentHtml)}
|
|
582
|
+
style={{
|
|
583
|
+
padding: '0.75rem 1.5rem',
|
|
584
|
+
background: 'transparent',
|
|
585
|
+
color: 'var(--theme-primary)',
|
|
586
|
+
border: '1px solid var(--theme-primary)',
|
|
587
|
+
borderRadius: '6px',
|
|
588
|
+
cursor: 'pointer'
|
|
589
|
+
}}
|
|
590
|
+
>
|
|
591
|
+
Load Rich Content
|
|
592
|
+
</button>
|
|
593
|
+
<button
|
|
594
|
+
onClick={() => setHtmlContent('')}
|
|
595
|
+
style={{
|
|
596
|
+
padding: '0.75rem 1.5rem',
|
|
597
|
+
background: 'transparent',
|
|
598
|
+
color: 'var(--theme-on-surface)',
|
|
599
|
+
border: '1px solid var(--theme-outline)',
|
|
600
|
+
borderRadius: '6px',
|
|
601
|
+
cursor: 'pointer'
|
|
602
|
+
}}
|
|
603
|
+
>
|
|
604
|
+
Clear Content
|
|
605
|
+
</button>
|
|
606
|
+
</div>
|
|
607
|
+
</Content>
|
|
608
|
+
</Content>
|
|
609
|
+
</Section>
|
|
610
|
+
);
|
|
611
|
+
},
|
|
612
612
|
};
|