@pallasoft/ps-core-components 2.33.0 → 2.35.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.
Files changed (144) hide show
  1. package/dist/analytics/index.d.ts +12 -0
  2. package/dist/analytics/index.js +28 -0
  3. package/dist/analytics/report-client-error.d.ts +1 -0
  4. package/dist/analytics/report-client-error.js +6 -0
  5. package/dist/components/ps-analytics-provider.d.ts +15 -0
  6. package/dist/components/ps-analytics-provider.js +73 -0
  7. package/dist/components/ps-client-error-reporter.d.ts +5 -0
  8. package/dist/components/ps-client-error-reporter.js +22 -0
  9. package/dist/components/ps-consent-banner.d.ts +11 -0
  10. package/dist/components/ps-consent-banner.js +21 -0
  11. package/dist/constants/ps-analytics-config.d.ts +20 -0
  12. package/dist/constants/ps-analytics-config.js +23 -0
  13. package/dist/constants/ps-client-error-config.d.ts +23 -0
  14. package/dist/constants/ps-client-error-config.js +17 -0
  15. package/dist/esm/analytics/index.js +12 -0
  16. package/dist/esm/analytics/report-client-error.js +2 -0
  17. package/dist/esm/components/ps-accordion.js +2 -2
  18. package/dist/esm/components/ps-action-tile.js +2 -2
  19. package/dist/esm/components/ps-active-filter-chip.js +1 -1
  20. package/dist/esm/components/ps-analytics-provider.js +69 -0
  21. package/dist/esm/components/ps-app-store-buttons.js +3 -3
  22. package/dist/esm/components/ps-audio-spectrum.js +1 -1
  23. package/dist/esm/components/ps-badge.js +2 -2
  24. package/dist/esm/components/ps-button.js +2 -2
  25. package/dist/esm/components/ps-capped-list.js +2 -2
  26. package/dist/esm/components/ps-card-block-skeleton.js +2 -2
  27. package/dist/esm/components/ps-card.js +3 -3
  28. package/dist/esm/components/ps-client-error-reporter.js +18 -0
  29. package/dist/esm/components/ps-client-only.js +1 -1
  30. package/dist/esm/components/ps-consent-banner.js +17 -0
  31. package/dist/esm/components/ps-drag-overlay.js +2 -2
  32. package/dist/esm/components/ps-draggable-grid.js +5 -5
  33. package/dist/esm/components/ps-fab.js +1 -1
  34. package/dist/esm/components/ps-faq-answer.js +1 -1
  35. package/dist/esm/components/ps-feature-card.js +1 -1
  36. package/dist/esm/components/ps-feature-grid.js +3 -3
  37. package/dist/esm/components/ps-form-fields-skeleton.js +3 -3
  38. package/dist/esm/components/ps-form-footer.js +1 -1
  39. package/dist/esm/components/ps-form-page-skeleton.js +4 -4
  40. package/dist/esm/components/ps-icon-button.js +1 -1
  41. package/dist/esm/components/ps-icon-link.js +1 -1
  42. package/dist/esm/components/ps-infinite-scroll.js +1 -1
  43. package/dist/esm/components/ps-intro-skeleton.js +1 -1
  44. package/dist/esm/components/ps-layer.js +1 -1
  45. package/dist/esm/components/ps-link-button.js +1 -1
  46. package/dist/esm/components/ps-link-tile-grid.js +1 -1
  47. package/dist/esm/components/ps-list-tile.js +2 -2
  48. package/dist/esm/components/ps-metric-card.js +2 -2
  49. package/dist/esm/components/ps-metric-grid-skeleton.js +1 -1
  50. package/dist/esm/components/ps-metric-grid.js +2 -2
  51. package/dist/esm/components/ps-metric-row.js +2 -2
  52. package/dist/esm/components/ps-navigation-progress.js +2 -2
  53. package/dist/esm/components/ps-page-header.js +2 -2
  54. package/dist/esm/components/ps-page-skeleton.js +6 -6
  55. package/dist/esm/components/ps-pallasoft-mark.js +1 -1
  56. package/dist/esm/components/ps-pallasoft-sign-in-button.js +2 -2
  57. package/dist/esm/components/ps-pill.js +2 -2
  58. package/dist/esm/components/ps-rating-display.js +2 -2
  59. package/dist/esm/components/ps-scaled-preview.js +2 -2
  60. package/dist/esm/components/ps-section-header.js +3 -3
  61. package/dist/esm/components/ps-section-panel-skeleton.js +1 -1
  62. package/dist/esm/components/ps-signed-amount.js +1 -1
  63. package/dist/esm/components/ps-site-header.js +2 -2
  64. package/dist/esm/components/ps-skeleton-card.js +2 -2
  65. package/dist/esm/components/ps-skeleton-list.js +4 -4
  66. package/dist/esm/components/ps-skeleton-media.js +3 -3
  67. package/dist/esm/components/ps-skeleton-table.js +4 -4
  68. package/dist/esm/components/ps-skeleton-text.js +3 -3
  69. package/dist/esm/components/ps-skeleton.js +3 -3
  70. package/dist/esm/components/ps-sortable-list.js +5 -5
  71. package/dist/esm/components/ps-start-panel.js +1 -1
  72. package/dist/esm/components/ps-step-list.js +2 -2
  73. package/dist/esm/components/ps-step-progress.js +1 -1
  74. package/dist/esm/components/ps-store-badge.js +1 -1
  75. package/dist/esm/components/ps-tab-row-skeleton.js +2 -2
  76. package/dist/esm/components/ps-table-list-skeleton.js +2 -2
  77. package/dist/esm/components/ps-table-page-skeleton.js +2 -2
  78. package/dist/esm/components/ps-theme-controller.js +2 -2
  79. package/dist/esm/components/ps-tool-card-grid.js +2 -2
  80. package/dist/esm/components/ps-voice-orb.js +1 -1
  81. package/dist/esm/constants/ps-analytics-config.js +20 -0
  82. package/dist/esm/constants/ps-client-error-config.js +14 -0
  83. package/dist/esm/constants/ps-draggable-grid.js +1 -1
  84. package/dist/esm/constants/ps-rating-display-config.js +1 -1
  85. package/dist/esm/constants/ps-skeleton-config.js +1 -1
  86. package/dist/esm/constants/ps-step-marker-class-names.js +1 -1
  87. package/dist/esm/constants/ps-store-badge-config.js +1 -1
  88. package/dist/esm/hooks/use-consent.js +17 -0
  89. package/dist/esm/hooks/use-ps-drag-sensors.js +1 -1
  90. package/dist/esm/hooks/use-ps-exit-transition.js +1 -1
  91. package/dist/esm/hooks/use-ps-hydrated.js +1 -1
  92. package/dist/esm/hooks/use-ps-layer-z-index.js +1 -1
  93. package/dist/esm/hooks/use-ps-navigation-progress.js +1 -1
  94. package/dist/esm/hooks/use-ps-optimistic-fetcher.js +2 -2
  95. package/dist/esm/hooks/use-ps-optimistic-save.js +2 -2
  96. package/dist/esm/hooks/use-ps-scroll-lock.js +1 -1
  97. package/dist/esm/index.js +173 -173
  98. package/dist/esm/models/analytics-csp-input.js +1 -0
  99. package/dist/esm/models/analytics-csp-origins.js +1 -0
  100. package/dist/esm/models/ps-client-error-payload.js +1 -0
  101. package/dist/esm/models/ps-client-error-settings.js +1 -0
  102. package/dist/esm/models/ps-consent-api.js +1 -0
  103. package/dist/esm/models/ps-consent-choice.js +1 -0
  104. package/dist/esm/models/ps-consent-state.js +1 -0
  105. package/dist/esm/models/ps-gtag-window.js +1 -0
  106. package/dist/esm/services/analytics-csp-service.js +27 -0
  107. package/dist/esm/services/ps-analytics-key-service.js +8 -0
  108. package/dist/esm/services/ps-client-error-service.js +72 -0
  109. package/dist/esm/services/ps-consent-service.js +57 -0
  110. package/dist/esm/services/ps-google-analytics-service.js +41 -0
  111. package/dist/esm/services/ps-optimistic-save-service.js +1 -1
  112. package/dist/esm/services/ps-post-hog-service.js +18 -0
  113. package/dist/esm/services/ps-skeleton-class-service.js +1 -1
  114. package/dist/hooks/use-consent.d.ts +2 -0
  115. package/dist/hooks/use-consent.js +21 -0
  116. package/dist/models/analytics-csp-input.d.ts +7 -0
  117. package/dist/models/analytics-csp-input.js +2 -0
  118. package/dist/models/analytics-csp-origins.d.ts +5 -0
  119. package/dist/models/analytics-csp-origins.js +2 -0
  120. package/dist/models/ps-client-error-payload.d.ts +9 -0
  121. package/dist/models/ps-client-error-payload.js +2 -0
  122. package/dist/models/ps-client-error-settings.d.ts +4 -0
  123. package/dist/models/ps-client-error-settings.js +2 -0
  124. package/dist/models/ps-consent-api.d.ts +9 -0
  125. package/dist/models/ps-consent-api.js +2 -0
  126. package/dist/models/ps-consent-choice.d.ts +1 -0
  127. package/dist/models/ps-consent-choice.js +2 -0
  128. package/dist/models/ps-consent-state.d.ts +5 -0
  129. package/dist/models/ps-consent-state.js +2 -0
  130. package/dist/models/ps-gtag-window.d.ts +4 -0
  131. package/dist/models/ps-gtag-window.js +2 -0
  132. package/dist/services/analytics-csp-service.d.ts +7 -0
  133. package/dist/services/analytics-csp-service.js +31 -0
  134. package/dist/services/ps-analytics-key-service.d.ts +3 -0
  135. package/dist/services/ps-analytics-key-service.js +12 -0
  136. package/dist/services/ps-client-error-service.d.ts +14 -0
  137. package/dist/services/ps-client-error-service.js +76 -0
  138. package/dist/services/ps-consent-service.d.ts +15 -0
  139. package/dist/services/ps-consent-service.js +61 -0
  140. package/dist/services/ps-google-analytics-service.d.ts +6 -0
  141. package/dist/services/ps-google-analytics-service.js +45 -0
  142. package/dist/services/ps-post-hog-service.d.ts +4 -0
  143. package/dist/services/ps-post-hog-service.js +55 -0
  144. package/package.json +17 -2
