feat: Initialize PolyWeather frontend project with Next.js, including core dependencies, root layout, and a dashboard detail panel.
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Analytics } from "@vercel/analytics/react";
|
import { Analytics } from "@vercel/analytics/react";
|
||||||
|
import { SpeedInsights } from "@vercel/speed-insights/next";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
@@ -28,6 +29,7 @@ export default function RootLayout({
|
|||||||
<body className="min-h-screen font-sans antialiased">
|
<body className="min-h-screen font-sans antialiased">
|
||||||
{children}
|
{children}
|
||||||
<Analytics />
|
<Analytics />
|
||||||
|
<SpeedInsights />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { ChartConfiguration } from "chart.js/auto";
|
import { ChartConfiguration } from "chart.js/auto";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
import { useEffect, useRef } from "react";
|
||||||
import { ForecastTable } from "@/components/dashboard/PanelSections";
|
import { ForecastTable } from "@/components/dashboard/PanelSections";
|
||||||
import { useChart } from "@/hooks/useChart";
|
import { useChart } from "@/hooks/useChart";
|
||||||
import { useDashboardStore } from "@/hooks/useDashboardStore";
|
import { useDashboardStore } from "@/hooks/useDashboardStore";
|
||||||
@@ -127,6 +128,7 @@ export function DetailPanel() {
|
|||||||
const store = useDashboardStore();
|
const store = useDashboardStore();
|
||||||
const { locale, t } = useI18n();
|
const { locale, t } = useI18n();
|
||||||
const detail = store.selectedDetail;
|
const detail = store.selectedDetail;
|
||||||
|
const panelRef = useRef<HTMLElement | null>(null);
|
||||||
const isOverlayOpen =
|
const isOverlayOpen =
|
||||||
Boolean(store.futureModalDate) ||
|
Boolean(store.futureModalDate) ||
|
||||||
store.historyState.isOpen ||
|
store.historyState.isOpen ||
|
||||||
@@ -139,18 +141,49 @@ export function DetailPanel() {
|
|||||||
!isOverlayOpen;
|
!isOverlayOpen;
|
||||||
const profileStats = detail ? getCityProfileStats(detail, locale) : [];
|
const profileStats = detail ? getCityProfileStats(detail, locale) : [];
|
||||||
const scenery = getCityScenery(detail?.name);
|
const scenery = getCityScenery(detail?.name);
|
||||||
|
const blurActiveElement = () => {
|
||||||
|
if (typeof document === "undefined") return;
|
||||||
|
const active = document.activeElement;
|
||||||
|
if (active instanceof HTMLElement) {
|
||||||
|
active.blur();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const panel = panelRef.current;
|
||||||
|
if (!panel) return;
|
||||||
|
|
||||||
|
if (!isVisible) {
|
||||||
|
panel.setAttribute("inert", "");
|
||||||
|
if (
|
||||||
|
typeof document !== "undefined" &&
|
||||||
|
panel.contains(document.activeElement)
|
||||||
|
) {
|
||||||
|
const active = document.activeElement;
|
||||||
|
if (active instanceof HTMLElement) {
|
||||||
|
active.blur();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
panel.removeAttribute("inert");
|
||||||
|
}, [isVisible]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside
|
<aside
|
||||||
|
ref={panelRef}
|
||||||
className={clsx("detail-panel", isVisible && "visible")}
|
className={clsx("detail-panel", isVisible && "visible")}
|
||||||
aria-hidden={!isVisible}
|
|
||||||
>
|
>
|
||||||
<div className="panel-header">
|
<div className="panel-header">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="panel-close"
|
className="panel-close"
|
||||||
aria-label={t("detail.closeAria")}
|
aria-label={t("detail.closeAria")}
|
||||||
onClick={store.closePanel}
|
onClick={() => {
|
||||||
|
blurActiveElement();
|
||||||
|
store.closePanel();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
@@ -169,7 +202,10 @@ export function DetailPanel() {
|
|||||||
type="button"
|
type="button"
|
||||||
className="history-btn"
|
className="history-btn"
|
||||||
title={t("detail.todayAnalysis")}
|
title={t("detail.todayAnalysis")}
|
||||||
onClick={() => void store.openTodayModal()}
|
onClick={() => {
|
||||||
|
blurActiveElement();
|
||||||
|
void store.openTodayModal();
|
||||||
|
}}
|
||||||
disabled={!detail}
|
disabled={!detail}
|
||||||
>
|
>
|
||||||
{t("detail.todayAnalysis")}
|
{t("detail.todayAnalysis")}
|
||||||
@@ -178,7 +214,10 @@ export function DetailPanel() {
|
|||||||
type="button"
|
type="button"
|
||||||
className="history-btn"
|
className="history-btn"
|
||||||
title={t("detail.history")}
|
title={t("detail.history")}
|
||||||
onClick={() => void store.openHistory()}
|
onClick={() => {
|
||||||
|
blurActiveElement();
|
||||||
|
void store.openHistory();
|
||||||
|
}}
|
||||||
disabled={!detail}
|
disabled={!detail}
|
||||||
>
|
>
|
||||||
{t("detail.history")}
|
{t("detail.history")}
|
||||||
|
|||||||
Generated
+44
@@ -10,6 +10,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-slot": "^1.1.2",
|
"@radix-ui/react-slot": "^1.1.2",
|
||||||
"@vercel/analytics": "^1.6.1",
|
"@vercel/analytics": "^1.6.1",
|
||||||
|
"@vercel/speed-insights": "^2.0.0",
|
||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
@@ -822,6 +823,43 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@vercel/speed-insights": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@vercel/speed-insights/-/speed-insights-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-jwkNcrTeafWxjmWq4AHBaptSqZiJkYU5adLC9QBSqeim0GcqDMgN5Ievh8OG1rJ6W3A4l1oiP7qr9CWxGuzu3w==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@sveltejs/kit": "^1 || ^2",
|
||||||
|
"next": ">= 13",
|
||||||
|
"nuxt": ">= 3",
|
||||||
|
"react": "^18 || ^19 || ^19.0.0-rc",
|
||||||
|
"svelte": ">= 4",
|
||||||
|
"vue": "^3",
|
||||||
|
"vue-router": "^4"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@sveltejs/kit": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"next": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"nuxt": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"svelte": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"vue": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"vue-router": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/any-promise": {
|
"node_modules/any-promise": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz",
|
"resolved": "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz",
|
||||||
@@ -2509,6 +2547,12 @@
|
|||||||
"integrity": "sha512-oH9He/bEM+6oKlv3chWuOOcp8Y6fo6/PSro8hEkgCW3pu9/OiCXiUpRUogDh3Fs3LH2sosDrx8CxeOLBEE+afg==",
|
"integrity": "sha512-oH9He/bEM+6oKlv3chWuOOcp8Y6fo6/PSro8hEkgCW3pu9/OiCXiUpRUogDh3Fs3LH2sosDrx8CxeOLBEE+afg==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
|
"@vercel/speed-insights": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@vercel/speed-insights/-/speed-insights-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-jwkNcrTeafWxjmWq4AHBaptSqZiJkYU5adLC9QBSqeim0GcqDMgN5Ievh8OG1rJ6W3A4l1oiP7qr9CWxGuzu3w==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"any-promise": {
|
"any-promise": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz",
|
"resolved": "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-slot": "^1.1.2",
|
"@radix-ui/react-slot": "^1.1.2",
|
||||||
"@vercel/analytics": "^1.6.1",
|
"@vercel/analytics": "^1.6.1",
|
||||||
|
"@vercel/speed-insights": "^2.0.0",
|
||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
|||||||
Reference in New Issue
Block a user