@qwickapps/react-framework 1.3.4 → 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 +1688 -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/Content.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/ProductCard.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/ThemeContext.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 +20722 -16021
- package/dist/index.js +20725 -16010
- 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/Content.tsx +25 -77
- 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 +51 -52
- 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/ThemeContext.tsx +1 -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
|
@@ -1,73 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Page
|
|
3
|
-
*
|
|
4
|
-
* The Page component is what developers primarily focus on when building applications.
|
|
5
|
-
* It provides:
|
|
6
|
-
* - Automatic page title management
|
|
7
|
-
* - Route-based configuration
|
|
8
|
-
* - Contextual menu items that appear in navigation
|
|
9
|
-
* - Proper content spacing and layout
|
|
10
|
-
* - SEO and accessibility features
|
|
11
|
-
*
|
|
12
|
-
* Page components automatically integrate with the AppScaffold to provide
|
|
13
|
-
* contextual actions and menu items specific to the current page.
|
|
2
|
+
* Page Architecture - Clean separation with functional controller
|
|
14
3
|
*
|
|
15
4
|
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
16
5
|
*/
|
|
17
6
|
|
|
18
|
-
import React, { useContext,
|
|
19
|
-
import '
|
|
7
|
+
import React, { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useState } from 'react';
|
|
8
|
+
import { t } from '../../contexts';
|
|
9
|
+
import { useBaseProps, WithBaseProps } from '../../hooks/useBaseProps';
|
|
10
|
+
import { usePrintMode } from '../../hooks/usePrintMode';
|
|
11
|
+
import { PageTemplateSchema } from '../../schemas/PageTemplateSchema';
|
|
12
|
+
import { PrintConfigSchema } from '../../schemas/PrintConfigSchema';
|
|
20
13
|
import { useSafeLocation } from '../../utils/reactUtils';
|
|
14
|
+
import { SafeSpan } from '../SafeSpan';
|
|
15
|
+
import './Page.css';
|
|
21
16
|
|
|
22
17
|
export interface PageContextValue {
|
|
23
18
|
/** Current page route/path */
|
|
24
19
|
route?: string;
|
|
20
|
+
/** Whether the page is currently in print mode */
|
|
21
|
+
isPrintMode: boolean;
|
|
22
|
+
/** Current print configuration */
|
|
23
|
+
printConfig: PrintConfigSchema;
|
|
24
|
+
/** Trigger print mode and dialog */
|
|
25
|
+
triggerPrint: (config?: Partial<PrintConfigSchema>) => void;
|
|
26
|
+
/** Set page loading state */
|
|
27
|
+
setLoading: (loading: boolean) => void;
|
|
28
|
+
/** Set page error state */
|
|
29
|
+
setError: (error: string | null) => void;
|
|
30
|
+
/** Current page loading state */
|
|
31
|
+
isLoading: boolean;
|
|
32
|
+
/** Current page error state */
|
|
33
|
+
error: string | null;
|
|
25
34
|
}
|
|
26
35
|
|
|
27
36
|
// Create context for page-level state
|
|
28
|
-
const PageContext = createContext<PageContextValue>(
|
|
37
|
+
const PageContext = createContext<PageContextValue | null>(null);
|
|
29
38
|
|
|
30
39
|
export const usePageContext = (): PageContextValue => {
|
|
31
40
|
const context = useContext(PageContext);
|
|
32
41
|
if (!context) {
|
|
33
|
-
throw new Error('usePageContext must be used within a
|
|
42
|
+
throw new Error('usePageContext must be used within a Page component');
|
|
34
43
|
}
|
|
35
44
|
return context;
|
|
36
45
|
};
|
|
37
46
|
|
|
38
|
-
export
|
|
39
|
-
|
|
40
|
-
export type LoadingType = 'spinner' | 'bar-top' | 'bar-bottom' | 'dots' | 'custom';
|
|
41
|
-
|
|
42
|
-
export interface MessageState {
|
|
43
|
-
/** Type of message to display */
|
|
44
|
-
type: MessageType;
|
|
45
|
-
/** Message content (string or custom JSX) */
|
|
46
|
-
content?: string | React.ReactNode;
|
|
47
|
-
/** Custom component to render instead of default */
|
|
48
|
-
customComponent?: React.ReactNode;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export interface LoadingState {
|
|
52
|
-
/** Type of loading indicator */
|
|
53
|
-
type?: LoadingType;
|
|
54
|
-
/** Loading message */
|
|
55
|
-
message?: string;
|
|
56
|
-
/** Custom loading component */
|
|
57
|
-
customComponent?: React.ReactNode;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export interface PageProps {
|
|
61
|
-
/** Page content */
|
|
62
|
-
children: React.ReactNode;
|
|
47
|
+
export interface PageProps extends WithBaseProps {
|
|
63
48
|
/** Current route/path for this page */
|
|
64
49
|
route?: string;
|
|
65
|
-
/** Optional header component */
|
|
66
|
-
header?: React.ReactNode;
|
|
67
|
-
/** Optional footer component */
|
|
68
|
-
footer?: React.ReactNode;
|
|
69
|
-
/** Additional CSS class */
|
|
70
|
-
className?: string;
|
|
71
50
|
/** Page layout variant */
|
|
72
51
|
variant?: 'default' | 'centered' | 'narrow' | 'wide' | 'fullwidth';
|
|
73
52
|
/** Padding around page content */
|
|
@@ -76,182 +55,274 @@ export interface PageProps {
|
|
|
76
55
|
background?: 'default' | 'surface' | 'alternate';
|
|
77
56
|
/** Maximum width for content */
|
|
78
57
|
maxWidth?: 'small' | 'medium' | 'large' | 'extra-large' | 'none';
|
|
79
|
-
/**
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
|
|
58
|
+
/** Print configuration for this page */
|
|
59
|
+
printConfig?: Partial<PrintConfigSchema>;
|
|
60
|
+
/** PageTemplate model instance */
|
|
61
|
+
template?: PageTemplateSchema;
|
|
62
|
+
/** Page title */
|
|
63
|
+
title?: string;
|
|
64
|
+
/** Page description for SEO */
|
|
65
|
+
description?: string;
|
|
66
|
+
/** Page name/heading */
|
|
67
|
+
name?: string;
|
|
68
|
+
/** Page slug for URL */
|
|
69
|
+
slug?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* PageWrapper - Internal component handling print mode detection and page infrastructure
|
|
74
|
+
*/
|
|
75
|
+
interface PageWrapperProps extends PageProps {
|
|
76
|
+
children: React.ReactNode;
|
|
83
77
|
}
|
|
84
78
|
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
79
|
+
const PageWrapper: React.FC<PageWrapperProps> = (props) => {
|
|
80
|
+
const {
|
|
81
|
+
children,
|
|
82
|
+
route: routeProp,
|
|
83
|
+
variant = 'default',
|
|
84
|
+
padding = 'medium',
|
|
85
|
+
background = 'default',
|
|
86
|
+
maxWidth = 'large',
|
|
87
|
+
printConfig: printConfigProp,
|
|
88
|
+
template,
|
|
89
|
+
title: titleProp,
|
|
90
|
+
description: descriptionProp,
|
|
91
|
+
name: nameProp,
|
|
92
|
+
slug: slugProp,
|
|
93
|
+
...restProps
|
|
94
|
+
} = props;
|
|
95
|
+
|
|
96
|
+
// Use base props hook to handle common properties
|
|
97
|
+
const { styleProps, htmlProps } = useBaseProps(restProps);
|
|
98
|
+
|
|
99
|
+
// Note: QwickApp context is available if needed for future enhancements
|
|
100
|
+
|
|
101
|
+
// Resolve values from template or props (props take precedence)
|
|
102
|
+
const resolvedValues = {
|
|
103
|
+
route: routeProp ?? (template?.slug ? `/${template.slug}` : undefined),
|
|
104
|
+
title: titleProp ?? template?.title ?? template?.name,
|
|
105
|
+
description: descriptionProp ?? template?.description,
|
|
106
|
+
name: nameProp ?? template?.name,
|
|
107
|
+
slug: slugProp ?? template?.slug,
|
|
108
|
+
printConfig: { ...template?.printConfig, ...printConfigProp },
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// Page state management
|
|
112
|
+
const [isLoading, setLoading] = useState(false);
|
|
113
|
+
const [error, setError] = useState<string | null>(null);
|
|
114
|
+
|
|
115
|
+
// Initialize print mode with page configuration
|
|
116
|
+
const { isPrintMode, printConfig, onViewLoading, onViewReady, triggerPrint: triggerPrintBase } = usePrintMode();
|
|
117
|
+
// Create page-specific trigger that applies page config
|
|
118
|
+
const triggerPrint = useCallback((config?: Partial<PrintConfigSchema>) => {
|
|
119
|
+
const mergedConfig = { ...resolvedValues.printConfig, ...config };
|
|
120
|
+
triggerPrintBase(mergedConfig);
|
|
121
|
+
}, [triggerPrintBase, resolvedValues.printConfig]);
|
|
122
|
+
|
|
98
123
|
// Auto-detect current route if available
|
|
99
124
|
const location = useSafeLocation();
|
|
100
|
-
const actualRoute = route || location?.pathname || '';
|
|
101
|
-
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
//
|
|
120
|
-
|
|
125
|
+
const actualRoute = resolvedValues.route || location?.pathname || '';
|
|
126
|
+
|
|
127
|
+
// Handle SEO and document head updates
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
if (resolvedValues.title) {
|
|
130
|
+
document.title = resolvedValues.title;
|
|
131
|
+
}
|
|
132
|
+
// Update meta description
|
|
133
|
+
if (resolvedValues.description) {
|
|
134
|
+
let metaDescription = document.querySelector('meta[name="description"]');
|
|
135
|
+
if (!metaDescription) {
|
|
136
|
+
metaDescription = document.createElement('meta');
|
|
137
|
+
metaDescription.setAttribute('name', 'description');
|
|
138
|
+
document.head.appendChild(metaDescription);
|
|
139
|
+
}
|
|
140
|
+
metaDescription.setAttribute('content', resolvedValues.description);
|
|
141
|
+
}
|
|
142
|
+
}, [resolvedValues.title, resolvedValues.description]);
|
|
143
|
+
|
|
144
|
+
// Signal view state changes - let reducer handle valid transitions
|
|
145
|
+
useLayoutEffect(() => {
|
|
146
|
+
if (isPrintMode && isLoading) {
|
|
147
|
+
onViewLoading();
|
|
148
|
+
} else if (isPrintMode && !isLoading) {
|
|
149
|
+
onViewReady();
|
|
150
|
+
}
|
|
151
|
+
}, [isPrintMode, isLoading, onViewLoading, onViewReady]);
|
|
121
152
|
|
|
122
153
|
// Memoized context value
|
|
123
|
-
const contextValue =
|
|
154
|
+
const contextValue = useMemo<PageContextValue>(() => ({
|
|
124
155
|
route: actualRoute,
|
|
125
|
-
|
|
156
|
+
isPrintMode,
|
|
157
|
+
printConfig,
|
|
158
|
+
triggerPrint,
|
|
159
|
+
setLoading,
|
|
160
|
+
setError,
|
|
161
|
+
isLoading,
|
|
162
|
+
error,
|
|
163
|
+
}), [actualRoute, isPrintMode, printConfig, triggerPrint, isLoading, error]);
|
|
164
|
+
|
|
165
|
+
// Inject dynamic @page margins during print to reserve header/footer space on every page
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
const styleId = 'qwickapps-print-page-setup';
|
|
168
|
+
|
|
169
|
+
const applyPrintPageSetup = () => {
|
|
170
|
+
const configuredHeader = printConfig.printHeaderHeight || '0';
|
|
171
|
+
const configuredFooter = printConfig.printFooterHeight || '0';
|
|
172
|
+
const size = (printConfig as any).pageSize || 'auto';
|
|
173
|
+
const bg = printConfig.printBackground || 'transparent';
|
|
174
|
+
const bgFirst = printConfig.printBackgroundFirstPage || bg;
|
|
175
|
+
|
|
176
|
+
// Measure actual rendered heights (fallback to configured)
|
|
177
|
+
const getPx = (v: string | number) => typeof v === 'number' ? `${v}px` : (String(v).match(/(px|mm|cm|in|pt|pc)$/) ? String(v) : `${v}px`);
|
|
178
|
+
const headerEl = document.querySelector('.page-print-header:not(.page-print-header-first-page)') as HTMLElement | null;
|
|
179
|
+
const footerEl = document.querySelector('.page-print-footer:not(.page-print-footer-first-page)') as HTMLElement | null;
|
|
180
|
+
const measuredHeaderPx = headerEl && headerEl.getBoundingClientRect ? Math.ceil(headerEl.getBoundingClientRect().height) : 0;
|
|
181
|
+
const measuredFooterPx = footerEl && footerEl.getBoundingClientRect ? Math.ceil(footerEl.getBoundingClientRect().height) : 0;
|
|
182
|
+
const headerH = measuredHeaderPx > 0 ? `${measuredHeaderPx}px` : getPx(configuredHeader);
|
|
183
|
+
const footerH = measuredFooterPx > 0 ? `${measuredFooterPx}px` : getPx(configuredFooter);
|
|
184
|
+
|
|
185
|
+
const css = `@media print{\n @page{\n size: ${String(size)};\n margin: 0;\n }\n .page-print-mode{\n --print-header-height: ${String(headerH)};\n --print-footer-height: ${String(footerH)};\n --print-background: ${String(bg)};\n --print-background-first-page: ${String(bgFirst)};\n}\n}`;
|
|
186
|
+
|
|
187
|
+
let el = document.getElementById(styleId) as HTMLStyleElement | null;
|
|
188
|
+
if (!el) {
|
|
189
|
+
el = document.createElement('style');
|
|
190
|
+
el.id = styleId;
|
|
191
|
+
document.head.appendChild(el);
|
|
192
|
+
}
|
|
193
|
+
el.textContent = css;
|
|
194
|
+
|
|
195
|
+
// Re-measure after layout settles once and update
|
|
196
|
+
setTimeout(() => {
|
|
197
|
+
const reHeaderEl = document.querySelector('.page-print-header:not(.page-print-header-first-page)') as HTMLElement | null;
|
|
198
|
+
const reFooterEl = document.querySelector('.page-print-footer:not(.page-print-footer-first-page)') as HTMLElement | null;
|
|
199
|
+
const reHeaderPx = reHeaderEl?.getBoundingClientRect ? Math.ceil(reHeaderEl.getBoundingClientRect().height) : 0;
|
|
200
|
+
const reFooterPx = reFooterEl?.getBoundingClientRect ? Math.ceil(reFooterEl.getBoundingClientRect().height) : 0;
|
|
201
|
+
if (reHeaderPx || reFooterPx) {
|
|
202
|
+
const finalHeader = reHeaderPx ? `${reHeaderPx}px` : headerH;
|
|
203
|
+
const finalFooter = reFooterPx ? `${reFooterPx}px` : footerH;
|
|
204
|
+
const finalCss = `@media print{\n @page{\n size: ${String(size)};\n margin: 0;\n }\n .page-print-mode{\n --print-header-height: ${String(finalHeader)};\n --print-footer-height: ${String(finalFooter)};\n --print-background: ${String(bg)};\n --print-background-first-page: ${String(bgFirst)};\n }\n}`;
|
|
205
|
+
let styleEl = document.getElementById(styleId) as HTMLStyleElement | null;
|
|
206
|
+
if (!styleEl) {
|
|
207
|
+
styleEl = document.createElement('style');
|
|
208
|
+
styleEl.id = styleId;
|
|
209
|
+
document.head.appendChild(styleEl);
|
|
210
|
+
}
|
|
211
|
+
styleEl.textContent = finalCss;
|
|
212
|
+
}
|
|
213
|
+
}, 60);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const removePrintPageSetup = () => {
|
|
217
|
+
const el = document.getElementById(styleId);
|
|
218
|
+
if (el && el.parentNode) el.parentNode.removeChild(el);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
if (isPrintMode) {
|
|
222
|
+
applyPrintPageSetup();
|
|
223
|
+
// Cleanup after the print dialog closes
|
|
224
|
+
const onAfterPrint = () => removePrintPageSetup();
|
|
225
|
+
window.addEventListener('afterprint', onAfterPrint);
|
|
226
|
+
return () => {
|
|
227
|
+
window.removeEventListener('afterprint', onAfterPrint);
|
|
228
|
+
removePrintPageSetup();
|
|
229
|
+
};
|
|
230
|
+
}
|
|
126
231
|
|
|
127
|
-
|
|
232
|
+
// If leaving print mode, ensure cleanup
|
|
233
|
+
removePrintPageSetup();
|
|
234
|
+
}, [isPrintMode, printConfig.printHeaderHeight, printConfig.printFooterHeight, (printConfig as any).pageSize, printConfig.printBackground, printConfig.printBackgroundFirstPage]);
|
|
235
|
+
|
|
236
|
+
// Calculate CSS variables for print configuration
|
|
237
|
+
|
|
238
|
+
// Generate class names based on configuration
|
|
128
239
|
const pageClasses = [
|
|
129
240
|
'page',
|
|
130
241
|
`page-variant-${variant}`,
|
|
131
242
|
`page-padding-${padding}`,
|
|
132
243
|
`page-background-${background}`,
|
|
133
244
|
`page-max-width-${maxWidth}`,
|
|
134
|
-
|
|
245
|
+
// Print mode classes
|
|
246
|
+
isPrintMode ? 'page-print-mode' : '',
|
|
247
|
+
// Print margin variants
|
|
248
|
+
isPrintMode && printConfig.pageMargins === '0mm' ? 'page-print-borderless' : '',
|
|
249
|
+
isPrintMode && printConfig.pageMargins === '6mm' ? 'page-print-compact' : '',
|
|
250
|
+
isPrintMode && printConfig.pageMargins === '20mm' ? 'page-print-large' : '',
|
|
251
|
+
isPrintMode && printConfig.pageMargins === '25mm' ? 'page-print-formal' : '',
|
|
252
|
+
// Background class when custom background is specified
|
|
253
|
+
isPrintMode && (printConfig.printBackground || printConfig.printBackgroundFirstPage) ? 'has-background' : '',
|
|
254
|
+
// User classes
|
|
255
|
+
styleProps.className,
|
|
135
256
|
].filter(Boolean).join(' ');
|
|
136
257
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
</div>
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Handle custom loading state
|
|
153
|
-
const { type = 'spinner', message, customComponent } = loadingState;
|
|
154
|
-
|
|
155
|
-
if (customComponent) {
|
|
156
|
-
return <div className="page-loading">{customComponent}</div>;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
switch (type) {
|
|
160
|
-
case 'bar-top':
|
|
161
|
-
return (
|
|
162
|
-
<div className="page-loading-bar page-loading-bar-top">
|
|
163
|
-
<div className="loading-bar-progress"></div>
|
|
164
|
-
{message && <div className="loading-message">{message}</div>}
|
|
165
|
-
</div>
|
|
166
|
-
);
|
|
167
|
-
case 'bar-bottom':
|
|
168
|
-
return (
|
|
169
|
-
<div className="page-loading-bar page-loading-bar-bottom">
|
|
170
|
-
<div className="loading-bar-progress"></div>
|
|
171
|
-
{message && <div className="loading-message">{message}</div>}
|
|
172
|
-
</div>
|
|
173
|
-
);
|
|
174
|
-
case 'dots':
|
|
175
|
-
return (
|
|
176
|
-
<div className="page-loading page-loading-dots">
|
|
177
|
-
<div className="loading-dots" aria-label="Loading content">
|
|
178
|
-
<span></span><span></span><span></span>
|
|
179
|
-
</div>
|
|
180
|
-
{message && <div className="loading-message">{message}</div>}
|
|
181
|
-
</div>
|
|
182
|
-
);
|
|
183
|
-
case 'spinner':
|
|
184
|
-
default:
|
|
185
|
-
return (
|
|
186
|
-
<div className="page-loading">
|
|
187
|
-
<div className="loading-spinner" aria-label="Loading content">
|
|
188
|
-
<div className="spinner"></div>
|
|
189
|
-
</div>
|
|
190
|
-
{message && <div className="loading-message">{message}</div>}
|
|
258
|
+
return (
|
|
259
|
+
<PageContext.Provider value={contextValue}>
|
|
260
|
+
<div className={pageClasses} {...htmlProps}>
|
|
261
|
+
{/* Custom print header - regular pages */}
|
|
262
|
+
{isPrintMode && printConfig.printHeader && (
|
|
263
|
+
<div className="page-print-header">
|
|
264
|
+
{typeof printConfig.printHeader === 'string' ? (
|
|
265
|
+
<SafeSpan html={t`${printConfig.printHeader}` as string} />
|
|
266
|
+
) : (
|
|
267
|
+
printConfig.printHeader
|
|
268
|
+
)}
|
|
191
269
|
</div>
|
|
192
|
-
)
|
|
193
|
-
}
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
// Render message (error/warning/success/info)
|
|
197
|
-
const renderMessage = (messageState: MessageState) => {
|
|
198
|
-
const { type, content, customComponent } = messageState;
|
|
199
|
-
|
|
200
|
-
if (customComponent) {
|
|
201
|
-
return <div className={`page-message page-message-${type}`}>{customComponent}</div>;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const icons = {
|
|
205
|
-
error: '⚠️',
|
|
206
|
-
warning: '⚠️',
|
|
207
|
-
success: '✅',
|
|
208
|
-
info: 'ℹ️',
|
|
209
|
-
loading: '⏳'
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
return (
|
|
213
|
-
<div className={`page-message page-message-${type}`}>
|
|
214
|
-
<div className="message-icon" aria-hidden="true">{icons[type]}</div>
|
|
215
|
-
<div className="message-content">
|
|
216
|
-
{typeof content === 'string' ? <p>{content}</p> : content}
|
|
217
|
-
</div>
|
|
218
|
-
</div>
|
|
219
|
-
);
|
|
220
|
-
};
|
|
270
|
+
)}
|
|
221
271
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
272
|
+
{/* Custom print header - first page only (rendered as a separate element for app control) */}
|
|
273
|
+
{isPrintMode && printConfig.printHeaderFirstPage && (
|
|
274
|
+
<div className="page-print-header page-print-header-first-page">
|
|
275
|
+
{typeof printConfig.printHeaderFirstPage === 'string' ? (
|
|
276
|
+
<SafeSpan html={t`${printConfig.printHeaderFirstPage}` as string} />
|
|
277
|
+
) : (
|
|
278
|
+
printConfig.printHeaderFirstPage
|
|
279
|
+
)}
|
|
280
|
+
</div>
|
|
281
|
+
)}
|
|
232
282
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
283
|
+
{/* Default print header - only if no custom header provided */}
|
|
284
|
+
{isPrintMode && !printConfig.printHeader && printConfig.printTitle && (
|
|
285
|
+
<div className="page-print-header">
|
|
286
|
+
<h1>{printConfig.printTitle}</h1>
|
|
287
|
+
{printConfig.showPrintDate && (
|
|
288
|
+
<div className="page-print-date">
|
|
289
|
+
Printed on: {new Date().toLocaleString()}
|
|
290
|
+
</div>
|
|
291
|
+
)}
|
|
240
292
|
</div>
|
|
241
293
|
)}
|
|
242
294
|
|
|
243
|
-
{/*
|
|
244
|
-
{
|
|
295
|
+
{/* Page name as heading (if provided and different from title) */}
|
|
296
|
+
{resolvedValues.name && resolvedValues.name !== resolvedValues.title && (
|
|
297
|
+
<div className="page-heading">
|
|
298
|
+
<h1>{resolvedValues.name}</h1>
|
|
299
|
+
</div>
|
|
300
|
+
)}
|
|
245
301
|
|
|
246
302
|
{/* Page Content */}
|
|
247
303
|
<div className="page-content">
|
|
248
304
|
{children}
|
|
249
305
|
</div>
|
|
250
306
|
|
|
251
|
-
{/*
|
|
252
|
-
{
|
|
253
|
-
<div className="page-footer">
|
|
254
|
-
{
|
|
307
|
+
{/* Custom print footer - regular pages */}
|
|
308
|
+
{isPrintMode && printConfig.printFooter && (
|
|
309
|
+
<div className="page-print-footer">
|
|
310
|
+
{typeof printConfig.printFooter === 'string' ? (
|
|
311
|
+
<SafeSpan html={t`${printConfig.printFooter}` as string} />
|
|
312
|
+
) : (
|
|
313
|
+
printConfig.printFooter
|
|
314
|
+
)}
|
|
315
|
+
</div>
|
|
316
|
+
)}
|
|
317
|
+
|
|
318
|
+
{/* Custom print footer - first page only (rendered as a separate element for app control) */}
|
|
319
|
+
{isPrintMode && printConfig.printFooterFirstPage && (
|
|
320
|
+
<div className="page-print-footer page-print-footer-first-page">
|
|
321
|
+
{typeof printConfig.printFooterFirstPage === 'string' ? (
|
|
322
|
+
<SafeSpan html={t`${printConfig.printFooterFirstPage}` as string} />
|
|
323
|
+
) : (
|
|
324
|
+
printConfig.printFooterFirstPage
|
|
325
|
+
)}
|
|
255
326
|
</div>
|
|
256
327
|
)}
|
|
257
328
|
</div>
|
|
@@ -259,87 +330,68 @@ const Page: React.FC<PageProps> = ({
|
|
|
259
330
|
);
|
|
260
331
|
};
|
|
261
332
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
333
|
+
/**
|
|
334
|
+
* Page - Functional component that controls print/normal view routing
|
|
335
|
+
*/
|
|
336
|
+
interface PageWithViewsProps extends PageProps {
|
|
337
|
+
/** Function to render normal view */
|
|
338
|
+
renderView: () => React.ReactNode;
|
|
339
|
+
/** Function to render print view */
|
|
340
|
+
renderPrintView: () => React.ReactNode;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const Page: React.FC<PageWithViewsProps> = (props) => {
|
|
344
|
+
const { renderView, renderPrintView, ...pageProps } = props;
|
|
345
|
+
|
|
346
|
+
return (
|
|
347
|
+
<PageWrapper {...pageProps}>
|
|
348
|
+
<PageController renderView={renderView} renderPrintView={renderPrintView} />
|
|
349
|
+
</PageWrapper>
|
|
350
|
+
);
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
// Controller component that uses context to get print mode state
|
|
354
|
+
const PageController: React.FC<{
|
|
355
|
+
renderView: () => React.ReactNode;
|
|
356
|
+
renderPrintView: () => React.ReactNode;
|
|
357
|
+
}> = ({ renderView, renderPrintView }) => {
|
|
358
|
+
const { isPrintMode } = usePageContext();
|
|
359
|
+
return <>{isPrintMode ? renderPrintView() : renderView()}</>;
|
|
273
360
|
};
|
|
274
361
|
|
|
275
362
|
/**
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
* Usage:
|
|
279
|
-
* class MyPage extends BasePage {
|
|
280
|
-
* getPageProps() {
|
|
281
|
-
* return {
|
|
282
|
-
* route: '/my-page',
|
|
283
|
-
* variant: 'narrow',
|
|
284
|
-
* padding: 'large',
|
|
285
|
-
* // ... other page props
|
|
286
|
-
* };
|
|
287
|
-
* }
|
|
288
|
-
*
|
|
289
|
-
* renderContent() {
|
|
290
|
-
* return (
|
|
291
|
-
* <div>My page content</div>
|
|
292
|
-
* );
|
|
293
|
-
* }
|
|
294
|
-
* }
|
|
363
|
+
* PageComponent - Base class for pages that provide view functions
|
|
295
364
|
*/
|
|
296
|
-
export abstract class
|
|
365
|
+
export abstract class PageComponent<P = {}, S = {}> extends React.Component<P, S> {
|
|
297
366
|
/**
|
|
298
367
|
* Override this method to provide page configuration
|
|
299
|
-
* This will be passed to the Page component wrapper
|
|
300
|
-
*/
|
|
301
|
-
abstract getPageProps(): Partial<PageProps>;
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Override this method to provide the page content
|
|
305
|
-
* This replaces the render() method in your page components
|
|
306
368
|
*/
|
|
307
|
-
abstract
|
|
369
|
+
abstract getPageProps(): PageProps;
|
|
308
370
|
|
|
309
371
|
/**
|
|
310
|
-
* Override this method to
|
|
311
|
-
* Called after the page is mounted and context is available
|
|
372
|
+
* Override this method to provide the normal view content
|
|
312
373
|
*/
|
|
313
|
-
|
|
374
|
+
abstract renderView(): React.ReactNode;
|
|
314
375
|
|
|
315
376
|
/**
|
|
316
|
-
* Override this method to
|
|
377
|
+
* Override this method to provide the print view content
|
|
317
378
|
*/
|
|
318
|
-
|
|
379
|
+
abstract renderPrintView(): React.ReactNode;
|
|
319
380
|
|
|
320
381
|
/**
|
|
321
|
-
*
|
|
382
|
+
* Render method - uses functional Page as controller
|
|
322
383
|
*/
|
|
323
384
|
render() {
|
|
324
385
|
const pageProps = this.getPageProps();
|
|
325
|
-
|
|
386
|
+
|
|
326
387
|
return (
|
|
327
|
-
<Page
|
|
328
|
-
{
|
|
329
|
-
|
|
388
|
+
<Page
|
|
389
|
+
{...pageProps}
|
|
390
|
+
renderView={() => this.renderView()}
|
|
391
|
+
renderPrintView={() => this.renderPrintView()}
|
|
392
|
+
/>
|
|
330
393
|
);
|
|
331
394
|
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Component lifecycle methods
|
|
335
|
-
*/
|
|
336
|
-
componentDidMount() {
|
|
337
|
-
this.onPageMount?.();
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
componentWillUnmount() {
|
|
341
|
-
this.onPageUnmount?.();
|
|
342
|
-
}
|
|
343
395
|
}
|
|
344
396
|
|
|
345
397
|
export default Page;
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
5
5
|
*/
|
|
6
|
-
export {
|
|
6
|
+
export { PageComponent, usePageContext } from './Page';
|
|
7
7
|
export { default as FormPage } from './FormPage';
|
|
8
8
|
export { default as Page } from './Page';
|
|
9
9
|
|
|
10
10
|
export type { FormPageProps } from './FormPage';
|
|
11
|
-
export type { PageProps } from './Page';
|
|
11
|
+
export type { PageProps, PageContextValue } from './Page';
|