@@ -0,0 +1,12 @@
1
+ export * from '../components/ps-analytics-provider';
2
+ export * from '../components/ps-client-error-reporter';
3
+ export * from '../components/ps-consent-banner';
4
+ export * from '../constants/ps-analytics-config';
5
+ export * from '../constants/ps-client-error-config';
6
+ export * from '../hooks/use-consent';
7
+ export * from '../models/analytics-csp-input';
8
+ export * from '../models/analytics-csp-origins';
9
+ export * from '../models/ps-consent-api';
10
+ export * from '../models/ps-consent-choice';
11
+ export * from '../services/analytics-csp-service';
12
+ export * from './report-client-error';
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("../components/ps-analytics-provider"), exports);
18
+ __exportStar(require("../components/ps-client-error-reporter"), exports);
19
+ __exportStar(require("../components/ps-consent-banner"), exports);
20
+ __exportStar(require("../constants/ps-analytics-config"), exports);
21
+ __exportStar(require("../constants/ps-client-error-config"), exports);
22
+ __exportStar(require("../hooks/use-consent"), exports);
23
+ __exportStar(require("../models/analytics-csp-input"), exports);
24
+ __exportStar(require("../models/analytics-csp-origins"), exports);
25
+ __exportStar(require("../models/ps-consent-api"), exports);
26
+ __exportStar(require("../models/ps-consent-choice"), exports);
27
+ __exportStar(require("../services/analytics-csp-service"), exports);
28
+ __exportStar(require("./report-client-error"), exports);
@@ -0,0 +1 @@
1
+ export declare const reportClientError: (error: unknown, context?: string) => void;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.reportClientError = void 0;
4
+ const ps_client_error_service_1 = require("../services/ps-client-error-service");
5
+ const reportClientError = (error, context) => ps_client_error_service_1.PsClientErrorService.report(error, context);
6
+ exports.reportClientError = reportClientError;
@@ -0,0 +1,15 @@
1
+ import { type ReactNode } from 'react';
2
+ export interface PsAnalyticsProviderProps {
3
+ children?: ReactNode;
4
+ googleAnalyticsId?: string;
5
+ googleTagManagerUrl?: string;
6
+ postHogKey?: string;
7
+ postHogApiHost?: string;
8
+ userId?: string | null;
9
+ appVersion?: string;
10
+ nonce?: string;
11
+ consentCookieName?: string;
12
+ requireConsent?: boolean;
13
+ pathname?: string;
14
+ }
15
+ export declare const PsAnalyticsProvider: ({ children, googleAnalyticsId, googleTagManagerUrl, postHogKey, postHogApiHost, userId, appVersion, nonce, consentCookieName, requireConsent, pathname, }: PsAnalyticsProviderProps) => import("react").JSX.Element;
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsAnalyticsProvider = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const ps_analytics_config_1 = require("../constants/ps-analytics-config");
7
+ const use_consent_1 = require("../hooks/use-consent");
8
+ const ps_analytics_key_service_1 = require("../services/ps-analytics-key-service");
9
+ const ps_google_analytics_service_1 = require("../services/ps-google-analytics-service");
10
+ const ps_post_hog_service_1 = require("../services/ps-post-hog-service");
11
+ const PsAnalyticsProvider = ({ children, googleAnalyticsId, googleTagManagerUrl = ps_analytics_config_1.PsAnalyticsConfig.defaultTagManagerUrl, postHogKey, postHogApiHost, userId, appVersion, nonce, consentCookieName = ps_analytics_config_1.PsAnalyticsConfig.defaultConsentCookieName, requireConsent = ps_analytics_config_1.PsAnalyticsConfig.defaultRequireConsent, pathname, }) => {
12
+ const consent = (0, use_consent_1.useConsent)(consentCookieName);
13
+ const isGranted = requireConsent ? consent.isGranted : true;
14
+ const [postHog, setPostHog] = (0, react_1.useState)(null);
15
+ const identifiedRef = (0, react_1.useRef)(null);
16
+ const isGoogleOn = ps_analytics_key_service_1.PsAnalyticsKeyService.isEnabled(googleAnalyticsId);
17
+ const isPostHogOn = ps_analytics_key_service_1.PsAnalyticsKeyService.isEnabled(postHogKey);
18
+ const currentPath = pathname !== undefined ? pathname : '';
19
+ const normalizedUserId = userId ? userId : null;
20
+ (0, react_1.useEffect)(() => {
21
+ if (!isGoogleOn)
22
+ return;
23
+ if (!isGranted) {
24
+ ps_google_analytics_service_1.PsGoogleAnalyticsService.disable(googleAnalyticsId);
25
+ return;
26
+ }
27
+ ps_google_analytics_service_1.PsGoogleAnalyticsService.load(googleAnalyticsId, googleTagManagerUrl, nonce);
28
+ }, [isGoogleOn, isGranted, googleAnalyticsId, googleTagManagerUrl, nonce]);
29
+ (0, react_1.useEffect)(() => {
30
+ if (!isGoogleOn || !isGranted)
31
+ return;
32
+ ps_google_analytics_service_1.PsGoogleAnalyticsService.configure(googleAnalyticsId, currentPath, normalizedUserId);
33
+ }, [isGoogleOn, isGranted, googleAnalyticsId, currentPath, normalizedUserId]);
34
+ (0, react_1.useEffect)(() => {
35
+ if (!isPostHogOn)
36
+ return;
37
+ if (!isGranted) {
38
+ if (postHog)
39
+ postHog.opt_out_capturing();
40
+ return;
41
+ }
42
+ let isCancelled = false;
43
+ ps_post_hog_service_1.PsPostHogService.load(postHogKey, postHogApiHost).then((client) => {
44
+ if (!isCancelled)
45
+ setPostHog(client);
46
+ });
47
+ return () => {
48
+ isCancelled = true;
49
+ };
50
+ }, [isPostHogOn, isGranted, postHogKey, postHogApiHost]);
51
+ (0, react_1.useEffect)(() => {
52
+ if (!postHog || !isGranted)
53
+ return;
54
+ if (normalizedUserId) {
55
+ postHog.identify(normalizedUserId, appVersion ? { app_version: appVersion } : undefined);
56
+ identifiedRef.current = normalizedUserId;
57
+ return;
58
+ }
59
+ if (identifiedRef.current) {
60
+ postHog.reset();
61
+ identifiedRef.current = null;
62
+ }
63
+ }, [postHog, isGranted, normalizedUserId, appVersion]);
64
+ (0, react_1.useEffect)(() => {
65
+ if (!postHog || !isGranted)
66
+ return;
67
+ postHog.capture(ps_analytics_config_1.PsAnalyticsConfig.postHog.pageviewEvent, {
68
+ $pathname: currentPath,
69
+ });
70
+ }, [postHog, isGranted, currentPath]);
71
+ return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children });
72
+ };
73
+ exports.PsAnalyticsProvider = PsAnalyticsProvider;
@@ -0,0 +1,5 @@
1
+ export interface PsClientErrorReporterProps {
2
+ endpoint: string;
3
+ appVersion?: string;
4
+ }
5
+ export declare const PsClientErrorReporter: ({ endpoint, appVersion }: PsClientErrorReporterProps) => null;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsClientErrorReporter = void 0;
4
+ const react_1 = require("react");
5
+ const ps_client_error_config_1 = require("../constants/ps-client-error-config");
6
+ const ps_client_error_service_1 = require("../services/ps-client-error-service");
7
+ const PsClientErrorReporter = ({ endpoint, appVersion }) => {
8
+ (0, react_1.useEffect)(() => {
9
+ ps_client_error_service_1.PsClientErrorService.configure({ endpoint, appVersion });
10
+ const onError = (event) => ps_client_error_service_1.PsClientErrorService.report(event.error ? event.error : event.message, '', ps_client_error_config_1.PsClientErrorConfig.sources.error);
11
+ const onRejection = (event) => ps_client_error_service_1.PsClientErrorService.report(event.reason, '', ps_client_error_config_1.PsClientErrorConfig.sources.rejection);
12
+ window.addEventListener('error', onError);
13
+ window.addEventListener('unhandledrejection', onRejection);
14
+ return () => {
15
+ window.removeEventListener('error', onError);
16
+ window.removeEventListener('unhandledrejection', onRejection);
17
+ ps_client_error_service_1.PsClientErrorService.clear();
18
+ };
19
+ }, [endpoint, appVersion]);
20
+ return null;
21
+ };
22
+ exports.PsClientErrorReporter = PsClientErrorReporter;
@@ -0,0 +1,11 @@
1
+ export interface PsConsentBannerProps {
2
+ title: string;
3
+ message: string;
4
+ acceptLabel: string;
5
+ rejectLabel: string;
6
+ privacyPolicyHref: string;
7
+ privacyPolicyLabel: string;
8
+ consentCookieName?: string;
9
+ className?: string;
10
+ }
11
+ export declare const PsConsentBanner: ({ title, message, acceptLabel, rejectLabel, privacyPolicyHref, privacyPolicyLabel, consentCookieName, className, }: PsConsentBannerProps) => import("react").JSX.Element | null;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsConsentBanner = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const ps_design_system_1 = require("@pallasoft/ps-design-system");
7
+ const ps_analytics_config_1 = require("../constants/ps-analytics-config");
8
+ const use_consent_1 = require("../hooks/use-consent");
9
+ const ps_button_1 = require("./ps-button");
10
+ const PsConsentBanner = ({ title, message, acceptLabel, rejectLabel, privacyPolicyHref, privacyPolicyLabel, consentCookieName = ps_analytics_config_1.PsAnalyticsConfig.defaultConsentCookieName, className, }) => {
11
+ const { isBannerOpen, accept, reject } = (0, use_consent_1.useConsent)(consentCookieName);
12
+ const ref = (0, react_1.useRef)(null);
13
+ (0, react_1.useEffect)(() => {
14
+ if (isBannerOpen)
15
+ ref.current?.focus();
16
+ }, [isBannerOpen]);
17
+ if (!isBannerOpen)
18
+ return null;
19
+ return ((0, jsx_runtime_1.jsxs)("div", { ref: ref, role: 'dialog', "aria-label": title, "aria-live": 'polite', tabIndex: -1, className: `ps-card fixed inset-x-3 bottom-3 flex flex-col gap-3 p-4 shadow-lg outline-none sm:left-auto sm:right-4 sm:max-w-md ${className ? className : ''}`, style: { zIndex: ps_design_system_1.PallasoftZIndex.Toast }, children: [(0, jsx_runtime_1.jsxs)("p", { className: 'text-sm', children: [message, ' ', (0, jsx_runtime_1.jsx)("a", { className: 'underline', href: privacyPolicyHref, children: privacyPolicyLabel })] }), (0, jsx_runtime_1.jsxs)("div", { className: 'flex gap-2', children: [(0, jsx_runtime_1.jsx)(ps_button_1.PsButton, { className: 'flex-1', size: 'compact', variant: 'secondary', onClick: reject, children: rejectLabel }), (0, jsx_runtime_1.jsx)(ps_button_1.PsButton, { className: 'flex-1', size: 'compact', variant: 'secondary', onClick: accept, children: acceptLabel })] })] }));
20
+ };
21
+ exports.PsConsentBanner = PsConsentBanner;
@@ -0,0 +1,20 @@
1
+ export declare const PsAnalyticsConfig: {
2
+ readonly offValues: readonly ["", "_"];
3
+ readonly defaultConsentCookieName: "ps_consent";
4
+ readonly defaultRequireConsent: true;
5
+ readonly consentGranted: "granted";
6
+ readonly consentDenied: "denied";
7
+ readonly consentMaxAgeSeconds: 31536000;
8
+ readonly defaultTagManagerUrl: "https://www.googletagmanager.com";
9
+ readonly tagManagerScriptPath: "/gtag/js?id=";
10
+ readonly scriptAttribute: "data-ps-analytics";
11
+ readonly postHog: {
12
+ readonly defaults: "2026-05-30";
13
+ readonly personProfiles: "identified_only";
14
+ readonly autocapture: true;
15
+ readonly capturePageleave: true;
16
+ readonly captureExceptions: true;
17
+ readonly pageviewEvent: "$pageview";
18
+ };
19
+ readonly listSeparator: ",";
20
+ };
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsAnalyticsConfig = void 0;
4
+ exports.PsAnalyticsConfig = {
5
+ offValues: ['', '_'],
6
+ defaultConsentCookieName: 'ps_consent',
7
+ defaultRequireConsent: true,
8
+ consentGranted: 'granted',
9
+ consentDenied: 'denied',
10
+ consentMaxAgeSeconds: 31536000,
11
+ defaultTagManagerUrl: 'https://www.googletagmanager.com',
12
+ tagManagerScriptPath: '/gtag/js?id=',
13
+ scriptAttribute: 'data-ps-analytics',
14
+ postHog: {
15
+ defaults: '2026-05-30',
16
+ personProfiles: 'identified_only',
17
+ autocapture: true,
18
+ capturePageleave: true,
19
+ captureExceptions: true,
20
+ pageviewEvent: '$pageview',
21
+ },
22
+ listSeparator: ',',
23
+ };
@@ -0,0 +1,23 @@
1
+ export declare const PsClientErrorConfig: {
2
+ readonly maxReportsPerPage: 10;
3
+ readonly maxStackLength: 2048;
4
+ readonly maxMessageLength: 500;
5
+ readonly maxContextLength: 200;
6
+ readonly jsonContentType: "application/json";
7
+ readonly emailPattern: RegExp;
8
+ readonly redactedValue: "[redacted]";
9
+ readonly unknownMessage: "Unknown error";
10
+ readonly botPattern: RegExp;
11
+ readonly mobilePattern: RegExp;
12
+ readonly userAgentClasses: {
13
+ readonly bot: "bot";
14
+ readonly mobile: "mobile";
15
+ readonly desktop: "desktop";
16
+ readonly unknown: "unknown";
17
+ };
18
+ readonly sources: {
19
+ readonly error: "error";
20
+ readonly rejection: "unhandledrejection";
21
+ readonly boundary: "boundary";
22
+ };
23
+ };
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsClientErrorConfig = void 0;
4
+ exports.PsClientErrorConfig = {
5
+ maxReportsPerPage: 10,
6
+ maxStackLength: 2048,
7
+ maxMessageLength: 500,
8
+ maxContextLength: 200,
9
+ jsonContentType: 'application/json',
10
+ emailPattern: /[^\s@"'()<>]+@[^\s@"'()<>]+\.[^\s@"'()<>]+/g,
11
+ redactedValue: '[redacted]',
12
+ unknownMessage: 'Unknown error',
13
+ botPattern: /bot|crawl|spider|slurp|headless/i,
14
+ mobilePattern: /mobi|android|iphone|ipad/i,
15
+ userAgentClasses: { bot: 'bot', mobile: 'mobile', desktop: 'desktop', unknown: 'unknown' },
16
+ sources: { error: 'error', rejection: 'unhandledrejection', boundary: 'boundary' },
17
+ };
@@ -0,0 +1,12 @@
1
+ export * from '../components/ps-analytics-provider.js';
2
+ export * from '../components/ps-client-error-reporter.js';
3
+ export * from '../components/ps-consent-banner.js';
4
+ export * from '../constants/ps-analytics-config.js';
5
+ export * from '../constants/ps-client-error-config.js';
6
+ export * from '../hooks/use-consent.js';
7
+ export * from '../models/analytics-csp-input.js';
8
+ export * from '../models/analytics-csp-origins.js';
9
+ export * from '../models/ps-consent-api.js';
10
+ export * from '../models/ps-consent-choice.js';
11
+ export * from '../services/analytics-csp-service.js';
12
+ export * from './report-client-error.js';
@@ -0,0 +1,2 @@
1
+ import { PsClientErrorService } from '../services/ps-client-error-service.js';
2
+ export const reportClientError = (error, context) => PsClientErrorService.report(error, context);
@@ -2,6 +2,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
3
3
  import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
