@naeil/ui 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-QQQQZEPV.js → chunk-KPE4KKYM.js} +111 -14
- package/dist/{chunk-MGCCKOGU.js → chunk-ULZPJI5X.js} +102 -44
- package/dist/{chunk-7JBLPGZ4.js → chunk-WA5D7UI2.js} +1 -1
- package/dist/components/index.d.ts +2 -2
- package/dist/components/index.js +3 -3
- package/dist/components/naeil-footer.d.ts +2 -2
- package/dist/components/naeil-footer.js +3 -1
- package/dist/components/naeil-header.d.ts +8 -2
- package/dist/components/naeil-header.js +3 -3
- package/dist/components/ui/index.d.ts +2 -2
- package/dist/components/ui/index.js +1 -1
- package/dist/lib/ui-release.d.ts +1 -1
- package/dist/lib/ui-release.js +1 -1
- package/dist/{navigation-CgPeL9tF.d.ts → navigation-D8N1tMTu.d.ts} +4 -2
- package/dist/{site-footer-vXDa_6yr.d.ts → site-footer-s113FZgC.d.ts} +1 -1
- package/dist/source/components/naeil-footer.js +2 -0
- package/dist/source/components/naeil-header-account.js +54 -19
- package/dist/source/components/naeil-header.d.ts +8 -2
- package/dist/source/components/naeil-header.js +49 -23
- package/dist/source/components/ui/navigation.d.ts +8 -2
- package/dist/source/components/ui/navigation.js +112 -15
- package/dist/source/lib/ui-release.d.ts +1 -1
- package/dist/source/lib/ui-release.js +1 -1
- package/docs/components/application-layout.md +7 -4
- package/docs/components/naeil-header.md +33 -5
- package/docs/package-release.md +15 -4
- package/package.json +1 -1
- package/src/styles/components.css +113 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
|
+
var __defProps = Object.defineProperties;
|
|
4
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
3
5
|
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
4
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
7
|
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
@@ -15,9 +17,12 @@ var __spreadValues = (a, b) => {
|
|
|
15
17
|
}
|
|
16
18
|
return a;
|
|
17
19
|
};
|
|
20
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
18
21
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
19
22
|
import { UserRound } from "lucide-react";
|
|
23
|
+
import { useContext, useRef } from "react";
|
|
20
24
|
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuItem } from "./ui/dropdown-menu.js";
|
|
25
|
+
import { NavigationSheetAction } from "./ui/navigation.js";
|
|
21
26
|
const labels = {
|
|
22
27
|
en: { menu: "Account menu", account: "Account", login: "Log in", logout: "Log out", busy: "Logging out\u2026" },
|
|
23
28
|
ko: { menu: "\uACC4\uC815 \uBA54\uB274", account: "\uACC4\uC815", login: "\uB85C\uADF8\uC778", logout: "\uB85C\uADF8\uC544\uC6C3", busy: "\uB85C\uADF8\uC544\uC6C3 \uC911\u2026" },
|
|
@@ -26,10 +31,18 @@ const labels = {
|
|
|
26
31
|
const NativeLink = (props) => /* @__PURE__ */ jsx("a", __spreadValues({}, props));
|
|
27
32
|
function NaeilHeaderAccountControl({ locale, account, Link = NativeLink }) {
|
|
28
33
|
const text = labels[locale];
|
|
34
|
+
const handoff = useContext(NavigationSheetAction);
|
|
35
|
+
const pendingAction = useRef(void 0);
|
|
29
36
|
if (account.status === "signed-out") {
|
|
30
37
|
return /* @__PURE__ */ jsx(Link, { href: account.loginHref, className: "ui-navigation-link ui-focus naeil-header-login", "data-naeil-account-state": "signed-out", children: text.login });
|
|
31
38
|
}
|
|
32
|
-
return /* @__PURE__ */ jsxs(DropdownMenu, {
|
|
39
|
+
return /* @__PURE__ */ jsxs(DropdownMenu, { onOpenChange: (open) => {
|
|
40
|
+
var _a;
|
|
41
|
+
if (open) {
|
|
42
|
+
(_a = pendingAction.current) == null ? void 0 : _a.cancel();
|
|
43
|
+
pendingAction.current = void 0;
|
|
44
|
+
}
|
|
45
|
+
}, children: [
|
|
33
46
|
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
34
47
|
"button",
|
|
35
48
|
{
|
|
@@ -42,24 +55,46 @@ function NaeilHeaderAccountControl({ locale, account, Link = NativeLink }) {
|
|
|
42
55
|
children: /* @__PURE__ */ jsx("span", { className: "naeil-header-account-avatar", "aria-hidden": "true", children: /* @__PURE__ */ jsx(UserRound, {}) })
|
|
43
56
|
}
|
|
44
57
|
) }),
|
|
45
|
-
/* @__PURE__ */ jsxs(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
{
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
/* @__PURE__ */ jsxs(
|
|
59
|
+
DropdownMenuContent,
|
|
60
|
+
{
|
|
61
|
+
align: "end",
|
|
62
|
+
className: "w-56 max-w-[calc(100vw-2rem)]",
|
|
63
|
+
"data-naeil-account-menu": true,
|
|
64
|
+
"data-naeil-chrome": "",
|
|
65
|
+
lang: locale,
|
|
66
|
+
onCloseAutoFocus: (event) => {
|
|
67
|
+
const flow = pendingAction.current;
|
|
68
|
+
if (!flow || event.target !== flow.menu) return;
|
|
69
|
+
event.preventDefault();
|
|
70
|
+
flow.menu = null;
|
|
71
|
+
flow.menuClosed();
|
|
72
|
+
},
|
|
73
|
+
children: [
|
|
74
|
+
/* @__PURE__ */ jsxs(DropdownMenuLabel, { className: "flex min-w-0 flex-col font-normal", children: [
|
|
75
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-base font-medium", children: account.displayName || text.account }),
|
|
76
|
+
account.email && /* @__PURE__ */ jsx("span", { className: "truncate text-sm text-muted-foreground", children: account.email })
|
|
77
|
+
] }),
|
|
78
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
79
|
+
/* @__PURE__ */ jsx(
|
|
80
|
+
DropdownMenuItem,
|
|
81
|
+
{
|
|
82
|
+
className: "min-h-11",
|
|
83
|
+
disabled: account.busy,
|
|
84
|
+
onSelect: (event) => {
|
|
85
|
+
if (account.busy) return;
|
|
86
|
+
if (handoff) {
|
|
87
|
+
const menu = event.target.closest("[data-naeil-account-menu]");
|
|
88
|
+
if (!(menu == null ? void 0 : menu.isConnected)) return;
|
|
89
|
+
pendingAction.current = __spreadProps(__spreadValues({}, handoff(account.onLogout)), { menu });
|
|
90
|
+
} else account.onLogout();
|
|
91
|
+
},
|
|
92
|
+
children: account.busy ? text.busy : text.logout
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
)
|
|
63
98
|
] });
|
|
64
99
|
}
|
|
65
100
|
export {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import { NavigationProps, NavigationLinkProps, NavigationLink, NavigationLinkComponent } from './ui/navigation.js';
|
|
3
|
+
import { NavigationProps, NavigationItem, NavigationLinkProps, NavigationLink, NavigationLinkComponent } from './ui/navigation.js';
|
|
4
4
|
import { NaeilHeaderLocale } from './naeil-header-content.js';
|
|
5
5
|
export { NaeilHeaderAccount, NaeilHeaderAccountControl, NaeilHeaderAccountControlProps } from './naeil-header-account.js';
|
|
6
6
|
|
|
@@ -10,6 +10,12 @@ interface NaeilHeaderProps extends Omit<NavigationProps, "brand" | "items" | "to
|
|
|
10
10
|
resolveHref?: (path: string) => string;
|
|
11
11
|
brandHref?: string;
|
|
12
12
|
brandSlot?: ReactNode;
|
|
13
|
+
/** Ignored when brandSlot replaces the complete brand content. */
|
|
14
|
+
brandSuffix?: string;
|
|
15
|
+
/** After the brand link, outside it; rendered once at every width. */
|
|
16
|
+
statusSlot?: ReactNode;
|
|
17
|
+
/** Product menu data, after catalog/blog and before flat subNav links. */
|
|
18
|
+
appNavigation?: NavigationItem[];
|
|
13
19
|
brandLinkProps?: Omit<NavigationLinkProps, "href" | "children">;
|
|
14
20
|
/** Omit when the application does not support changing its UI language. */
|
|
15
21
|
onLocaleChange?: (locale: NaeilHeaderLocale) => void;
|
|
@@ -48,6 +54,6 @@ declare function NaeilHeaderLocaleControl({ locale, onLocaleChange }: {
|
|
|
48
54
|
onLocaleChange: (locale: NaeilHeaderLocale) => void;
|
|
49
55
|
}): react_jsx_runtime.JSX.Element;
|
|
50
56
|
/** One complete, opt-in branded composition shared by naeil.dev and applications. */
|
|
51
|
-
declare function NaeilHeader({ locale, resolveHref, brandHref, brandSlot, brandLinkProps, brandLinkComponent, onLocaleChange, authSlot, toolbarSlot, projects, projectsExternal, translations, showProjects, showBlog, subNav, currentProjectKey, currentProjectHref, labels, Link, currentHref, onNavigate, className, containerClassName, ...props }: NaeilHeaderProps): react_jsx_runtime.JSX.Element;
|
|
57
|
+
declare function NaeilHeader({ locale, resolveHref, brandHref, brandSlot, brandSuffix, statusSlot, appNavigation, brandLinkProps, brandLinkComponent, onLocaleChange, authSlot, toolbarSlot, projects, projectsExternal, translations, showProjects, showBlog, subNav, currentProjectKey, currentProjectHref, labels, Link, currentHref, onNavigate, className, containerClassName, ...props }: NaeilHeaderProps): react_jsx_runtime.JSX.Element;
|
|
52
58
|
|
|
53
59
|
export { NaeilHeader, NaeilHeaderLocale, NaeilHeaderLocaleControl, type NaeilHeaderProps, NaeilHeaderThemeControl };
|
|
@@ -72,7 +72,7 @@ function NaeilHeaderLocaleControl({ locale, onLocaleChange }) {
|
|
|
72
72
|
children: /* @__PURE__ */ jsx(SelectValue, { children: locale.toUpperCase() })
|
|
73
73
|
}
|
|
74
74
|
),
|
|
75
|
-
/* @__PURE__ */ jsx(SelectContent, { align: "end", children: headerLocales.map((value) => /* @__PURE__ */ jsx(SelectItem, { value, className: "min-h-11", lang: value, children: headerLocaleNames[value] }, value)) })
|
|
75
|
+
/* @__PURE__ */ jsx(SelectContent, { align: "end", "data-naeil-chrome": "", "data-naeil-locale-menu": "", lang: locale, children: headerLocales.map((value) => /* @__PURE__ */ jsx(SelectItem, { value, className: "min-h-11", lang: value, children: headerLocaleNames[value] }, value)) })
|
|
76
76
|
] });
|
|
77
77
|
}
|
|
78
78
|
const NativeLink = (props) => /* @__PURE__ */ jsx("a", __spreadValues({}, props));
|
|
@@ -82,6 +82,9 @@ function NaeilHeader(_a) {
|
|
|
82
82
|
resolveHref = (path) => `https://naeil.dev/${locale}${path === "/" ? "" : path}`,
|
|
83
83
|
brandHref,
|
|
84
84
|
brandSlot,
|
|
85
|
+
brandSuffix,
|
|
86
|
+
statusSlot,
|
|
87
|
+
appNavigation,
|
|
85
88
|
brandLinkProps,
|
|
86
89
|
brandLinkComponent,
|
|
87
90
|
onLocaleChange,
|
|
@@ -106,6 +109,9 @@ function NaeilHeader(_a) {
|
|
|
106
109
|
"resolveHref",
|
|
107
110
|
"brandHref",
|
|
108
111
|
"brandSlot",
|
|
112
|
+
"brandSuffix",
|
|
113
|
+
"statusSlot",
|
|
114
|
+
"appNavigation",
|
|
109
115
|
"brandLinkProps",
|
|
110
116
|
"brandLinkComponent",
|
|
111
117
|
"onLocaleChange",
|
|
@@ -148,41 +154,61 @@ function NaeilHeader(_a) {
|
|
|
148
154
|
const brandDestination = brandHref != null ? brandHref : resolveHref("/");
|
|
149
155
|
const BrandLink = brandLinkComponent != null ? brandLinkComponent : Link;
|
|
150
156
|
const _a2 = brandLinkProps != null ? brandLinkProps : {}, { onClick: onBrandClick } = _a2, brandProps = __objRest(_a2, ["onClick"]);
|
|
151
|
-
const toolbar = /* @__PURE__ */ jsx(
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
const toolbar = /* @__PURE__ */ jsx(
|
|
158
|
+
"div",
|
|
159
|
+
{
|
|
160
|
+
"data-app-toolbar": true,
|
|
161
|
+
className: "naeil-header-controls",
|
|
162
|
+
"data-naeil-chrome": toolbarSlot === void 0 ? "" : void 0,
|
|
163
|
+
lang: toolbarSlot === void 0 ? locale : void 0,
|
|
164
|
+
children: toolbarSlot !== void 0 ? toolbarSlot : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
165
|
+
/* @__PURE__ */ jsx(NaeilHeaderThemeControl, { locale }),
|
|
166
|
+
onLocaleChange && /* @__PURE__ */ jsx(NaeilHeaderLocaleControl, { locale, onLocaleChange }),
|
|
167
|
+
authSlot && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
168
|
+
/* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "naeil-header-separator" }),
|
|
169
|
+
authSlot
|
|
170
|
+
] })
|
|
171
|
+
] })
|
|
172
|
+
}
|
|
173
|
+
);
|
|
174
|
+
const logo = /* @__PURE__ */ jsx(Logo, { variant: "horizontal", size: 28, className: "block", label: null });
|
|
175
|
+
const brand = /* @__PURE__ */ jsx(
|
|
176
|
+
BrandLink,
|
|
177
|
+
__spreadProps(__spreadValues({
|
|
178
|
+
href: brandDestination,
|
|
179
|
+
"data-app-brand": true,
|
|
180
|
+
"aria-label": brandSlot == null ? ".naeil" : void 0
|
|
181
|
+
}, brandProps), {
|
|
182
|
+
onClick: (event) => {
|
|
183
|
+
onBrandClick == null ? void 0 : onBrandClick(event);
|
|
184
|
+
onNavigate == null ? void 0 : onNavigate(event, { id: "brand", href: brandDestination, label: ".naeil" });
|
|
185
|
+
},
|
|
186
|
+
children: brandSlot != null ? brandSlot : brandSuffix ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
187
|
+
logo,
|
|
188
|
+
/* @__PURE__ */ jsx("span", { className: "naeil-header-brand-suffix", children: brandSuffix })
|
|
189
|
+
] }) : logo
|
|
190
|
+
})
|
|
191
|
+
);
|
|
159
192
|
return /* @__PURE__ */ jsx(
|
|
160
193
|
Navigation,
|
|
161
194
|
__spreadProps(__spreadValues({}, props), {
|
|
162
195
|
"data-global-header": true,
|
|
163
196
|
"data-naeil-header": true,
|
|
197
|
+
"data-naeil-chrome": "",
|
|
198
|
+
lang: locale,
|
|
164
199
|
"data-naeil-ui-version": uiRelease.version,
|
|
165
200
|
"data-naeil-ui-contract": uiRelease.contractVersion,
|
|
166
201
|
Link,
|
|
167
202
|
currentHref,
|
|
168
203
|
onNavigate,
|
|
169
|
-
brand: /* @__PURE__ */
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
"data-app-brand": true,
|
|
174
|
-
"aria-label": brandSlot == null ? ".naeil" : void 0
|
|
175
|
-
}, brandProps), {
|
|
176
|
-
onClick: (event) => {
|
|
177
|
-
onBrandClick == null ? void 0 : onBrandClick(event);
|
|
178
|
-
onNavigate == null ? void 0 : onNavigate(event, { id: "brand", href: brandDestination, label: ".naeil" });
|
|
179
|
-
},
|
|
180
|
-
children: brandSlot != null ? brandSlot : /* @__PURE__ */ jsx(Logo, { variant: "horizontal", size: 28, className: "block", label: null })
|
|
181
|
-
})
|
|
182
|
-
),
|
|
204
|
+
brand: statusSlot != null ? /* @__PURE__ */ jsxs("div", { className: "naeil-header-brand-row", children: [
|
|
205
|
+
brand,
|
|
206
|
+
statusSlot
|
|
207
|
+
] }) : brand,
|
|
183
208
|
items: [
|
|
184
209
|
...showProjects ? [{ id: "projects", label: (_b2 = translations == null ? void 0 : translations.projects) != null ? _b2 : content.nav.projects, items: projectLinks, columns: projectLinks.length > 1 ? 2 : 1 }] : [],
|
|
185
210
|
...showBlog ? [{ id: "blog", label: (_c = translations == null ? void 0 : translations.blog) != null ? _c : content.nav.blog, href: resolveHref("/blog"), external: projectsExternal }] : [],
|
|
211
|
+
...appNavigation != null ? appNavigation : [],
|
|
186
212
|
...(subNav != null ? subNav : []).map((item) => __spreadProps(__spreadValues({}, item), { id: item.href }))
|
|
187
213
|
],
|
|
188
214
|
labels: labels != null ? labels : { navigation: content.nav.navigation, menu: content.nav.menu, close: content.nav.closeMenu },
|
|
@@ -38,11 +38,17 @@ interface NavigationProps extends Omit<react.ComponentPropsWithoutRef<"header">,
|
|
|
38
38
|
toolbar?: react.ReactNode;
|
|
39
39
|
mobileToolbar?: react.ReactNode;
|
|
40
40
|
mobileFooter?: react.ReactNode;
|
|
41
|
+
mobileOpen?: boolean;
|
|
42
|
+
onMobileOpenChange?: (open: boolean) => void;
|
|
41
43
|
/** Use the same product-owned frame class on header, footer and main. */
|
|
42
44
|
containerClassName?: string;
|
|
43
45
|
/** Preserve modified clicks; applications may preventDefault for client routing/guards. */
|
|
44
46
|
onNavigate?: (event: react.MouseEvent<HTMLAnchorElement>, item: NavigationLink) => void;
|
|
45
47
|
}
|
|
46
|
-
declare
|
|
48
|
+
declare const NavigationSheetAction: react.Context<((action: () => void) => {
|
|
49
|
+
menuClosed: () => void;
|
|
50
|
+
cancel: () => void;
|
|
51
|
+
}) | undefined>;
|
|
52
|
+
declare function Navigation({ brand, items, labels, Link, currentHref, toolbar, mobileToolbar, mobileFooter, mobileOpen: controlledOpen, onMobileOpenChange, containerClassName, onNavigate, className, ...props }: NavigationProps): react_jsx_runtime.JSX.Element;
|
|
47
53
|
|
|
48
|
-
export { Navigation, type NavigationGroup, type NavigationItem, type NavigationLink, type NavigationLinkComponent, type NavigationLinkProps, type NavigationProps };
|
|
54
|
+
export { Navigation, type NavigationGroup, type NavigationItem, type NavigationLink, type NavigationLinkComponent, type NavigationLinkProps, type NavigationProps, NavigationSheetAction };
|
|
@@ -40,13 +40,15 @@ const NativeLink = React.forwardRef(
|
|
|
40
40
|
return /* @__PURE__ */ jsx("a", __spreadValues({ ref }, props));
|
|
41
41
|
}
|
|
42
42
|
);
|
|
43
|
+
const NavigationSheetAction = React.createContext(void 0);
|
|
43
44
|
function DesktopGroup({
|
|
44
45
|
item,
|
|
45
46
|
active,
|
|
46
47
|
open,
|
|
47
48
|
anotherGroupOpen,
|
|
48
49
|
onOpenChange,
|
|
49
|
-
children
|
|
50
|
+
children,
|
|
51
|
+
chrome
|
|
50
52
|
}) {
|
|
51
53
|
var _a;
|
|
52
54
|
const hoverOpened = React.useRef(false);
|
|
@@ -125,7 +127,7 @@ function DesktopGroup({
|
|
|
125
127
|
),
|
|
126
128
|
/* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(
|
|
127
129
|
Popover.Content,
|
|
128
|
-
{
|
|
130
|
+
__spreadProps(__spreadValues({}, chrome), {
|
|
129
131
|
ref: panel,
|
|
130
132
|
"data-slot": "navigation-popover",
|
|
131
133
|
className: "ui-navigation-popover ui-popup",
|
|
@@ -151,7 +153,7 @@ function DesktopGroup({
|
|
|
151
153
|
event.preventDefault();
|
|
152
154
|
},
|
|
153
155
|
children: /* @__PURE__ */ jsx("nav", { "aria-label": item.label, className: "ui-navigation-menu-grid", children })
|
|
154
|
-
}
|
|
156
|
+
})
|
|
155
157
|
) })
|
|
156
158
|
]
|
|
157
159
|
}
|
|
@@ -167,6 +169,8 @@ function Navigation(_a) {
|
|
|
167
169
|
toolbar,
|
|
168
170
|
mobileToolbar,
|
|
169
171
|
mobileFooter,
|
|
172
|
+
mobileOpen: controlledOpen,
|
|
173
|
+
onMobileOpenChange,
|
|
170
174
|
containerClassName,
|
|
171
175
|
onNavigate,
|
|
172
176
|
className
|
|
@@ -179,18 +183,75 @@ function Navigation(_a) {
|
|
|
179
183
|
"toolbar",
|
|
180
184
|
"mobileToolbar",
|
|
181
185
|
"mobileFooter",
|
|
186
|
+
"mobileOpen",
|
|
187
|
+
"onMobileOpenChange",
|
|
182
188
|
"containerClassName",
|
|
183
189
|
"onNavigate",
|
|
184
190
|
"className"
|
|
185
191
|
]);
|
|
186
|
-
const [
|
|
192
|
+
const [internalOpen, setInternalOpen] = React.useState(false);
|
|
193
|
+
const mobileOpen = controlledOpen != null ? controlledOpen : internalOpen;
|
|
194
|
+
const state = React.useRef({ open: mobileOpen, controlled: controlledOpen !== void 0, onChange: onMobileOpenChange });
|
|
195
|
+
const mounted = React.useRef(false);
|
|
196
|
+
const pendingAction = React.useRef(void 0);
|
|
197
|
+
const cancelAction = React.useCallback(() => {
|
|
198
|
+
var _a2;
|
|
199
|
+
clearTimeout((_a2 = pendingAction.current) == null ? void 0 : _a2.timer);
|
|
200
|
+
pendingAction.current = void 0;
|
|
201
|
+
}, []);
|
|
202
|
+
React.useLayoutEffect(() => {
|
|
203
|
+
const reopening = mobileOpen && !state.current.open;
|
|
204
|
+
state.current = { open: mobileOpen, controlled: controlledOpen !== void 0, onChange: onMobileOpenChange };
|
|
205
|
+
if (reopening) cancelAction();
|
|
206
|
+
});
|
|
207
|
+
React.useLayoutEffect(() => {
|
|
208
|
+
mounted.current = true;
|
|
209
|
+
return () => {
|
|
210
|
+
mounted.current = false;
|
|
211
|
+
cancelAction();
|
|
212
|
+
};
|
|
213
|
+
}, [cancelAction]);
|
|
214
|
+
const setMobileOpen = React.useCallback((next, owner) => {
|
|
215
|
+
var _a2, _b2;
|
|
216
|
+
if (next) cancelAction();
|
|
217
|
+
if (next === state.current.open) return;
|
|
218
|
+
if (pendingAction.current !== owner) cancelAction();
|
|
219
|
+
if (!state.current.controlled) {
|
|
220
|
+
state.current.open = next;
|
|
221
|
+
setInternalOpen(next);
|
|
222
|
+
}
|
|
223
|
+
(_b2 = (_a2 = state.current).onChange) == null ? void 0 : _b2.call(_a2, next);
|
|
224
|
+
}, [cancelAction]);
|
|
225
|
+
const chrome = "data-naeil-chrome" in props ? { "data-naeil-chrome": "", lang: props.lang } : {};
|
|
187
226
|
const [openGroup, setOpenGroup] = React.useState(null);
|
|
188
227
|
const trigger = React.useRef(null);
|
|
189
228
|
const desktop = React.useRef(null);
|
|
229
|
+
const sheetContent = React.useRef(null);
|
|
230
|
+
function settleAction(flow) {
|
|
231
|
+
if (!mounted.current || pendingAction.current !== flow || state.current.open || !flow.menuClosed || !flow.sheetClosed || flow.timer !== void 0) return;
|
|
232
|
+
flow.timer = setTimeout(() => {
|
|
233
|
+
var _a2, _b2;
|
|
234
|
+
flow.timer = void 0;
|
|
235
|
+
if (!mounted.current || pendingAction.current !== flow) return;
|
|
236
|
+
if (state.current.open) {
|
|
237
|
+
cancelAction();
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (!flow.menuClosed || !flow.sheetClosed) return;
|
|
241
|
+
const target = [trigger.current, (_b2 = (_a2 = desktop.current) == null ? void 0 : _a2.parentElement) == null ? void 0 : _b2.querySelector(".ui-navigation-toolbar [data-naeil-account-trigger]")].find((element) => (element == null ? void 0 : element.isConnected) && element.getBoundingClientRect().width && element.getBoundingClientRect().height);
|
|
242
|
+
cancelAction();
|
|
243
|
+
if (!target) return;
|
|
244
|
+
target.focus();
|
|
245
|
+
flow.action();
|
|
246
|
+
}, 0);
|
|
247
|
+
}
|
|
248
|
+
const previousHref = React.useRef(currentHref);
|
|
190
249
|
React.useEffect(() => {
|
|
250
|
+
if (previousHref.current === currentHref) return;
|
|
251
|
+
previousHref.current = currentHref;
|
|
191
252
|
setMobileOpen(false);
|
|
192
253
|
setOpenGroup(null);
|
|
193
|
-
}, [currentHref]);
|
|
254
|
+
}, [currentHref, setMobileOpen]);
|
|
194
255
|
React.useEffect(() => {
|
|
195
256
|
const element = desktop.current;
|
|
196
257
|
if (!element || typeof ResizeObserver === "undefined") return;
|
|
@@ -200,7 +261,7 @@ function Navigation(_a) {
|
|
|
200
261
|
});
|
|
201
262
|
observer.observe(element);
|
|
202
263
|
return () => observer.disconnect();
|
|
203
|
-
}, []);
|
|
264
|
+
}, [setMobileOpen]);
|
|
204
265
|
const active = (item) => {
|
|
205
266
|
var _a2;
|
|
206
267
|
return (_a2 = item.active) != null ? _a2 : !item.external && item.href === currentHref;
|
|
@@ -220,8 +281,10 @@ function Navigation(_a) {
|
|
|
220
281
|
),
|
|
221
282
|
onClick: (event) => {
|
|
222
283
|
onNavigate == null ? void 0 : onNavigate(event, item);
|
|
223
|
-
if (event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey)
|
|
224
|
-
close
|
|
284
|
+
if (event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey) {
|
|
285
|
+
if (close === "mobile") setMobileOpen(false);
|
|
286
|
+
else if (close === "group") setOpenGroup(null);
|
|
287
|
+
}
|
|
225
288
|
},
|
|
226
289
|
children: [
|
|
227
290
|
item.icon && /* @__PURE__ */ jsx("span", { className: "ui-navigation-icon", "aria-hidden": "true", children: item.icon }),
|
|
@@ -256,6 +319,7 @@ function Navigation(_a) {
|
|
|
256
319
|
(item) => "items" in item ? /* @__PURE__ */ jsx(
|
|
257
320
|
DesktopGroup,
|
|
258
321
|
{
|
|
322
|
+
chrome,
|
|
259
323
|
item,
|
|
260
324
|
active: item.items.some(active),
|
|
261
325
|
open: openGroup === item.id,
|
|
@@ -264,7 +328,7 @@ function Navigation(_a) {
|
|
|
264
328
|
(current) => open ? item.id : current === item.id ? null : current
|
|
265
329
|
),
|
|
266
330
|
children: item.items.map(
|
|
267
|
-
(child) => link(child,
|
|
331
|
+
(child) => link(child, "group", true)
|
|
268
332
|
)
|
|
269
333
|
},
|
|
270
334
|
item.id
|
|
@@ -293,12 +357,26 @@ function Navigation(_a) {
|
|
|
293
357
|
),
|
|
294
358
|
/* @__PURE__ */ jsxs(
|
|
295
359
|
Dialog.Content,
|
|
296
|
-
{
|
|
360
|
+
__spreadProps(__spreadValues({}, chrome), {
|
|
361
|
+
ref: (element) => {
|
|
362
|
+
if (element) sheetContent.current = element;
|
|
363
|
+
},
|
|
297
364
|
"data-slot": "navigation-sheet",
|
|
298
365
|
className: "ui-navigation-sheet ui-popup",
|
|
299
366
|
"aria-describedby": void 0,
|
|
300
367
|
onCloseAutoFocus: (event) => {
|
|
301
368
|
var _a2, _b2;
|
|
369
|
+
if (controlledOpen !== void 0 && (!mounted.current || state.current.open)) {
|
|
370
|
+
event.preventDefault();
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const flow = pendingAction.current;
|
|
374
|
+
if (flow && event.target === flow.sheet) {
|
|
375
|
+
event.preventDefault();
|
|
376
|
+
flow.sheetClosed = true;
|
|
377
|
+
settleAction(flow);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
302
380
|
if (trigger.current && trigger.current.getBoundingClientRect().width === 0) {
|
|
303
381
|
event.preventDefault();
|
|
304
382
|
(_b2 = (_a2 = desktop.current) == null ? void 0 : _a2.querySelector("button, a")) == null ? void 0 : _b2.focus();
|
|
@@ -325,16 +403,34 @@ function Navigation(_a) {
|
|
|
325
403
|
(item) => "items" in item ? /* @__PURE__ */ jsxs("section", { "aria-label": item.label, children: [
|
|
326
404
|
/* @__PURE__ */ jsx("h3", { className: "ui-navigation-group-label", children: item.label }),
|
|
327
405
|
item.items.map(
|
|
328
|
-
(child) => link(child,
|
|
406
|
+
(child) => link(child, "mobile", true)
|
|
329
407
|
)
|
|
330
|
-
] }, item.id) : link(item,
|
|
408
|
+
] }, item.id) : link(item, "mobile")
|
|
331
409
|
)
|
|
332
410
|
}
|
|
333
411
|
),
|
|
334
|
-
(mobileToolbar != null ? mobileToolbar : toolbar) && /* @__PURE__ */ jsx("div", { className: "ui-navigation-mobile-toolbar", children:
|
|
412
|
+
(mobileToolbar != null ? mobileToolbar : toolbar) && /* @__PURE__ */ jsx("div", { className: "ui-navigation-mobile-toolbar", children: /* @__PURE__ */ jsx(NavigationSheetAction.Provider, { value: controlledOpen === void 0 ? void 0 : (action) => {
|
|
413
|
+
if (!mounted.current) return { menuClosed: () => {
|
|
414
|
+
}, cancel: () => {
|
|
415
|
+
} };
|
|
416
|
+
cancelAction();
|
|
417
|
+
const flow = { action, sheet: sheetContent.current, menuClosed: false, sheetClosed: false };
|
|
418
|
+
pendingAction.current = flow;
|
|
419
|
+
return {
|
|
420
|
+
menuClosed: () => {
|
|
421
|
+
if (!mounted.current || pendingAction.current !== flow) return;
|
|
422
|
+
flow.menuClosed = true;
|
|
423
|
+
setMobileOpen(false, flow);
|
|
424
|
+
settleAction(flow);
|
|
425
|
+
},
|
|
426
|
+
cancel: () => {
|
|
427
|
+
if (pendingAction.current === flow) cancelAction();
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
}, children: mobileToolbar != null ? mobileToolbar : toolbar }) }),
|
|
335
431
|
mobileFooter && /* @__PURE__ */ jsx("div", { className: "ui-navigation-mobile-footer", children: mobileFooter })
|
|
336
432
|
]
|
|
337
|
-
}
|
|
433
|
+
})
|
|
338
434
|
)
|
|
339
435
|
] })
|
|
340
436
|
] })
|
|
@@ -343,5 +439,6 @@ function Navigation(_a) {
|
|
|
343
439
|
);
|
|
344
440
|
}
|
|
345
441
|
export {
|
|
346
|
-
Navigation
|
|
442
|
+
Navigation,
|
|
443
|
+
NavigationSheetAction
|
|
347
444
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Application frame and shared navigation/footer
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The published registry baseline is **@naeil/ui 0.7.2**; the new Home profile is an **unpublished 0.8.0 candidate**. Install exact `@naeil/ui@0.7.2` for published consumers; hosted Docs and service activation have separate receipts. See [release identities and status](../package-release.md#publication-and-deployment) and [consumer setup](../design/v2-migration.md) for CSS, Tailwind sources and fonts.
|
|
4
4
|
|
|
5
5
|
## Choose the shared composition first
|
|
6
6
|
|
|
@@ -8,7 +8,8 @@ This maintained guide covers **published and registry-verified @naeil/ui 0.7.1**
|
|
|
8
8
|
| --- | --- |
|
|
9
9
|
| The actual naeil.dev brand, project catalog and controls | `NaeilHeader` from `@naeil/ui/naeil-header` |
|
|
10
10
|
| Owned-service login/account/logout presentation | `NaeilHeaderAccountControl` from `@naeil/ui/naeil-header` in `authSlot` |
|
|
11
|
-
|
|
|
11
|
+
| Owned naeil service with product menu data (0.8.0 candidate) | Complete `NaeilHeader`, with `appNavigation`, catalog/blog hidden, optional `brandSuffix` and `statusSlot` |
|
|
12
|
+
| A different product menu without canonical naeil chrome | `Navigation` from `@naeil/ui/ui` |
|
|
12
13
|
| Owned naeil service footer | `NaeilFooter` from `@naeil/ui/naeil-footer` |
|
|
13
14
|
| Different product footer or explicit content override | `SiteFooter` from `@naeil/ui/ui` |
|
|
14
15
|
| An existing Next/next-intl consumer | Keep its `Nav` / `Footer` adapters |
|
|
@@ -78,7 +79,9 @@ export function ApplicationLayout({ children, locale, Link, currentHref,
|
|
|
78
79
|
|
|
79
80
|
`NaeilHeader` is fixed; reserve its 65px height and account for it in sticky areas and focus/anchor scroll margins. Default destinations are official naeil.dev URLs. Preserve routing/unsaved-change guards using the [header contract](naeil-header.md) and [footer interception contract](naeil-footer.md); a Link adapter must handle absolute official and GitHub destinations as well as local routes. Only pass `onLocaleChange` when the application supports its complete language set.
|
|
80
81
|
|
|
81
|
-
For a different product menu, use the [generic Navigation composition](navigation.md) with the same main/footer structure. Generic Navigation is in document flow by default and needs no fixed-header clearance. The [standalone React example](https://github.com/naeil-dev/naeil-ui/tree/main/examples/react) exercises the installed header, narrow form and SiteFooter together.
|
|
82
|
+
For owned naeil product navigation, keep the whole header and pass menu data through `appNavigation`; see the [Home profile and host conditions](naeil-header.md#host-typography-and-mobile-handoff-080-candidate). `mobileOpen`/`onMobileOpenChange` can coordinate the sheet with a consumer confirmation, while busy state and request locking remain above the header. For a different product menu without canonical chrome, use the [generic Navigation composition](navigation.md) with the same main/footer structure. Generic Navigation is in document flow by default and needs no fixed-header clearance. The [standalone React example](https://github.com/naeil-dev/naeil-ui/tree/main/examples/react) exercises the installed header, narrow form and SiteFooter together.
|
|
83
|
+
|
|
84
|
+
An empty desktop menu preserves B1's limitation: resizing alone leaves the sheet open; Escape/close/overlay/route change dismiss it. Controlled-mobile account callbacks wait for both closing layers and their focus handoff; desktop and uncontrolled-mobile account callbacks retain B1's selection-time behavior. A consumer confirmation owns cancellation focus, including a visible desktop account fallback after resize.
|
|
82
85
|
|
|
83
86
|
## Deliberate overrides
|
|
84
87
|
|
|
@@ -119,7 +122,7 @@ Owned-service checks also verify canonical header/footer destinations, rendered
|
|
|
119
122
|
|
|
120
123
|
A blanket ban on `<header>`, `<nav>` or `<footer>` would also reject valid section landmarks and adapters. Review the root composition and its rendered output instead. This repository checks packed React/Next examples; external applications still need their own upgrades, adapter migrations and CI. Publication and deployment are separate work.
|
|
121
124
|
|
|
122
|
-
See [canonical footer content and save interception](naeil-footer.md). Version 0.7.
|
|
125
|
+
See [canonical footer content and save interception](naeil-footer.md). Version 0.7.2 includes package/contract identity; registry publication, hosted Docs and per-service operational rollout have separate receipts.
|
|
123
126
|
|
|
124
127
|
## Shared naeil.dev login (services on `*.naeil.dev`)
|
|
125
128
|
|