@djangocfg/layouts 2.1.611 → 2.1.612
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/package.json +24 -25
- package/src/layouts/AppLayout/BaseApp.tsx +2 -1
- package/src/layouts/AppLayout/pwaMeta.test.ts +25 -1
- package/src/layouts/AppLayout/pwaMeta.tsx +25 -0
- package/src/layouts/PrivateLayout/hooks/useSidebarKeyboard.ts +1 -1
- package/src/layouts/PublicLayout/footers/DefaultFooter/FooterSocialLinks.tsx +11 -9
- package/src/layouts/PublicLayout/footers/DefaultFooter/brandIcons.tsx +26 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.612",
|
|
4
4
|
"description": "Simple, straightforward layout components for Next.js - import and use with props",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"layouts",
|
|
@@ -91,14 +91,14 @@
|
|
|
91
91
|
"test": "vitest run"
|
|
92
92
|
},
|
|
93
93
|
"peerDependencies": {
|
|
94
|
-
"@djangocfg/analytics": "^2.1.
|
|
95
|
-
"@djangocfg/api": "^2.1.
|
|
96
|
-
"@djangocfg/devtools": "^2.1.
|
|
97
|
-
"@djangocfg/i18n": "^2.1.
|
|
98
|
-
"@djangocfg/ui-core": "^2.1.
|
|
99
|
-
"@hookform/resolvers": "^5.2.2",
|
|
94
|
+
"@djangocfg/analytics": "^2.1.612",
|
|
95
|
+
"@djangocfg/api": "^2.1.612",
|
|
96
|
+
"@djangocfg/devtools": "^2.1.612",
|
|
97
|
+
"@djangocfg/i18n": "^2.1.612",
|
|
98
|
+
"@djangocfg/ui-core": "^2.1.612",
|
|
99
|
+
"@hookform/resolvers": "^5.2.2 || ^7.0.2",
|
|
100
100
|
"consola": "^3.4.2",
|
|
101
|
-
"lucide-react": "^
|
|
101
|
+
"lucide-react": "^1.68.0",
|
|
102
102
|
"moment": "^2.30.1",
|
|
103
103
|
"next": "^16.2.2",
|
|
104
104
|
"next-intl": "^4.9.1",
|
|
@@ -119,28 +119,27 @@
|
|
|
119
119
|
}
|
|
120
120
|
},
|
|
121
121
|
"dependencies": {
|
|
122
|
-
"libphonenumber-js": "^1.13.
|
|
122
|
+
"libphonenumber-js": "^1.13.15",
|
|
123
123
|
"nextjs-toploader": "^3.9.17",
|
|
124
|
-
"qrcode.react": "^4.2.0"
|
|
125
|
-
"uuid": "^11.1.1"
|
|
124
|
+
"qrcode.react": "^4.2.0"
|
|
126
125
|
},
|
|
127
126
|
"devDependencies": {
|
|
128
|
-
"@djangocfg/analytics": "^2.1.
|
|
129
|
-
"@djangocfg/api": "^2.1.
|
|
130
|
-
"@djangocfg/devtools": "^2.1.
|
|
131
|
-
"@djangocfg/i18n": "^2.1.
|
|
132
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
133
|
-
"@djangocfg/ui-core": "^2.1.
|
|
134
|
-
"@storybook/react-vite": "^10.5
|
|
127
|
+
"@djangocfg/analytics": "^2.1.612",
|
|
128
|
+
"@djangocfg/api": "^2.1.612",
|
|
129
|
+
"@djangocfg/devtools": "^2.1.612",
|
|
130
|
+
"@djangocfg/i18n": "^2.1.612",
|
|
131
|
+
"@djangocfg/typescript-config": "^2.1.612",
|
|
132
|
+
"@djangocfg/ui-core": "^2.1.612",
|
|
133
|
+
"@storybook/react-vite": "^10.6.5",
|
|
135
134
|
"@types/node": "^25.9.5",
|
|
136
|
-
"@types/react": "19.
|
|
137
|
-
"@types/react-dom": "19.
|
|
138
|
-
"eslint": "^
|
|
139
|
-
"next-intl": "^4.
|
|
140
|
-
"storybook": "^10.5
|
|
135
|
+
"@types/react": "19.3.0",
|
|
136
|
+
"@types/react-dom": "19.3.0",
|
|
137
|
+
"eslint": "^10.14.0",
|
|
138
|
+
"next-intl": "^4.14.9",
|
|
139
|
+
"storybook": "^10.6.5",
|
|
141
140
|
"typescript": "^5.9.3",
|
|
142
|
-
"vitest": "^
|
|
143
|
-
"zustand": "^5.0.
|
|
141
|
+
"vitest": "^5.1.0",
|
|
142
|
+
"zustand": "^5.0.16"
|
|
144
143
|
},
|
|
145
144
|
"publishConfig": {
|
|
146
145
|
"access": "public"
|
|
@@ -62,7 +62,7 @@ import { AuthDialog } from '../../snippets/AuthDialog';
|
|
|
62
62
|
import type { BaseLayoutProps } from '../types/layout.types';
|
|
63
63
|
import { LayoutI18nProvider } from '../../shared/LayoutI18nProvider';
|
|
64
64
|
import { ProjectNameProvider } from '../../shared/ProjectContext';
|
|
65
|
-
import { PwaMetaGuard } from './pwaMeta';
|
|
65
|
+
import { PwaManifestCheck, PwaMetaGuard } from './pwaMeta';
|
|
66
66
|
|
|
67
67
|
import React from 'react';
|
|
68
68
|
|
|
@@ -126,6 +126,7 @@ export function BaseApp({
|
|
|
126
126
|
const content = (
|
|
127
127
|
<>
|
|
128
128
|
{pwaMeta && <PwaMetaGuard />}
|
|
129
|
+
{pwaMeta && <PwaManifestCheck />}
|
|
129
130
|
<ThemeProvider
|
|
130
131
|
defaultTheme={theme?.defaultTheme || 'system'}
|
|
131
132
|
storageKey={theme?.storageKey}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @vitest-environment jsdom
|
|
2
2
|
import { beforeEach, describe, expect, it } from 'vitest';
|
|
3
3
|
|
|
4
|
-
import { PWA_META_SCRIPT } from './pwaMeta';
|
|
4
|
+
import { isManifestMissing, MANIFEST_HINT, PWA_META_SCRIPT } from './pwaMeta';
|
|
5
5
|
|
|
6
6
|
const meta = (n: string) => document.head.querySelector(`meta[name="${n}"]`)?.getAttribute('content');
|
|
7
7
|
const run = () => new Function(PWA_META_SCRIPT)();
|
|
@@ -34,3 +34,27 @@ describe('PWA_META_SCRIPT', () => {
|
|
|
34
34
|
expect(meta('apple-mobile-web-app-status-bar-style')).toBe('default');
|
|
35
35
|
});
|
|
36
36
|
});
|
|
37
|
+
|
|
38
|
+
describe('isManifestMissing', () => {
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
document.head.innerHTML = '';
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('is true without a manifest link', () => {
|
|
44
|
+
expect(isManifestMissing(document)).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('is false with a manifest link', () => {
|
|
48
|
+
document.head.innerHTML = '<link rel="manifest" href="/manifest.webmanifest">';
|
|
49
|
+
expect(isManifestMissing(document)).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('is false without a document (SSR / bare test env)', () => {
|
|
53
|
+
expect(isManifestMissing(undefined)).toBe(false);
|
|
54
|
+
expect(isManifestMissing(null)).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('hint names the supported import path only', () => {
|
|
58
|
+
expect(MANIFEST_HINT).toContain('@djangocfg/nextjs/pwa/manifest');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -38,3 +38,28 @@ if(window.MutationObserver){new MutationObserver(function(){var v=q('viewport');
|
|
|
38
38
|
export function PwaMetaGuard() {
|
|
39
39
|
return <script dangerouslySetInnerHTML={{ __html: PWA_META_SCRIPT }} suppressHydrationWarning />;
|
|
40
40
|
}
|
|
41
|
+
|
|
42
|
+
export const MANIFEST_HINT =
|
|
43
|
+
"[djangocfg] No <link rel=\"manifest\"> in <head>, so the app is not installable. Add app/manifest.ts: " +
|
|
44
|
+
"`import { createManifest } from '@djangocfg/nextjs/pwa/manifest'; export default createManifest({ name, ... })`. " +
|
|
45
|
+
'Disable this check with <BaseApp pwaMeta={false} />.';
|
|
46
|
+
|
|
47
|
+
/** Pure check: true when the document is available and has no manifest link. */
|
|
48
|
+
export function isManifestMissing(doc: Pick<Document, 'head'> | null | undefined): boolean {
|
|
49
|
+
const head = doc?.head;
|
|
50
|
+
if (!head) return false;
|
|
51
|
+
return !head.querySelector('link[rel~="manifest"]');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Dev-only, once per page load: warns when the app forgot `app/manifest.ts`. Renders nothing. */
|
|
55
|
+
export function PwaManifestCheck() {
|
|
56
|
+
React.useEffect(() => {
|
|
57
|
+
if (process.env.NODE_ENV === 'production') return;
|
|
58
|
+
if (typeof document === 'undefined') return;
|
|
59
|
+
const w = window as unknown as { __djangocfgManifestChecked?: boolean };
|
|
60
|
+
if (w.__djangocfgManifestChecked) return;
|
|
61
|
+
w.__djangocfgManifestChecked = true;
|
|
62
|
+
if (isManifestMissing(document)) console.warn(MANIFEST_HINT);
|
|
63
|
+
}, []);
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
@@ -26,7 +26,7 @@ export function useSidebarKeyboard() {
|
|
|
26
26
|
const current = focusables.indexOf(document.activeElement as HTMLElement);
|
|
27
27
|
if (current === -1) return;
|
|
28
28
|
|
|
29
|
-
let next
|
|
29
|
+
let next: number;
|
|
30
30
|
switch (e.key) {
|
|
31
31
|
case 'ArrowDown':
|
|
32
32
|
next = (current + 1) % focusables.length;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
Facebook, Github, Instagram, Linkedin, Mail, MessageCircle, MessageSquare, Twitter, Youtube
|
|
5
|
-
} from 'lucide-react';
|
|
3
|
+
import { Mail, MessageCircle, MessageSquare } from 'lucide-react';
|
|
6
4
|
import React from 'react';
|
|
7
5
|
|
|
6
|
+
import {
|
|
7
|
+
FacebookIcon, GithubIcon, InstagramIcon, LinkedinIcon, TwitterIcon, YoutubeIcon,
|
|
8
|
+
} from './brandIcons';
|
|
9
|
+
|
|
8
10
|
import type { FooterSocialLinks } from './types';
|
|
9
11
|
|
|
10
12
|
export interface FooterSocialLinksProps {
|
|
@@ -14,13 +16,13 @@ export interface FooterSocialLinksProps {
|
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
const socialIconsMap = {
|
|
17
|
-
github: { icon:
|
|
18
|
-
linkedin: { icon:
|
|
19
|
-
twitter: { icon:
|
|
19
|
+
github: { icon: GithubIcon, title: 'GitHub' },
|
|
20
|
+
linkedin: { icon: LinkedinIcon, title: 'LinkedIn' },
|
|
21
|
+
twitter: { icon: TwitterIcon, title: 'Twitter' },
|
|
20
22
|
telegram: { icon: MessageCircle, title: 'Telegram' },
|
|
21
|
-
youtube: { icon:
|
|
22
|
-
facebook: { icon:
|
|
23
|
-
instagram: { icon:
|
|
23
|
+
youtube: { icon: YoutubeIcon, title: 'YouTube' },
|
|
24
|
+
facebook: { icon: FacebookIcon, title: 'Facebook' },
|
|
25
|
+
instagram: { icon: InstagramIcon, title: 'Instagram' },
|
|
24
26
|
whatsapp: { icon: MessageSquare, title: 'WhatsApp' },
|
|
25
27
|
email: { icon: Mail, title: 'Email' },
|
|
26
28
|
} as const;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Brand glyphs for the footer social links. lucide-react 1.0 dropped every brand
|
|
5
|
+
* icon (Github, Twitter, Linkedin, ...), so these are inlined. Paths are from
|
|
6
|
+
* simple-icons (CC0); LinkedIn from simple-icons 13, the last release shipping it.
|
|
7
|
+
* Same API as a lucide icon: `className`, filled with `currentColor`.
|
|
8
|
+
*/
|
|
9
|
+
type IconProps = ComponentProps<'svg'>;
|
|
10
|
+
|
|
11
|
+
const brand = (name: string, d: string) => {
|
|
12
|
+
const Icon = (props: IconProps) => (
|
|
13
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" {...props}>
|
|
14
|
+
<path d={d} />
|
|
15
|
+
</svg>
|
|
16
|
+
);
|
|
17
|
+
Icon.displayName = name;
|
|
18
|
+
return Icon;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const GithubIcon = brand('GithubIcon', 'M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12');
|
|
22
|
+
export const LinkedinIcon = brand('LinkedinIcon', 'M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z');
|
|
23
|
+
export const TwitterIcon = brand('TwitterIcon', 'M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z');
|
|
24
|
+
export const YoutubeIcon = brand('YoutubeIcon', 'M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z');
|
|
25
|
+
export const FacebookIcon = brand('FacebookIcon', 'M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z');
|
|
26
|
+
export const InstagramIcon = brand('InstagramIcon', 'M7.0301.084c-1.2768.0602-2.1487.264-2.911.5634-.7888.3075-1.4575.72-2.1228 1.3877-.6652.6677-1.075 1.3368-1.3802 2.127-.2954.7638-.4956 1.6365-.552 2.914-.0564 1.2775-.0689 1.6882-.0626 4.947.0062 3.2586.0206 3.6671.0825 4.9473.061 1.2765.264 2.1482.5635 2.9107.308.7889.72 1.4573 1.388 2.1228.6679.6655 1.3365 1.0743 2.1285 1.38.7632.295 1.6361.4961 2.9134.552 1.2773.056 1.6884.069 4.9462.0627 3.2578-.0062 3.668-.0207 4.9478-.0814 1.28-.0607 2.147-.2652 2.9098-.5633.7889-.3086 1.4578-.72 2.1228-1.3881.665-.6682 1.0745-1.3378 1.3795-2.1284.2957-.7632.4966-1.636.552-2.9124.056-1.2809.0692-1.6898.063-4.948-.0063-3.2583-.021-3.6668-.0817-4.9465-.0607-1.2797-.264-2.1487-.5633-2.9117-.3084-.7889-.72-1.4568-1.3876-2.1228C21.2982 1.33 20.628.9208 19.8378.6165 19.074.321 18.2017.1197 16.9244.0645 15.6471.0093 15.236-.005 11.977.0014 8.718.0076 8.31.0215 7.0301.0839m.1402 21.6932c-1.17-.0509-1.8053-.2453-2.2287-.408-.5606-.216-.96-.4771-1.3819-.895-.422-.4178-.6811-.8186-.9-1.378-.1644-.4234-.3624-1.058-.4171-2.228-.0595-1.2645-.072-1.6442-.079-4.848-.007-3.2037.0053-3.583.0607-4.848.05-1.169.2456-1.805.408-2.2282.216-.5613.4762-.96.895-1.3816.4188-.4217.8184-.6814 1.3783-.9003.423-.1651 1.0575-.3614 2.227-.4171 1.2655-.06 1.6447-.072 4.848-.079 3.2033-.007 3.5835.005 4.8495.0608 1.169.0508 1.8053.2445 2.228.408.5608.216.96.4754 1.3816.895.4217.4194.6816.8176.9005 1.3787.1653.4217.3617 1.056.4169 2.2263.0602 1.2655.0739 1.645.0796 4.848.0058 3.203-.0055 3.5834-.061 4.848-.051 1.17-.245 1.8055-.408 2.2294-.216.5604-.4763.96-.8954 1.3814-.419.4215-.8181.6811-1.3783.9-.4224.1649-1.0577.3617-2.2262.4174-1.2656.0595-1.6448.072-4.8493.079-3.2045.007-3.5825-.006-4.848-.0608M16.953 5.5864A1.44 1.44 0 1 0 18.39 4.144a1.44 1.44 0 0 0-1.437 1.4424M5.8385 12.012c.0067 3.4032 2.7706 6.1557 6.173 6.1493 3.4026-.0065 6.157-2.7701 6.1506-6.1733-.0065-3.4032-2.771-6.1565-6.174-6.1498-3.403.0067-6.156 2.771-6.1496 6.1738M8 12.0077a4 4 0 1 1 4.008 3.9921A3.9996 3.9996 0 0 1 8 12.0077');
|