4
4
  import * as AccordionPrimitive from '@radix-ui/react-accordion';
5
- import { PsSkeleton } from './ps-skeleton';
6
- import { PsSkeletonGroup } from './ps-skeleton-group';
5
+ import { PsSkeleton } from './ps-skeleton.js';
6
+ import { PsSkeletonGroup } from './ps-skeleton-group.js';
7
7
  export const PsAccordion = ({ children, className, isLoading = false, isOpen, label, onToggle, trailing, }) => isLoading ? (_jsx(PsSkeletonGroup, { className: PallasoftClassNameService.merge('rounded-ps-card border border-line/70 bg-surface/60 px-4 py-3.5', className), label: 'Loading', children: _jsx(PsSkeleton, { className: 'h-4 w-40' }) })) : (_jsx(AccordionPrimitive.Root, { type: 'single', collapsible: true, value: isOpen ? 'open' : '', onValueChange: onToggle, className: PallasoftClassNameService.merge('overflow-hidden rounded-ps-card border border-line/70 bg-surface/60 transition-colors duration-200', isOpen ? 'border-primary/25 bg-surface' : 'hover:border-line hover:bg-surface/90', className), children: _jsxs(AccordionPrimitive.Item, { value: 'open', children: [_jsx(AccordionPrimitive.Header, { children: _jsxs(AccordionPrimitive.Trigger, { className: 'ps-focus-ring flex w-full items-center justify-between gap-3 px-4 py-3.5 text-left', children: [_jsx("span", { className: 'text-body-2 font-semibold text-ink', children: label }), _jsxs("span", { className: 'flex shrink-0 items-center gap-2', children: [trailing, _jsx(PsIcon, { iconKey: PsIconKey.ChevronRight, className: PallasoftClassNameService.merge('h-4 w-4 text-ink/40 transition-transform duration-200', isOpen ? 'rotate-90 text-primary' : '') })] })] }) }), _jsx(AccordionPrimitive.Content, { className: 'ps-accordion-content', children: _jsx("div", { className: 'border-t border-line/70 p-4', children: children }) })] }) }));
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
3
- import { PsSkeletonShape } from '../constants/ps-skeleton-shape';
4
- import { PsSkeleton } from './ps-skeleton';
3
+ import { PsSkeletonShape } from '../constants/ps-skeleton-shape.js';
4
+ import { PsSkeleton } from './ps-skeleton.js';
5
5
  const variantClassNames = {
6
6
  default: 'ps-action-tile',
7
7
  primary: 'ps-action-tile ps-action-tile-primary',
@@ -1,4 +1,4 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
3
- import { PsPill } from './ps-pill';
3
+ import { PsPill } from './ps-pill.js';
4
4
  export const PsActiveFilterChip = ({ id, label, onRemove, removeLabel, }) => (_jsx("button", { "aria-label": removeLabel, className: 'ps-focus-ring shrink-0 rounded-ps-control', id: id, onClick: onRemove, type: 'button', children: _jsx(PsPill, { className: 'pr-2', tone: 'primary', children: _jsxs("span", { className: 'flex items-center gap-1.5', children: [label, _jsx(PsIcon, { className: 'h-3 w-3', iconKey: PsIconKey.Close })] }) }) }));
@@ -0,0 +1,69 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import { PsAnalyticsConfig } from '../constants/ps-analytics-config.js';
4
+ import { useConsent } from '../hooks/use-consent.js';
5
+ import { PsAnalyticsKeyService } from '../services/ps-analytics-key-service.js';
6
+ import { PsGoogleAnalyticsService } from '../services/ps-google-analytics-service.js';
7
+ import { PsPostHogService } from '../services/ps-post-hog-service.js';
8
+ export const PsAnalyticsProvider = ({ children, googleAnalyticsId, googleTagManagerUrl = PsAnalyticsConfig.defaultTagManagerUrl, postHogKey, postHogApiHost, userId, appVersion, nonce, consentCookieName = PsAnalyticsConfig.defaultConsentCookieName, requireConsent = PsAnalyticsConfig.defaultRequireConsent, pathname, }) => {
9
+ const consent = useConsent(consentCookieName);
10
+ const isGranted = requireConsent ? consent.isGranted : true;
11
+ const [postHog, setPostHog] = useState(null);
12
+ const identifiedRef = useRef(null);
13
+ const isGoogleOn = PsAnalyticsKeyService.isEnabled(googleAnalyticsId);
14
+ const isPostHogOn = PsAnalyticsKeyService.isEnabled(postHogKey);
15
+ const currentPath = pathname !== undefined ? pathname : '';
16
+ const normalizedUserId = userId ? userId : null;
17
+ useEffect(() => {
18
+ if (!isGoogleOn)
19
+ return;
20
+ if (!isGranted) {
21
+ PsGoogleAnalyticsService.disable(googleAnalyticsId);
22
+ return;
23
+ }
24
+ PsGoogleAnalyticsService.load(googleAnalyticsId, googleTagManagerUrl, nonce);
25
+ }, [isGoogleOn, isGranted, googleAnalyticsId, googleTagManagerUrl, nonce]);
26
+ useEffect(() => {
27
+ if (!isGoogleOn || !isGranted)
28
+ return;
29
+ PsGoogleAnalyticsService.configure(googleAnalyticsId, currentPath, normalizedUserId);
30
+ }, [isGoogleOn, isGranted, googleAnalyticsId, currentPath, normalizedUserId]);
31
+ useEffect(() => {
32
+ if (!isPostHogOn)
33
+ return;
34
+ if (!isGranted) {
35
+ if (postHog)
36
+ postHog.opt_out_capturing();
37
+ return;
38
+ }
39
+ let isCancelled = false;
40
+ PsPostHogService.load(postHogKey, postHogApiHost).then((client) => {
41
+ if (!isCancelled)
42
+ setPostHog(client);
43
+ });
44
+ return () => {
45
+ isCancelled = true;
46
+ };
47
+ }, [isPostHogOn, isGranted, postHogKey, postHogApiHost]);
48
+ useEffect(() => {
49
+ if (!postHog || !isGranted)
50
+ return;
51
+ if (normalizedUserId) {
52
+ postHog.identify(normalizedUserId, appVersion ? { app_version: appVersion } : undefined);
53
+ identifiedRef.current = normalizedUserId;
54
+ return;
55
+ }
56
+ if (identifiedRef.current) {
57
+ postHog.reset();
58
+ identifiedRef.current = null;
59
+ }
60
+ }, [postHog, isGranted, normalizedUserId, appVersion]);
61
+ useEffect(() => {
62
+ if (!postHog || !isGranted)
63
+ return;
64
+ postHog.capture(PsAnalyticsConfig.postHog.pageviewEvent, {
65
+ $pathname: currentPath,
66
+ });
67
+ }, [postHog, isGranted, currentPath]);
68
+ return _jsx(_Fragment, { children: children });
69
+ };
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
3
- import { PsStoreBadgeClasses } from '../constants/ps-store-badge-config';
4
- import { PsStorePlatform } from '../constants/ps-store-platform';
5
- import { PsStoreBadge } from './ps-store-badge';
3
+ import { PsStoreBadgeClasses } from '../constants/ps-store-badge-config.js';
4
+ import { PsStorePlatform } from '../constants/ps-store-platform.js';
5
+ import { PsStoreBadge } from './ps-store-badge.js';
6
6
  const isLinked = (url) => url !== undefined && url.trim() !== '';
7
7
  export const PsAppStoreButtons = ({ appStoreLabels, appStoreUrl, className, playStoreLabels, playStoreUrl, size = 'default', }) => {
8
8
  const appStoreHref = isLinked(appStoreUrl) ? appStoreUrl : undefined;
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useRef } from 'react';
3
3
  import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
4
- import { PsAudioSpectrumConfig } from '../constants/ps-audio-spectrum-config';
4
+ import { PsAudioSpectrumConfig } from '../constants/ps-audio-spectrum-config.js';
5
5
  const scaleFor = (level) => {
6
6
  const bounded = Math.max(0, Math.min(1, level));
7
7
  return PsAudioSpectrumConfig.minimumScale + bounded * (1 - PsAudioSpectrumConfig.minimumScale);
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
3
- import { PsSkeletonShape } from '../constants/ps-skeleton-shape';
4
- import { PsSkeleton } from './ps-skeleton';
3
+ import { PsSkeletonShape } from '../constants/ps-skeleton-shape.js';
4
+ import { PsSkeleton } from './ps-skeleton.js';
5
5
  const toneClassNames = {
6
6
  danger: 'bg-danger-soft text-danger dark:bg-rose-500/18 dark:text-rose-100',
7
7
  neutral: 'bg-slate-100 text-slate-700 dark:bg-white/10 dark:text-slate-100',
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
3
- import { PsButtonBaseClassName, PsButtonSizeClassNames, PsButtonVariantClassNames, } from '../constants/ps-button-classes';
4
- import { PsSpinner } from './ps-spinner';
3
+ import { PsButtonBaseClassName, PsButtonSizeClassNames, PsButtonVariantClassNames, } from '../constants/ps-button-classes.js';
4
+ import { PsSpinner } from './ps-spinner.js';
5
5
  export const PsButton = ({ children, className, disabled, isLabelHiddenOnMobile = false, isLoading = false, leadingIcon, loadingLabel, size = 'default', trailingIcon, type = 'button', variant = 'secondary', ...props }) => (_jsxs("button", { className: PallasoftClassNameService.merge(PsButtonBaseClassName, 'disabled:cursor-not-allowed disabled:opacity-50', PsButtonSizeClassNames[size], isLabelHiddenOnMobile && leadingIcon ? 'min-w-11 max-sm:px-0' : '', PsButtonVariantClassNames[variant], className), "aria-busy": isLoading || undefined, disabled: disabled || isLoading, type: type, ...props, children: [isLoading ? (_jsx(PsSpinner, { className: 'absolute inset-0 m-auto', color: 'current', label: loadingLabel !== null && loadingLabel !== undefined ? loadingLabel : 'Loading', size: 'small' })) : null, _jsxs("span", { "aria-hidden": isLoading || undefined, className: PallasoftClassNameService.merge('inline-flex min-w-0 items-center gap-2', isLoading ? 'invisible' : ''), children: [leadingIcon ? _jsx("span", { className: 'shrink-0', children: leadingIcon }) : null, _jsx("span", { className: isLabelHiddenOnMobile && leadingIcon ? 'sr-only sm:not-sr-only sm:truncate' : 'truncate', children: children }), trailingIcon ? _jsx("span", { className: 'shrink-0', children: trailingIcon }) : null] })] }));
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState } from 'react';
3
- import { PsCappedListConfig } from '../constants/ps-capped-list-config';
4
- import { PsButton } from './ps-button';
3
+ import { PsCappedListConfig } from '../constants/ps-capped-list-config.js';
4
+ import { PsButton } from './ps-button.js';
5
5
  export const PsCappedList = ({ getKey, items, renderItem, showAllLabel, visibleRows = PsCappedListConfig.defaultVisibleRows, }) => {
6
6
  const [isExpanded, setIsExpanded] = useState(false);
7
7
  const isCapped = !isExpanded && items.length > visibleRows;
@@ -1,4 +1,4 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { PsSkeletonShape } from '../constants/ps-skeleton-shape';
3
- import { PsSkeleton } from './ps-skeleton';
2
+ import { PsSkeletonShape } from '../constants/ps-skeleton-shape.js';
3
+ import { PsSkeleton } from './ps-skeleton.js';
4
4
  export const PsCardBlockSkeleton = ({ height }) => (_jsx(PsSkeleton, { height: height, radius: '1.25rem', shape: PsSkeletonShape.Block, width: '100%' }));
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
3
- import { PsSkeletonShape } from '../constants/ps-skeleton-shape';
4
- import { PsSkeleton } from './ps-skeleton';
5
- import { PsSkeletonGroup } from './ps-skeleton-group';
3
+ import { PsSkeletonShape } from '../constants/ps-skeleton-shape.js';
4
+ import { PsSkeleton } from './ps-skeleton.js';
5
+ import { PsSkeletonGroup } from './ps-skeleton-group.js';
6
6
  const densityClassNames = {
7
7
  compact: 'p-3',
8
8
  normal: 'p-4',
@@ -0,0 +1,18 @@
1
+ import { useEffect } from 'react';
2
+ import { PsClientErrorConfig } from '../constants/ps-client-error-config.js';
3
+ import { PsClientErrorService } from '../services/ps-client-error-service.js';
4
+ export const PsClientErrorReporter = ({ endpoint, appVersion }) => {
5
+ useEffect(() => {
6
+ PsClientErrorService.configure({ endpoint, appVersion });
7
+ const onError = (event) => PsClientErrorService.report(event.error ? event.error : event.message, '', PsClientErrorConfig.sources.error);
8
+ const onRejection = (event) => PsClientErrorService.report(event.reason, '', PsClientErrorConfig.sources.rejection);
9
+ window.addEventListener('error', onError);
10
+ window.addEventListener('unhandledrejection', onRejection);
11
+ return () => {
12
+ window.removeEventListener('error', onError);
13
+ window.removeEventListener('unhandledrejection', onRejection);
14
+ PsClientErrorService.clear();
15
+ };
16
+ }, [endpoint, appVersion]);
17
+ return null;
18
+ };
@@ -1,3 +1,3 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
- import { usePsHydrated } from '../hooks/use-ps-hydrated';
2
+ import { usePsHydrated } from '../hooks/use-ps-hydrated.js';
3
3
  export const PsClientOnly = ({ children, fallback = null }) => usePsHydrated() ? _jsx(_Fragment, { children: children() }) : _jsx(_Fragment, { children: fallback });
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef } from 'react';
3
+ import { PallasoftZIndex } from '@pallasoft/ps-design-system';
4
+ import { PsAnalyticsConfig } from '../constants/ps-analytics-config.js';
5
+ import { useConsent } from '../hooks/use-consent.js';
6
+ import { PsButton } from './ps-button.js';
7
+ export const PsConsentBanner = ({ title, message, acceptLabel, rejectLabel, privacyPolicyHref, privacyPolicyLabel, consentCookieName = PsAnalyticsConfig.defaultConsentCookieName, className, }) => {
8
+ const { isBannerOpen, accept, reject } = useConsent(consentCookieName);
9
+ const ref = useRef(null);
10
+ useEffect(() => {
11
+ if (isBannerOpen)
12
+ ref.current?.focus();
13
+ }, [isBannerOpen]);
14
+ if (!isBannerOpen)
15
+ return null;
16
+ return (_jsxs("div", { ref: ref, role: 'dialog', "aria-label": title, "aria-live": 'polite', tabIndex: -1, className: `ps-card fixed inset-x-3 bottom-3 flex flex-col gap-3 p-4 shadow-lg outline-none sm:left-auto sm:right-4 sm:max-w-md ${className ? className : ''}`, style: { zIndex: PallasoftZIndex.Toast }, children: [_jsxs("p", { className: 'text-sm', children: [message, ' ', _jsx("a", { className: 'underline', href: privacyPolicyHref, children: privacyPolicyLabel })] }), _jsxs("div", { className: 'flex gap-2', children: [_jsx(PsButton, { className: 'flex-1', size: 'compact', variant: 'secondary', onClick: reject, children: rejectLabel }), _jsx(PsButton, { className: 'flex-1', size: 'compact', variant: 'secondary', onClick: accept, children: acceptLabel })] })] }));
17
+ };
@@ -2,8 +2,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from 'react';
3
3
  import { DragOverlay, defaultDropAnimationSideEffects } from '@dnd-kit/core';
4
4
  import { createPortal } from 'react-dom';
5
- import { PsDragConfig } from '../constants/ps-drag-config';
6
- import { usePsReducedMotion } from '../hooks/use-ps-reduced-motion';
5
+ import { PsDragConfig } from '../constants/ps-drag-config.js';
6
+ import { usePsReducedMotion } from '../hooks/use-ps-reduced-motion.js';
7
7
  const dropAnimation = {
8
8
  duration: PsDragConfig.dropDuration,
9
9
  easing: PsDragConfig.dropEasing,
@@ -3,11 +3,11 @@ import { useState } from 'react';
3
3
  import { DndContext, closestCenter } from '@dnd-kit/core';
4
4
  import { SortableContext, arrayMove, rectSortingStrategy, useSortable } from '@dnd-kit/sortable';
5
5
  import { CSS } from '@dnd-kit/utilities';
6
- import { PsDragConfig } from '../constants/ps-drag-config';
7
- import { PsDraggableGridClasses, PsDraggableGridLiftFrames } from '../constants/ps-draggable-grid';
8
- import { usePsDragSensors } from '../hooks/use-ps-drag-sensors';
9
- import { PsDragGrip } from './ps-drag-grip';
10
- import { PsDragOverlay } from './ps-drag-overlay';
6
+ import { PsDragConfig } from '../constants/ps-drag-config.js';
7
+ import { PsDraggableGridClasses, PsDraggableGridLiftFrames } from '../constants/ps-draggable-grid.js';
8
+ import { usePsDragSensors } from '../hooks/use-ps-drag-sensors.js';
9
+ import { PsDragGrip } from './ps-drag-grip.js';
10
+ import { PsDragOverlay } from './ps-drag-overlay.js';
11
11
  const PsDraggableGridHandle = ({ attributes, label, listeners, setActivatorNodeRef, }) => (_jsx("button", { ...attributes, ...listeners, "aria-label": `Move ${label}`, className: PsDraggableGridClasses.handle, ref: setActivatorNodeRef, title: `Move ${label}`, type: 'button', children: _jsx(PsDragGrip, {}) }));
12
12
  const PsSortableGridItem = ({ className, index, item, renderItem, }) => {
13
13
  const { attributes, isDragging, listeners, setActivatorNodeRef, setNodeRef, transform, transition, } = useSortable({ id: item.id });
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
3
- import { PsButton } from './ps-button';
3
+ import { PsButton } from './ps-button.js';
4
4
  const positionClassNames = {
5
5
  'bottom-right': 'bottom-6 right-6',
6
6
  'bottom-left': 'bottom-6 left-6',
@@ -1,3 +1,3 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { PsFaqBlockKind } from '../constants/ps-faq-block-kind';
2
+ import { PsFaqBlockKind } from '../constants/ps-faq-block-kind.js';
3
3
  export const PsFaqAnswer = ({ blocks }) => (_jsx("div", { className: 'flex flex-col gap-3', children: blocks.map((block, index) => block.kind === PsFaqBlockKind.List ? (_jsx("ul", { className: 'list-disc pl-5', children: block.items.map((item) => (_jsx("li", { children: item }, item))) }, index)) : (_jsx("div", { className: 'flex flex-col gap-3', children: block.items.map((item) => (_jsx("p", { children: item }, item))) }, index))) }));
@@ -1,4 +1,4 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { PsIcon } from '@pallasoft/ps-icons';
3
- import { PsCard } from './ps-card';
3
+ import { PsCard } from './ps-card.js';
4
4
  export const PsFeatureCard = ({ description, iconKey, title }) => (_jsxs(PsCard, { className: 'h-full text-center', density: 'spacious', children: [_jsx("span", { className: 'ps-icon-surface mx-auto h-12 w-12', children: _jsx(PsIcon, { className: 'h-6 w-6', iconKey: iconKey }) }), _jsx("h3", { className: 'text-header-4 mb-2 mt-4 text-ink', children: title }), _jsx("p", { className: 'text-body-2 text-ink/60', children: description })] }));
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { PsStaggerContainer, PsStaggerItem } from '@pallasoft/ps-motion-components';
3
- import { PsWebsiteSectionConfig } from '../constants/ps-website-section-config';
4
- import { PsFeatureCard } from './ps-feature-card';
5
- import { PsSectionIntro } from './ps-section-intro';
3
+ import { PsWebsiteSectionConfig } from '../constants/ps-website-section-config.js';
4
+ import { PsFeatureCard } from './ps-feature-card.js';
5
+ import { PsSectionIntro } from './ps-section-intro.js';
6
6
  export const PsFeatureGrid = ({ description, eyebrow, features, title }) => (_jsxs("div", { className: 'flex w-full flex-col items-center gap-8', children: [_jsx(PsSectionIntro, { description: description, eyebrow: eyebrow, title: title }), _jsx(PsStaggerContainer, { className: 'grid w-full grid-cols-1 gap-5 md:grid-cols-3', staggerDelay: PsWebsiteSectionConfig.staggerDelay, children: features.map((feature) => (_jsx(PsStaggerItem, { children: _jsx(PsFeatureCard, { description: feature.description, iconKey: feature.iconKey, title: feature.title }) }, feature.title))) })] }));