@donotdev/ui 0.0.9 → 0.0.11
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/dist/crud/components/EntityCardList.d.ts +16 -0
- package/dist/crud/components/EntityCardList.d.ts.map +1 -0
- package/dist/crud/components/EntityCardList.js +175 -0
- package/dist/crud/components/EntityDisplayRenderer.d.ts +13 -21
- package/dist/crud/components/EntityDisplayRenderer.d.ts.map +1 -1
- package/dist/crud/components/EntityDisplayRenderer.js +138 -23
- package/dist/crud/components/EntityFormRenderer.d.ts +18 -0
- package/dist/crud/components/EntityFormRenderer.d.ts.map +1 -0
- package/dist/crud/components/EntityFormRenderer.js +275 -0
- package/dist/crud/components/EntityList.d.ts +14 -0
- package/dist/crud/components/EntityList.d.ts.map +1 -0
- package/dist/crud/components/EntityList.js +201 -0
- package/dist/crud/components/index.d.ts +7 -5
- package/dist/crud/components/index.d.ts.map +1 -1
- package/dist/crud/components/index.js +6 -5
- package/dist/dndev.css +467 -143
- package/dist/index.js +4 -64
- package/dist/internal/common/RouteErrorFallback.d.ts.map +1 -1
- package/dist/internal/common/RouteErrorFallback.js +1 -2
- package/dist/internal/layout/DnDevLayout.d.ts.map +1 -1
- package/dist/internal/layout/DnDevLayout.js +3 -2
- package/dist/internal/layout/components/AutoMetaTags.d.ts.map +1 -1
- package/dist/internal/layout/components/AutoMetaTags.js +36 -6
- package/dist/internal/layout/components/NextJsAutoMetaTags.d.ts.map +1 -1
- package/dist/internal/layout/components/NextJsAutoMetaTags.js +38 -10
- package/dist/internal/layout/components/footer/FooterBranding.d.ts +0 -2
- package/dist/internal/layout/components/footer/FooterBranding.d.ts.map +1 -1
- package/dist/internal/layout/components/footer/FooterBranding.js +4 -8
- package/dist/internal/layout/components/footer/FooterCopyright.d.ts +0 -2
- package/dist/internal/layout/components/footer/FooterCopyright.d.ts.map +1 -1
- package/dist/internal/layout/components/footer/FooterCopyright.js +2 -6
- package/dist/internal/layout/config/presets/moolti.js +2 -2
- package/dist/internal/layout/zones/DnDevFooter.d.ts.map +1 -1
- package/dist/internal/layout/zones/DnDevFooter.js +2 -2
- package/dist/routing/GoTo.d.ts +1 -1
- package/dist/routing/GoTo.d.ts.map +1 -1
- package/dist/routing/GoTo.js +1 -1
- package/dist/routing/hooks/hooks.next.js +1 -1
- package/dist/routing/hooks/hooks.vite.js +1 -1
- package/dist/routing/hooks/useFormNavigationBlocker.d.ts +14 -0
- package/dist/routing/hooks/useFormNavigationBlocker.d.ts.map +1 -0
- package/dist/routing/hooks/useFormNavigationBlocker.js +42 -0
- package/dist/routing/hooks/useNavigate.next.d.ts +1 -1
- package/dist/routing/hooks/useNavigate.next.d.ts.map +1 -1
- package/dist/routing/hooks/useNavigate.next.js +7 -1
- package/dist/routing/hooks/useNavigate.vite.d.ts +1 -1
- package/dist/routing/hooks/useNavigate.vite.d.ts.map +1 -1
- package/dist/routing/hooks/useNavigate.vite.js +7 -1
- package/dist/styles/index.css +467 -143
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/useFormStoreSafe.d.ts +59 -0
- package/dist/utils/useFormStoreSafe.d.ts.map +1 -0
- package/dist/utils/useFormStoreSafe.js +115 -0
- package/package.json +12 -12
- package/dist/crud/components/DisplayFieldRenderer.d.ts +0 -26
- package/dist/crud/components/DisplayFieldRenderer.d.ts.map +0 -1
- package/dist/crud/components/DisplayFieldRenderer.js +0 -107
- package/dist/crud/components/fields/display/AvatarFieldDisplay.d.ts +0 -23
- package/dist/crud/components/fields/display/AvatarFieldDisplay.d.ts.map +0 -1
- package/dist/crud/components/fields/display/AvatarFieldDisplay.js +0 -38
- package/dist/crud/components/fields/display/BadgeFieldDisplay.d.ts +0 -21
- package/dist/crud/components/fields/display/BadgeFieldDisplay.d.ts.map +0 -1
- package/dist/crud/components/fields/display/BadgeFieldDisplay.js +0 -31
- package/dist/crud/components/fields/display/ButtonFieldDisplay.d.ts +0 -29
- package/dist/crud/components/fields/display/ButtonFieldDisplay.d.ts.map +0 -1
- package/dist/crud/components/fields/display/ButtonFieldDisplay.js +0 -12
- package/dist/crud/components/fields/display/CheckboxFieldDisplay.d.ts +0 -21
- package/dist/crud/components/fields/display/CheckboxFieldDisplay.d.ts.map +0 -1
- package/dist/crud/components/fields/display/CheckboxFieldDisplay.js +0 -27
- package/dist/crud/components/fields/display/DateFieldDisplay.d.ts +0 -24
- package/dist/crud/components/fields/display/DateFieldDisplay.d.ts.map +0 -1
- package/dist/crud/components/fields/display/DateFieldDisplay.js +0 -42
- package/dist/crud/components/fields/display/DropdownDisplay.d.ts +0 -21
- package/dist/crud/components/fields/display/DropdownDisplay.d.ts.map +0 -1
- package/dist/crud/components/fields/display/DropdownDisplay.js +0 -25
- package/dist/crud/components/fields/display/FileFieldDisplay.d.ts +0 -21
- package/dist/crud/components/fields/display/FileFieldDisplay.d.ts.map +0 -1
- package/dist/crud/components/fields/display/FileFieldDisplay.js +0 -25
- package/dist/crud/components/fields/display/GeoPointFieldDisplay.d.ts +0 -25
- package/dist/crud/components/fields/display/GeoPointFieldDisplay.d.ts.map +0 -1
- package/dist/crud/components/fields/display/GeoPointFieldDisplay.js +0 -25
- package/dist/crud/components/fields/display/HiddenFieldDisplay.d.ts +0 -30
- package/dist/crud/components/fields/display/HiddenFieldDisplay.d.ts.map +0 -1
- package/dist/crud/components/fields/display/HiddenFieldDisplay.js +0 -12
- package/dist/crud/components/fields/display/ImageFieldDisplay.d.ts +0 -24
- package/dist/crud/components/fields/display/ImageFieldDisplay.d.ts.map +0 -1
- package/dist/crud/components/fields/display/ImageFieldDisplay.js +0 -38
- package/dist/crud/components/fields/display/LinkFieldDisplay.d.ts +0 -22
- package/dist/crud/components/fields/display/LinkFieldDisplay.d.ts.map +0 -1
- package/dist/crud/components/fields/display/LinkFieldDisplay.js +0 -47
- package/dist/crud/components/fields/display/MapFieldDisplay.d.ts +0 -25
- package/dist/crud/components/fields/display/MapFieldDisplay.d.ts.map +0 -1
- package/dist/crud/components/fields/display/MapFieldDisplay.js +0 -25
- package/dist/crud/components/fields/display/MultiDropdownDisplay.d.ts +0 -22
- package/dist/crud/components/fields/display/MultiDropdownDisplay.d.ts.map +0 -1
- package/dist/crud/components/fields/display/MultiDropdownDisplay.js +0 -25
- package/dist/crud/components/fields/display/MultiInputTextFieldDisplay.d.ts +0 -22
- package/dist/crud/components/fields/display/MultiInputTextFieldDisplay.d.ts.map +0 -1
- package/dist/crud/components/fields/display/MultiInputTextFieldDisplay.js +0 -25
- package/dist/crud/components/fields/display/NumberFieldDisplay.d.ts +0 -24
- package/dist/crud/components/fields/display/NumberFieldDisplay.d.ts.map +0 -1
- package/dist/crud/components/fields/display/NumberFieldDisplay.js +0 -29
- package/dist/crud/components/fields/display/PasswordFieldDisplay.d.ts +0 -24
- package/dist/crud/components/fields/display/PasswordFieldDisplay.d.ts.map +0 -1
- package/dist/crud/components/fields/display/PasswordFieldDisplay.js +0 -31
- package/dist/crud/components/fields/display/PhoneNumberDisplay.d.ts +0 -22
- package/dist/crud/components/fields/display/PhoneNumberDisplay.d.ts.map +0 -1
- package/dist/crud/components/fields/display/PhoneNumberDisplay.js +0 -26
- package/dist/crud/components/fields/display/RadioFieldDisplay.d.ts +0 -22
- package/dist/crud/components/fields/display/RadioFieldDisplay.d.ts.map +0 -1
- package/dist/crud/components/fields/display/RadioFieldDisplay.js +0 -25
- package/dist/crud/components/fields/display/RangeFieldDisplay.d.ts +0 -22
- package/dist/crud/components/fields/display/RangeFieldDisplay.d.ts.map +0 -1
- package/dist/crud/components/fields/display/RangeFieldDisplay.js +0 -25
- package/dist/crud/components/fields/display/ReferenceFieldDisplay.d.ts +0 -22
- package/dist/crud/components/fields/display/ReferenceFieldDisplay.d.ts.map +0 -1
- package/dist/crud/components/fields/display/ReferenceFieldDisplay.js +0 -26
- package/dist/crud/components/fields/display/RichTextDisplay.d.ts +0 -25
- package/dist/crud/components/fields/display/RichTextDisplay.d.ts.map +0 -1
- package/dist/crud/components/fields/display/RichTextDisplay.js +0 -104
- package/dist/crud/components/fields/display/TextAreaDisplay.d.ts +0 -22
- package/dist/crud/components/fields/display/TextAreaDisplay.d.ts.map +0 -1
- package/dist/crud/components/fields/display/TextAreaDisplay.js +0 -25
- package/dist/crud/components/fields/display/TextFieldDisplay.d.ts +0 -42
- package/dist/crud/components/fields/display/TextFieldDisplay.d.ts.map +0 -1
- package/dist/crud/components/fields/display/TextFieldDisplay.js +0 -97
- package/dist/crud/components/fields/display/TimestampFieldDisplay.d.ts +0 -22
- package/dist/crud/components/fields/display/TimestampFieldDisplay.d.ts.map +0 -1
- package/dist/crud/components/fields/display/TimestampFieldDisplay.js +0 -33
- package/dist/crud/components/fields/display/index.d.ts +0 -32
- package/dist/crud/components/fields/display/index.d.ts.map +0 -1
- package/dist/crud/components/fields/display/index.js +0 -32
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RouteErrorFallback.d.ts","sourceRoot":"","sources":["../../../src/internal/common/RouteErrorFallback.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"RouteErrorFallback.d.ts","sourceRoot":"","sources":["../../../src/internal/common/RouteErrorFallback.tsx"],"names":[],"mappings":"AAqCA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAE3C,UAAU,uBAAuB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,EAAE,aAAa,CAAC,uBAAuB,CAyPrE,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
|
|
@@ -14,7 +14,6 @@ import { useEffect, useState } from 'react';
|
|
|
14
14
|
import { Button, BUTTON_VARIANT, Card, CARD_VARIANT, Alert, ALERT_VARIANT, Stack, } from '@donotdev/components';
|
|
15
15
|
import { handleError, useTranslation } from '@donotdev/core';
|
|
16
16
|
import { useLocation, useBack } from '@donotdev/ui/routing/hooks';
|
|
17
|
-
import { PageContainer } from '../../components/layout/PageContainer';
|
|
18
17
|
// Platform-specific hooks via conditional exports
|
|
19
18
|
import { Link } from '../../routing/Link';
|
|
20
19
|
/**
|
|
@@ -95,7 +94,7 @@ export const RouteErrorFallback = ({ error, resetError, componentStack, eventId,
|
|
|
95
94
|
}
|
|
96
95
|
}
|
|
97
96
|
};
|
|
98
|
-
return (_jsx(
|
|
97
|
+
return (_jsx("div", { className: "dndev-container", "data-variant": "standard", "data-centered": "true", children: _jsx(Card, { variant: CARD_VARIANT.DEFAULT, elevated: true, className: "dndev-w-full", style: { maxWidth: '65ch', margin: '0 auto' }, children: _jsxs(Stack, { gap: "large", align: "center", className: "dndev-text-center", children: [_jsx(Stack, { align: "center", justify: "center", style: {
|
|
99
98
|
width: '5rem',
|
|
100
99
|
height: '5rem',
|
|
101
100
|
backgroundColor: 'rgba(220, 38, 38, 0.1)',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DnDevLayout.d.ts","sourceRoot":"","sources":["../../../src/internal/layout/DnDevLayout.tsx"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AAEH,OAAO,EAQL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EAIV,YAAY,EACb,MAAM,gBAAgB,CAAC;AA0CxB,UAAU,yBAAyB;IACjC,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,WAAW,GAAI,kCAIzB,yBAAyB,
|
|
1
|
+
{"version":3,"file":"DnDevLayout.d.ts","sourceRoot":"","sources":["../../../src/internal/layout/DnDevLayout.tsx"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AAEH,OAAO,EAQL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EAIV,YAAY,EACb,MAAM,gBAAgB,CAAC;AA0CxB,UAAU,yBAAyB;IACjC,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,WAAW,GAAI,kCAIzB,yBAAyB,4CAwT3B,CAAC"}
|
|
@@ -66,8 +66,9 @@ export const DnDevLayout = ({ children, layout, className, }) => {
|
|
|
66
66
|
const app = useAppConfig('app');
|
|
67
67
|
// Subscribe to language changes for zone re-renders
|
|
68
68
|
useTranslation('dndev');
|
|
69
|
-
//
|
|
69
|
+
// Layout config props
|
|
70
70
|
const breadcrumbs = layout?.breadcrumbs ?? 'smart';
|
|
71
|
+
const footerMode = layout?.footerMode;
|
|
71
72
|
// Route detection for animations
|
|
72
73
|
const location = useLocation();
|
|
73
74
|
const pathname = location.pathname;
|
|
@@ -246,5 +247,5 @@ export const DnDevLayout = ({ children, layout, className, }) => {
|
|
|
246
247
|
};
|
|
247
248
|
startTransition(updateDOM);
|
|
248
249
|
}, [effectivePreset]);
|
|
249
|
-
return (_jsxs("div", { className: cn('dndev-layout', className), children: [resolvedHeader, resolvedSidebar, _jsxs("main", { ref: mainRef, role: "main", className: "main", children: [breadcrumbs !== 'never' && (_jsx("div", { className: "breadcrumbs-container", children: _jsx(Breadcrumbs, { variant: breadcrumbs === 'always' ? 'default' : 'smart' }) })), isDev() && (_jsx(Suspense, { fallback: null, children: _jsx(DebugTools, {}) })),
|
|
250
|
+
return (_jsxs("div", { className: cn('dndev-layout', className), "data-footer-mode": footerMode, children: [resolvedHeader, resolvedSidebar, _jsxs("main", { ref: mainRef, role: "main", className: "main", children: [breadcrumbs !== 'never' && (_jsx("div", { className: "breadcrumbs-container", children: _jsx(Breadcrumbs, { variant: breadcrumbs === 'always' ? 'default' : 'smart' }) })), isDev() && (_jsx(Suspense, { fallback: null, children: _jsx(DebugTools, {}) })), content] }), resolvedFooter, resolvedMergedBar, _jsx(GoToWrapper, {})] }));
|
|
250
251
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutoMetaTags.d.ts","sourceRoot":"","sources":["../../../../src/internal/layout/components/AutoMetaTags.tsx"],"names":[],"mappings":"AA0DA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"AutoMetaTags.d.ts","sourceRoot":"","sources":["../../../../src/internal/layout/components/AutoMetaTags.tsx"],"names":[],"mappings":"AA0DA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAyE3C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,YAAY,EAAE,aAoN1B,CAAC;AAEF;;;GAGG;AAEH,eAAe,YAAY,CAAC"}
|
|
@@ -49,12 +49,34 @@ import { useMemo } from 'react';
|
|
|
49
49
|
import { Helmet } from 'react-helmet-async';
|
|
50
50
|
import { useTranslation, useIsClient } from '@donotdev/core';
|
|
51
51
|
import { getDndevConfig } from '@donotdev/core';
|
|
52
|
-
import { useSeoConfig, useAppConfig } from '@donotdev/core';
|
|
52
|
+
import { useSeoConfig, useAppConfig, getPlatformEnvVar } from '@donotdev/core';
|
|
53
53
|
import { AssetResolver } from '@donotdev/ui';
|
|
54
54
|
// Platform-specific hooks via conditional exports
|
|
55
55
|
import { useLocation } from '@donotdev/ui/routing/hooks';
|
|
56
56
|
/**
|
|
57
|
-
*
|
|
57
|
+
* Match pathname against route path pattern (e.g. /cars/:id or /blog/:slug/comments/:id).
|
|
58
|
+
* Supports arbitrary nesting: any depth, any mix of static and :param segments.
|
|
59
|
+
*/
|
|
60
|
+
function matchRoutePath(pattern, pathname) {
|
|
61
|
+
const patternSegments = pattern.split('/').filter(Boolean);
|
|
62
|
+
const pathnameSegments = pathname.split('/').filter(Boolean);
|
|
63
|
+
if (patternSegments.length !== pathnameSegments.length)
|
|
64
|
+
return false;
|
|
65
|
+
for (let i = 0; i < patternSegments.length; i++) {
|
|
66
|
+
const segment = patternSegments[i];
|
|
67
|
+
const pathSegment = pathnameSegments[i];
|
|
68
|
+
if (segment === undefined || pathSegment === undefined)
|
|
69
|
+
return false;
|
|
70
|
+
if (segment.startsWith(':'))
|
|
71
|
+
continue; // param matches any value
|
|
72
|
+
if (segment !== pathSegment)
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get route metadata from window object (injected by build system).
|
|
79
|
+
* Resolves dynamic routes by pattern (e.g. /cars/:id) so PageMeta (including namespace) is used for SEO.
|
|
58
80
|
*/
|
|
59
81
|
function getRouteMetadata(pathname, isClient) {
|
|
60
82
|
if (!isClient)
|
|
@@ -63,12 +85,20 @@ function getRouteMetadata(pathname, isClient) {
|
|
|
63
85
|
const config = getDndevConfig();
|
|
64
86
|
const routeData = config?.routes?.mapping;
|
|
65
87
|
if (routeData && Array.isArray(routeData)) {
|
|
66
|
-
|
|
67
|
-
|
|
88
|
+
// 1. Exact match (static routes)
|
|
89
|
+
let route = routeData.find((r) => r.path === pathname);
|
|
90
|
+
if (route)
|
|
91
|
+
return route.meta;
|
|
92
|
+
// 2. Pattern match (dynamic routes: /cars/:id, /blog/:slug/comments/:id, etc.)
|
|
93
|
+
const dynamicRoutes = routeData.filter((r) => typeof r.path === 'string' && r.path.includes(':'));
|
|
94
|
+
const matches = dynamicRoutes.filter((r) => matchRoutePath(r.path, pathname));
|
|
95
|
+
// Prefer most specific (longest path) so /blog/:slug/comments wins over /blog/:slug for /blog/a/comments
|
|
96
|
+
if (matches.length > 0) {
|
|
97
|
+
route = matches.reduce((best, r) => r.path.length > best.path.length ? r : best);
|
|
68
98
|
return route.meta;
|
|
69
99
|
}
|
|
70
100
|
}
|
|
71
|
-
//
|
|
101
|
+
// Fallback: first path segment as namespace (e.g. /cars/abc123 → ns: cars)
|
|
72
102
|
const segments = pathname.split('/').filter(Boolean);
|
|
73
103
|
if (segments.length > 0) {
|
|
74
104
|
return { ns: segments[0] };
|
|
@@ -97,7 +127,7 @@ export const AutoMetaTags = () => {
|
|
|
97
127
|
return null;
|
|
98
128
|
const { defaultNamespace = 'home', defaultImage, twitterHandle, staticTags = {}, } = seoConfig;
|
|
99
129
|
const siteName = useAppConfig('name');
|
|
100
|
-
const baseUrl =
|
|
130
|
+
const baseUrl = getPlatformEnvVar('APP_URL') || '';
|
|
101
131
|
// Get route metadata
|
|
102
132
|
const routeMeta = getRouteMetadata(location.pathname, isClient);
|
|
103
133
|
// Memoize computed values for performance
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NextJsAutoMetaTags.d.ts","sourceRoot":"","sources":["../../../../src/internal/layout/components/NextJsAutoMetaTags.tsx"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAE3C,UAAU,cAAc;IACtB,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,uCAAuC;IACvC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mCAAmC;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kCAAkC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC;AAED,UAAU,aAAa;IACrB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,UAAU,uBAAuB;IAC/B,kCAAkC;IAClC,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,8BAA8B;IAC9B,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,qBAAqB;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qBAAqB;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC;AA+
|
|
1
|
+
{"version":3,"file":"NextJsAutoMetaTags.d.ts","sourceRoot":"","sources":["../../../../src/internal/layout/components/NextJsAutoMetaTags.tsx"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAE3C,UAAU,cAAc;IACtB,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,uCAAuC;IACvC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mCAAmC;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kCAAkC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC;AAED,UAAU,aAAa;IACrB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,UAAU,uBAAuB;IAC/B,kCAAkC;IAClC,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,8BAA8B;IAC9B,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,qBAAqB;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qBAAqB;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC;AA+DD;;;;;;;;GAQG;AACH,QAAA,MAAM,kBAAkB,EAAE,aAAa,CAAC,uBAAuB,CA8L9D,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
|
|
@@ -17,9 +17,31 @@ import { usePathname } from 'next/navigation';
|
|
|
17
17
|
import { useMemo } from 'react';
|
|
18
18
|
import { Helmet } from 'react-helmet-async';
|
|
19
19
|
import { useTranslation, getDndevConfig, useIsClient } from '@donotdev/core';
|
|
20
|
-
import { useAppConfig } from '@donotdev/core';
|
|
20
|
+
import { useAppConfig, getPlatformEnvVar } from '@donotdev/core';
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
22
|
+
* Match pathname against route path pattern (e.g. /cars/:id or /blog/:slug/comments/:id).
|
|
23
|
+
* Supports arbitrary nesting: any depth, any mix of static and :param segments.
|
|
24
|
+
*/
|
|
25
|
+
function matchRoutePath(pattern, pathname) {
|
|
26
|
+
const patternSegments = pattern.split('/').filter(Boolean);
|
|
27
|
+
const pathnameSegments = pathname.split('/').filter(Boolean);
|
|
28
|
+
if (patternSegments.length !== pathnameSegments.length)
|
|
29
|
+
return false;
|
|
30
|
+
for (let i = 0; i < patternSegments.length; i++) {
|
|
31
|
+
const segment = patternSegments[i];
|
|
32
|
+
const pathSegment = pathnameSegments[i];
|
|
33
|
+
if (segment === undefined || pathSegment === undefined)
|
|
34
|
+
return false;
|
|
35
|
+
if (segment.startsWith(':'))
|
|
36
|
+
continue; // param matches any value
|
|
37
|
+
if (segment !== pathSegment)
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get route metadata from config (injected by build system).
|
|
44
|
+
* Resolves dynamic routes by pattern so PageMeta (including namespace) is used for SEO.
|
|
23
45
|
*/
|
|
24
46
|
function getRouteMetadata(pathname, isClient) {
|
|
25
47
|
if (!isClient)
|
|
@@ -28,16 +50,22 @@ function getRouteMetadata(pathname, isClient) {
|
|
|
28
50
|
const config = getDndevConfig();
|
|
29
51
|
const routeData = config?.routes?.mapping;
|
|
30
52
|
if (routeData && Array.isArray(routeData)) {
|
|
31
|
-
|
|
53
|
+
// 1. Exact match (static routes)
|
|
54
|
+
let route = routeData.find((r) => r.path === pathname);
|
|
32
55
|
if (route && route.meta) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
56
|
+
return { ...route.meta, ns: route.meta.namespace };
|
|
57
|
+
}
|
|
58
|
+
// 2. Pattern match (dynamic routes: /cars/:id, /blog/:slug/comments/:id, etc.)
|
|
59
|
+
const dynamicRoutes = routeData.filter((r) => typeof r.path === 'string' && r.path.includes(':'));
|
|
60
|
+
const matches = dynamicRoutes.filter((r) => matchRoutePath(r.path, pathname));
|
|
61
|
+
if (matches.length > 0) {
|
|
62
|
+
const best = matches.reduce((a, b) => b.path.length > a.path.length ? b : a);
|
|
63
|
+
if (best.meta) {
|
|
64
|
+
return { ...best.meta, ns: best.meta.namespace };
|
|
65
|
+
}
|
|
38
66
|
}
|
|
39
67
|
}
|
|
40
|
-
//
|
|
68
|
+
// Fallback: first path segment as namespace
|
|
41
69
|
const segments = pathname.split('/').filter(Boolean);
|
|
42
70
|
if (segments.length > 0) {
|
|
43
71
|
return { ns: segments[0] };
|
|
@@ -64,7 +92,7 @@ const NextJsAutoMetaTags = ({ config = {}, routeMeta: routeMetaOverride, title:
|
|
|
64
92
|
const { t } = useTranslation(namespaces);
|
|
65
93
|
const { defaultNamespace = 'home', defaultImage = '/og-image.png', twitterHandle, defaultAuthor, staticTags = {}, } = config;
|
|
66
94
|
const siteName = config.siteName || useAppConfig('name');
|
|
67
|
-
const baseUrl =
|
|
95
|
+
const baseUrl = getPlatformEnvVar('APP_URL') || '';
|
|
68
96
|
// Get route metadata
|
|
69
97
|
const routeMeta = getRouteMetadata(pathname || '/', isClient);
|
|
70
98
|
const finalRouteMeta = { ...routeMeta, ...routeMetaOverride };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FooterBranding.d.ts","sourceRoot":"","sources":["../../../../../src/internal/layout/components/footer/FooterBranding.tsx"],"names":[],"mappings":"AAeA,MAAM,WAAW,mBAAmB;IAClC,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"FooterBranding.d.ts","sourceRoot":"","sources":["../../../../../src/internal/layout/components/footer/FooterBranding.tsx"],"names":[],"mappings":"AAeA,MAAM,WAAW,mBAAmB;IAClC,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,aAAa,CAAC,mBAAmB,CA8BnE,CAAC;AAEF,eAAe,cAAc,CAAC"}
|
|
@@ -9,7 +9,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
9
9
|
* @author AMBROISE PARK Consulting
|
|
10
10
|
*/
|
|
11
11
|
import { ExternalLink } from 'lucide-react';
|
|
12
|
-
import { useAppConfig } from '@donotdev/core';
|
|
12
|
+
import { useAppConfig, getPlatformEnvVar } from '@donotdev/core';
|
|
13
13
|
/**
|
|
14
14
|
* FooterBranding - Framework branding link (power move)
|
|
15
15
|
*
|
|
@@ -20,20 +20,16 @@ import { useAppConfig } from '@donotdev/core';
|
|
|
20
20
|
* @since 0.0.1
|
|
21
21
|
* @author AMBROISE PARK Consulting
|
|
22
22
|
*/
|
|
23
|
-
export const FooterBranding = ({ className,
|
|
24
|
-
const appUrl =
|
|
23
|
+
export const FooterBranding = ({ className, }) => {
|
|
24
|
+
const appUrl = getPlatformEnvVar('APP_URL');
|
|
25
25
|
const isFrameworkSite = appUrl === 'https://donotdev.com';
|
|
26
|
-
const sizeStyles = {
|
|
27
|
-
xs: { fontSize: 'var(--font-size-xs)' },
|
|
28
|
-
sm: { fontSize: 'var(--font-size-sm)' },
|
|
29
|
-
};
|
|
30
26
|
return (_jsxs("a", { href: isFrameworkSite
|
|
31
27
|
? 'https://www.ambroise-park.com'
|
|
32
28
|
: 'https://donotdev.com', target: "_blank", rel: "noopener noreferrer", className: className, style: {
|
|
33
29
|
display: 'inline-flex',
|
|
34
30
|
alignItems: 'center',
|
|
35
31
|
gap: 'var(--gap-sm)',
|
|
36
|
-
|
|
32
|
+
fontSize: 'var(--font-size-xs)',
|
|
37
33
|
}, children: [isFrameworkSite ? 'AMBROISE-PARK' : 'DoNotDev', _jsx(ExternalLink, { style: { width: '12px', height: '12px' }, "aria-hidden": "true" })] }));
|
|
38
34
|
};
|
|
39
35
|
export default FooterBranding;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FooterCopyright.d.ts","sourceRoot":"","sources":["../../../../../src/internal/layout/components/footer/FooterCopyright.tsx"],"names":[],"mappings":"AAcA,MAAM,WAAW,oBAAoB;IACnC,wDAAwD;IACxD,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"FooterCopyright.d.ts","sourceRoot":"","sources":["../../../../../src/internal/layout/components/footer/FooterCopyright.tsx"],"names":[],"mappings":"AAcA,MAAM,WAAW,oBAAoB;IACnC,wDAAwD;IACxD,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,aAAa,CAAC,oBAAoB,CAqBrE,CAAC;AAEF,eAAe,eAAe,CAAC"}
|
|
@@ -20,18 +20,14 @@ import { useTranslation, useAppConfig } from '@donotdev/core';
|
|
|
20
20
|
* @since 0.0.1
|
|
21
21
|
* @author AMBROISE PARK Consulting
|
|
22
22
|
*/
|
|
23
|
-
export const FooterCopyright = ({ appName, year = new Date().getFullYear(), className,
|
|
23
|
+
export const FooterCopyright = ({ appName, year = new Date().getFullYear(), className, }) => {
|
|
24
24
|
const { t } = useTranslation('dndev');
|
|
25
25
|
const app = useAppConfig('app');
|
|
26
26
|
const resolvedAppName = appName || app?.name || 'App';
|
|
27
|
-
const sizeStyles = {
|
|
28
|
-
xs: { fontSize: 'var(--font-size-xs)' },
|
|
29
|
-
sm: { fontSize: 'var(--font-size-sm)' },
|
|
30
|
-
};
|
|
31
27
|
return (_jsxs("span", { className: cn(className), style: {
|
|
32
28
|
flexShrink: 0,
|
|
33
29
|
color: 'var(--muted-foreground)',
|
|
34
|
-
|
|
30
|
+
fontSize: 'var(--font-size-xs)',
|
|
35
31
|
}, children: ["\u00A9 ", year, " ", resolvedAppName, ". ", t('footer.legal.allRightsReserved')] }));
|
|
36
32
|
};
|
|
37
33
|
export default FooterCopyright;
|
|
@@ -32,7 +32,7 @@ export const mooltiPreset = {
|
|
|
32
32
|
sidebar: {
|
|
33
33
|
top: () => _jsx(AppBranding, { display: DISPLAY.AUTO }),
|
|
34
34
|
content: () => (_jsxs(_Fragment, { children: [_jsx(GoTo, { display: DISPLAY.AUTO }), _jsx(DnDevNavigationMenu, { vertical: true, display: DISPLAY.AUTO })] })),
|
|
35
|
-
bottom: () => (_jsxs(Stack, { direction: "column", gap: "tight", align: "stretch", children: [_jsx(AuthHeader, { display: DISPLAY.AUTO }), _jsx(LanguageSelector, { display: DISPLAY.AUTO }), _jsx(ThemeToggle, { display: DISPLAY.AUTO }), _jsx(FooterCopyright, {
|
|
35
|
+
bottom: () => (_jsxs(Stack, { direction: "column", gap: "tight", align: "stretch", children: [_jsx(AuthHeader, { display: DISPLAY.AUTO }), _jsx(LanguageSelector, { display: DISPLAY.AUTO }), _jsx(ThemeToggle, { display: DISPLAY.AUTO }), _jsx(FooterCopyright, {})] })),
|
|
36
36
|
defaultWidth: 256,
|
|
37
37
|
minWidth: 48,
|
|
38
38
|
maxWidth: 400,
|
|
@@ -48,7 +48,7 @@ export const mooltiPreset = {
|
|
|
48
48
|
_jsxs(_Fragment, { children: [_jsx(GoTo, { display: DISPLAY.AUTO }), _jsx(DnDevNavigationMenu, { vertical: true, display: DISPLAY.AUTO })] })),
|
|
49
49
|
bottom: () => (
|
|
50
50
|
// sidebar.bottom → bottom (Auth, Language, Theme, Footer)
|
|
51
|
-
_jsxs(Stack, { direction: "column", gap: "tight", align: "stretch", children: [_jsx(AuthHeader, { display: DISPLAY.AUTO }), _jsx(LanguageSelector, { display: DISPLAY.AUTO }), _jsx(ThemeToggle, { display: DISPLAY.AUTO }), _jsx(FooterCopyright, {
|
|
51
|
+
_jsxs(Stack, { direction: "column", gap: "tight", align: "stretch", children: [_jsx(AuthHeader, { display: DISPLAY.AUTO }), _jsx(LanguageSelector, { display: DISPLAY.AUTO }), _jsx(ThemeToggle, { display: DISPLAY.AUTO }), _jsx(FooterCopyright, {})] })),
|
|
52
52
|
},
|
|
53
53
|
},
|
|
54
54
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DnDevFooter.d.ts","sourceRoot":"","sources":["../../../../src/internal/layout/zones/DnDevFooter.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAUlD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,MAAM,WAAW,gBAAgB;IAC/B,uCAAuC;IACvC,GAAG,CAAC,EAAE,WAAW,CAAC;CACnB;AAED;;;;;;;;;;;;;GAaG;AACH,iBAAS,oBAAoB,CAAC,EAAE,GAAQ,EAAE,EAAE,gBAAgB,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"DnDevFooter.d.ts","sourceRoot":"","sources":["../../../../src/internal/layout/zones/DnDevFooter.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAUlD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,MAAM,WAAW,gBAAgB;IAC/B,uCAAuC;IACvC,GAAG,CAAC,EAAE,WAAW,CAAC;CACnB;AAED;;;;;;;;;;;;;GAaG;AACH,iBAAS,oBAAoB,CAAC,EAAE,GAAQ,EAAE,EAAE,gBAAgB,GAAG,SAAS,CA8EvE;AAED,eAAO,MAAM,WAAW,kEAA6B,CAAC"}
|
|
@@ -49,9 +49,9 @@ function DnDevFooterComponent({ app = {} }) {
|
|
|
49
49
|
: copyrightConfig;
|
|
50
50
|
// Desktop/Wide: 2-zone layout [Copyright] | [Links + DoNotDev]
|
|
51
51
|
if (isLaptopOrDesktop) {
|
|
52
|
-
return (_jsx("footer", { role: "contentinfo", className: "footer", children: _jsxs(Stack, { direction: "row", align: "center", justify: "between", gap: "none", children: [showCopyright && (_jsx("div", { className: "dndev-flex dndev-justify-start", children: _jsx("span", { className: "footer-copyright", children: copyrightText }) })), _jsx("div", { className: "dndev-flex dndev-justify-end", children: _jsxs(Stack, { direction: "row", gap: "medium", align: "center", children: [links.map((link) => (_jsx(Link, { path: link.path, children: maybeTranslate(t, link.label) }, link.path))), _jsx(FooterBranding, {})] }) })] }) }));
|
|
52
|
+
return (_jsx("footer", { role: "contentinfo", className: "footer", children: _jsxs(Stack, { direction: "row", align: "center", justify: "between", gap: "none", children: [showCopyright && (_jsx("div", { className: "dndev-flex dndev-justify-start", children: _jsx("span", { className: "footer-copyright", children: copyrightText }) })), _jsx("div", { className: "dndev-flex dndev-justify-end", children: _jsxs(Stack, { direction: "row", gap: "medium", align: "center", children: [links.map((link) => (_jsx(Link, { path: link.path, style: { fontSize: 'var(--font-size-xs)' }, children: maybeTranslate(t, link.label) }, link.path))), _jsx(FooterBranding, {})] }) })] }) }));
|
|
53
53
|
}
|
|
54
54
|
// Mobile/Tablet: stacked layout
|
|
55
|
-
return (_jsx("footer", { role: "contentinfo", className: "footer", children: _jsxs(Stack, { align: "center", gap: "tight", children: [showCopyright && (_jsx("span", { className: "footer-copyright", children: copyrightText })), _jsxs(Stack, { direction: "row", wrap: "wrap", gap: "tight", justify: "center", align: "center", children: [links.map((link) => (_jsx(Link, { path: link.path, children: maybeTranslate(t, link.label) }, link.path))), _jsx(FooterBranding, {})] })] }) }));
|
|
55
|
+
return (_jsx("footer", { role: "contentinfo", className: "footer", children: _jsxs(Stack, { align: "center", gap: "tight", children: [showCopyright && (_jsx("span", { className: "footer-copyright", children: copyrightText })), _jsxs(Stack, { direction: "row", wrap: "wrap", gap: "tight", justify: "center", align: "center", children: [links.map((link) => (_jsx(Link, { path: link.path, style: { fontSize: 'var(--font-size-xs)' }, children: maybeTranslate(t, link.label) }, link.path))), _jsx(FooterBranding, {})] })] }) }));
|
|
56
56
|
}
|
|
57
57
|
export const DnDevFooter = memo(DnDevFooterComponent);
|
package/dist/routing/GoTo.d.ts
CHANGED
|
@@ -16,6 +16,6 @@ export interface GoToProps {
|
|
|
16
16
|
* Simple button: Search icon + "Go to" label + keyboard shortcut.
|
|
17
17
|
* Opens command dialog on click.
|
|
18
18
|
*/
|
|
19
|
-
export declare const GoTo: ({ display, className, onOpen }: GoToProps) => import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
export declare const GoTo: ({ display, className, onOpen, }: GoToProps) => import("react/jsx-runtime").JSX.Element;
|
|
20
20
|
export default GoTo;
|
|
21
21
|
//# sourceMappingURL=GoTo.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GoTo.d.ts","sourceRoot":"","sources":["../../src/routing/GoTo.tsx"],"names":[],"mappings":"AAaA,OAAO,EAA0B,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAGvE,MAAM,WAAW,SAAS;IACxB;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,OAAO,OAAO,CAAC,CAAC,MAAM,OAAO,OAAO,CAAC,CAAC;IACjD,2BAA2B;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6FAA6F;IAC7F,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;;;GAKG;AACH,eAAO,MAAM,IAAI,GAAI,
|
|
1
|
+
{"version":3,"file":"GoTo.d.ts","sourceRoot":"","sources":["../../src/routing/GoTo.tsx"],"names":[],"mappings":"AAaA,OAAO,EAA0B,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAGvE,MAAM,WAAW,SAAS;IACxB;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,OAAO,OAAO,CAAC,CAAC,MAAM,OAAO,OAAO,CAAC,CAAC;IACjD,2BAA2B;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6FAA6F;IAC7F,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;;;GAKG;AACH,eAAO,MAAM,IAAI,GAAI,iCAIlB,SAAS,4CAsCX,CAAC;AAEF,eAAe,IAAI,CAAC"}
|
package/dist/routing/GoTo.js
CHANGED
|
@@ -17,7 +17,7 @@ import { useOverlayStore, useTranslation } from '@donotdev/core';
|
|
|
17
17
|
* Simple button: Search icon + "Go to" label + keyboard shortcut.
|
|
18
18
|
* Opens command dialog on click.
|
|
19
19
|
*/
|
|
20
|
-
export const GoTo = ({ display = DISPLAY.AUTO, className, onOpen }) => {
|
|
20
|
+
export const GoTo = ({ display = DISPLAY.AUTO, className, onOpen, }) => {
|
|
21
21
|
const { t } = useTranslation('dndev');
|
|
22
22
|
const openCommandDialog = useOverlayStore((state) => state.openCommandDialog);
|
|
23
23
|
// Platform-aware shortcut (Mac: ⌘K, others: Ctrl+K)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use client";import{useRouter as
|
|
1
|
+
"use client";import{useRouter as h}from"next/navigation";import{useCallback as p}from"react";import{isClient as F,useOverlayStore as w}from"@donotdev/core";import*as D from"@donotdev/crud";var m=D?.useFormStore,A={forms:{},startSubmit:()=>{},setUploading:()=>{},setValidating:()=>{},setSubmitting:()=>{},setSuccess:()=>{},setError:()=>{},reset:()=>{},cleanup:()=>{},setIsDirty:()=>{},hasDirtyForms:()=>!1,getDirtyFormIds:()=>[],getStatus:()=>"idle",isLoading:()=>!1,getUploadProgress:()=>0,getError:()=>null,getIsDirty:()=>!1};function x(e){return m?m(e):e(A)}x.getState=()=>m&&typeof m.getState=="function"?m.getState():A;async function T(e="You have unsaved changes. Discard them?"){return x.getState().hasDirtyForms()&&typeof window<"u"&&window.confirm?window.confirm(e):!0}function g(){let e=h(),t=w(r=>r.closeAll);return p(async(r,u)=>{if(r==="back")return t(),e.back();if(await T()){if(t(),u?.preserveScroll&&F()){let o=window.scrollY;u?.replace?e.replace(r):e.push(r),requestAnimationFrame(()=>{window.scrollTo({top:o,behavior:"auto"})});return}return u?.replace?e.replace(r):e.push(r)}},[e,t])}function b(){let e=h();return p(()=>e.back(),[e])}function k(){let e=h();return p(()=>e.refresh(),[e])}function E(){let e=h();return p(t=>e.prefetch(t),[e])}import{usePathname as I,useSearchParams as N}from"next/navigation";function f(){let e=I(),t=N(),r=t.toString()?`?${t.toString()}`:"";return{pathname:e||"/",search:r,hash:"",state:null}}import{useParams as C}from"next/navigation";function R(){return C()}function G(e){let r=R()[e];if(typeof r=="string")return r;if(Array.isArray(r)&&r.length>0)return r[0]}import{useSearchParams as O}from"next/navigation";function S(){return O()}import{usePathname as U}from"next/navigation";function $(e){let t=U(),r=e.replace(/:[^/]+/g,"([^/]+)").replace(/\*/g,".*"),u=new RegExp(`^${r}$`),i=t.match(u);if(!i)return null;let o=e.match(/:[^/]+/g)?.map(n=>n.slice(1))||[],s={};return o.forEach((n,c)=>{s[n]=i[c+1]||""}),{params:s,pathname:t,pattern:e}}import{useCallback as y}from"react";function K(){let e=S(),t=g(),r=f(),u=y((s,n)=>{let c=new URLSearchParams(e.toString());c.set(s,n);let l=c.toString(),P=`${r.pathname||"/"}${l?`?${l}`:""}`;t(P)},[e,t,r.pathname]),i=y(s=>{let n=new URLSearchParams(e.toString());n.delete(s);let c=n.toString(),a=`${r.pathname||"/"}${c?`?${c}`:""}`;t(a)},[e,t,r.pathname]),o=y(()=>{let s=r.pathname||"/";t(s)},[t,r.pathname]);return{query:e,setQuery:u,removeQuery:i,clearQueries:o}}import{useMemo as Q}from"react";import{isClient as j,FEATURE_STATUS as q}from"@donotdev/core";import{useAuthConfig as Y}from"@donotdev/core";import*as _ from"@donotdev/auth";import{DEGRADED_AUTH_API as L}from"@donotdev/core";var v=_?.useAuth;function M(e){return L[e]}function d(e){return v?v(e):M(e)}function B(e={}){let{auth:t,redirectTo:r,condition:u}=e;if(!j())return{shouldRedirect:!1,redirectTo:null,isChecking:!1};let i=f(),o=Y(),s=d("user"),n=d("can"),c=d("status");return Q(()=>{if(c===q.INITIALIZING)return{shouldRedirect:!1,redirectTo:null,isChecking:!0};if(u){let a=u(s,c);return{shouldRedirect:a,redirectTo:a&&r||null,isChecking:!1}}if(t!==!1&&t!==void 0){if(!n)return{shouldRedirect:!1,redirectTo:null,isChecking:!1};if(!n.navigate(t)){let a=null;return typeof t=="object"&&t.required&&!s?i.search.includes("code=")||i.search.includes("state=")||i.search.includes("error=")?a=`${o.authRoute}${i.search}`:a=o.authRoute:typeof t=="object"&&t.role?a=o.roleRoute:typeof t=="object"&&t.tier?a=o.tierRoute:a=o.roleRoute,{shouldRedirect:!0,redirectTo:r||a,isChecking:!1}}}return{shouldRedirect:!1,redirectTo:null,isChecking:!1}},[t,c,s,n,u,r,i.search,o.authRoute,o.roleRoute,o.tierRoute])}export{b as useBack,f as useLocation,$ as useMatch,g as useNavigate,R as useParams,E as usePrefetch,K as useQueryParams,B as useRedirectGuard,k as useRefresh,G as useRouteParam,S as useSearchParams};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use client";import{useCallback as
|
|
1
|
+
"use client";import{useCallback as d}from"react";import{useNavigate as T}from"react-router-dom";import{isClient as F,useOverlayStore as k}from"@donotdev/core";import*as D from"@donotdev/crud";var f=D?.useFormStore,A={forms:{},startSubmit:()=>{},setUploading:()=>{},setValidating:()=>{},setSubmitting:()=>{},setSuccess:()=>{},setError:()=>{},reset:()=>{},cleanup:()=>{},setIsDirty:()=>{},hasDirtyForms:()=>!1,getDirtyFormIds:()=>[],getStatus:()=>"idle",isLoading:()=>!1,getUploadProgress:()=>0,getError:()=>null,getIsDirty:()=>!1};function P(e){return f?f(e):e(A)}P.getState=()=>f&&typeof f.getState=="function"?f.getState():A;async function x(e="You have unsaved changes. Discard them?"){return P.getState().hasDirtyForms()&&typeof window<"u"&&window.confirm?window.confirm(e):!0}function h(){let e=T(),t=k(r=>r.closeAll);return d(async(r,c)=>{if(r==="back")return t(),e(-1);if(await x())return t(),c?.replace?e(r,{replace:!0}):e(r)},[e,t])}function w(){let e=T();return d(()=>e(-1),[e])}function b(){return d(()=>{F()&&window.location.reload()},[])}function I(){return d(e=>{},[])}import{useLocation as E}from"react-router-dom";function m(){return E()}import{useParams as C}from"react-router-dom";function g(){return C()}function G(e){let r=g()[e];if(typeof r=="string")return r;if(Array.isArray(r)&&r.length>0)return r[0]}import{useSearchParams as O}from"react-router-dom";function R(){return O()[0]}import{useMatch as N}from"react-router-dom";function U(e){return N(e)}import{useCallback as y}from"react";function L(){let e=R(),t=h(),r=m(),c=y((n,i)=>{let a=new URLSearchParams(e.toString());a.set(n,i);let l=a.toString(),S=`${r.pathname||"/"}${l?`?${l}`:""}`;t(S)},[e,t,r.pathname]),u=y(n=>{let i=new URLSearchParams(e.toString());i.delete(n);let a=i.toString(),o=`${r.pathname||"/"}${a?`?${a}`:""}`;t(o)},[e,t,r.pathname]),s=y(()=>{let n=r.pathname||"/";t(n)},[t,r.pathname]);return{query:e,setQuery:c,removeQuery:u,clearQueries:s}}import{useMemo as $}from"react";import{isClient as Q,FEATURE_STATUS as j}from"@donotdev/core";import{useAuthConfig as q}from"@donotdev/core";import*as _ from"@donotdev/auth";import{DEGRADED_AUTH_API as K}from"@donotdev/core";var v=_?.useAuth;function M(e){return K[e]}function p(e){return v?v(e):M(e)}function B(e={}){let{auth:t,redirectTo:r,condition:c}=e;if(!Q())return{shouldRedirect:!1,redirectTo:null,isChecking:!1};let u=m(),s=q(),n=p("user"),i=p("can"),a=p("status");return $(()=>{if(a===j.INITIALIZING)return{shouldRedirect:!1,redirectTo:null,isChecking:!0};if(c){let o=c(n,a);return{shouldRedirect:o,redirectTo:o&&r||null,isChecking:!1}}if(t!==!1&&t!==void 0){if(!i)return{shouldRedirect:!1,redirectTo:null,isChecking:!1};if(!i.navigate(t)){let o=null;return typeof t=="object"&&t.required&&!n?u.search.includes("code=")||u.search.includes("state=")||u.search.includes("error=")?o=`${s.authRoute}${u.search}`:o=s.authRoute:typeof t=="object"&&t.role?o=s.roleRoute:typeof t=="object"&&t.tier?o=s.tierRoute:o=s.roleRoute,{shouldRedirect:!0,redirectTo:r||o,isChecking:!1}}}return{shouldRedirect:!1,redirectTo:null,isChecking:!1}},[t,a,n,i,c,r,u.search,s.authRoute,s.roleRoute,s.tierRoute])}export{w as useBack,m as useLocation,U as useMatch,h as useNavigate,g as useParams,I as usePrefetch,L as useQueryParams,B as useRedirectGuard,b as useRefresh,G as useRouteParam,R as useSearchParams};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook to block React Router navigation when forms are dirty.
|
|
3
|
+
* Uses FormStore as single source of truth.
|
|
4
|
+
* Safe - gracefully degrades if CRUD package not installed.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```tsx
|
|
8
|
+
* // In your app root/layout
|
|
9
|
+
* useFormNavigationBlocker();
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export declare function useFormNavigationBlocker(): void;
|
|
13
|
+
export default useFormNavigationBlocker;
|
|
14
|
+
//# sourceMappingURL=useFormNavigationBlocker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useFormNavigationBlocker.d.ts","sourceRoot":"","sources":["../../../src/routing/hooks/useFormNavigationBlocker.ts"],"names":[],"mappings":"AAqBA;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAmB/C;AAED,eAAe,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
// packages/ui/src/routing/hooks/useFormNavigationBlocker.ts
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Form Navigation Blocker Hook
|
|
5
|
+
* @description React Router integration for blocking navigation when forms are dirty.
|
|
6
|
+
* Uses FormStore as single source of truth. Safe - works if CRUD package not installed.
|
|
7
|
+
*
|
|
8
|
+
* @version 0.0.1
|
|
9
|
+
* @since 0.0.1
|
|
10
|
+
* @author AMBROISE PARK Consulting
|
|
11
|
+
*/
|
|
12
|
+
import { useBlocker } from 'react-router-dom';
|
|
13
|
+
import { useEffect } from 'react';
|
|
14
|
+
import { useHasDirtyFormsSafe, checkFormNavigationSafe, } from '../../utils/useFormStoreSafe';
|
|
15
|
+
/**
|
|
16
|
+
* Hook to block React Router navigation when forms are dirty.
|
|
17
|
+
* Uses FormStore as single source of truth.
|
|
18
|
+
* Safe - gracefully degrades if CRUD package not installed.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```tsx
|
|
22
|
+
* // In your app root/layout
|
|
23
|
+
* useFormNavigationBlocker();
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export function useFormNavigationBlocker() {
|
|
27
|
+
const hasDirtyForms = useHasDirtyFormsSafe();
|
|
28
|
+
const blocker = useBlocker(({ currentLocation, nextLocation }) => hasDirtyForms && currentLocation.pathname !== nextLocation.pathname);
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (blocker.state === 'blocked') {
|
|
31
|
+
checkFormNavigationSafe().then((proceed) => {
|
|
32
|
+
if (proceed) {
|
|
33
|
+
blocker.proceed();
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
blocker.reset();
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}, [blocker]);
|
|
41
|
+
}
|
|
42
|
+
export default useFormNavigationBlocker;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { NavigateOptions } from './types';
|
|
2
|
-
export declare function useNavigate(): (to: string, options?: NavigateOptions) => void
|
|
2
|
+
export declare function useNavigate(): (to: string, options?: NavigateOptions) => Promise<void>;
|
|
3
3
|
export declare function useBack(): () => void;
|
|
4
4
|
export declare function useRefresh(): () => void;
|
|
5
5
|
export declare function usePrefetch(): (to: string) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useNavigate.next.d.ts","sourceRoot":"","sources":["../../../src/routing/hooks/useNavigate.next.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useNavigate.next.d.ts","sourceRoot":"","sources":["../../../src/routing/hooks/useNavigate.next.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,wBAAgB,WAAW,SAKZ,MAAM,YAAY,eAAe,mBAgC/C;AAED,wBAAgB,OAAO,eAGtB;AAED,wBAAgB,UAAU,eAGzB;AAED,wBAAgB,WAAW,SAED,MAAM,UAC/B"}
|
|
@@ -11,14 +11,20 @@
|
|
|
11
11
|
import { useRouter as useNextRouter } from 'next/navigation';
|
|
12
12
|
import { useCallback } from 'react';
|
|
13
13
|
import { isClient, useOverlayStore } from '@donotdev/core';
|
|
14
|
+
import { checkFormNavigationSafe } from '../../utils/useFormStoreSafe';
|
|
14
15
|
export function useNavigate() {
|
|
15
16
|
const router = useNextRouter();
|
|
16
17
|
const closeAll = useOverlayStore((state) => state.closeAll);
|
|
17
|
-
return useCallback((to, options) => {
|
|
18
|
+
return useCallback(async (to, options) => {
|
|
18
19
|
if (to === 'back') {
|
|
19
20
|
closeAll();
|
|
20
21
|
return router.back();
|
|
21
22
|
}
|
|
23
|
+
// Check FormStore for dirty forms before navigation (safe - works if CRUD not installed)
|
|
24
|
+
const shouldProceed = await checkFormNavigationSafe();
|
|
25
|
+
if (!shouldProceed) {
|
|
26
|
+
return; // Navigation blocked by user
|
|
27
|
+
}
|
|
22
28
|
closeAll();
|
|
23
29
|
if (options?.preserveScroll && isClient()) {
|
|
24
30
|
const scrollY = window.scrollY;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { NavigateOptions } from './types';
|
|
2
|
-
export declare function useNavigate(): (to: string, options?: NavigateOptions) =>
|
|
2
|
+
export declare function useNavigate(): (to: string, options?: NavigateOptions) => Promise<void>;
|
|
3
3
|
export declare function useBack(): () => void | Promise<void>;
|
|
4
4
|
export declare function useRefresh(): () => void;
|
|
5
5
|
export declare function usePrefetch(): (_to: string) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useNavigate.vite.d.ts","sourceRoot":"","sources":["../../../src/routing/hooks/useNavigate.vite.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useNavigate.vite.d.ts","sourceRoot":"","sources":["../../../src/routing/hooks/useNavigate.vite.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,wBAAgB,WAAW,SAKZ,MAAM,YAAY,eAAe,mBAmB/C;AAED,wBAAgB,OAAO,+BAGtB;AAED,wBAAgB,UAAU,eAIzB;AAED,wBAAgB,WAAW,UACA,MAAM,UAGhC"}
|
|
@@ -11,14 +11,20 @@
|
|
|
11
11
|
import { useCallback } from 'react';
|
|
12
12
|
import { useNavigate as useRouterNavigate } from 'react-router-dom';
|
|
13
13
|
import { isClient, useOverlayStore } from '@donotdev/core';
|
|
14
|
+
import { checkFormNavigationSafe } from '../../utils/useFormStoreSafe';
|
|
14
15
|
export function useNavigate() {
|
|
15
16
|
const navigate = useRouterNavigate();
|
|
16
17
|
const closeAll = useOverlayStore((state) => state.closeAll);
|
|
17
|
-
return useCallback((to, options) => {
|
|
18
|
+
return useCallback(async (to, options) => {
|
|
18
19
|
if (to === 'back') {
|
|
19
20
|
closeAll();
|
|
20
21
|
return navigate(-1);
|
|
21
22
|
}
|
|
23
|
+
// Check FormStore for dirty forms before navigation (safe - works if CRUD not installed)
|
|
24
|
+
const shouldProceed = await checkFormNavigationSafe();
|
|
25
|
+
if (!shouldProceed) {
|
|
26
|
+
return; // Navigation blocked by user
|
|
27
|
+
}
|
|
22
28
|
closeAll();
|
|
23
29
|
if (options?.replace)
|
|
24
30
|
return navigate(to, { replace: true });
|