@naeil/ui 0.7.1 → 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/CHANGELOG.md +10 -1
- package/README.md +15 -10
- package/dist/{chunk-QQQQZEPV.js → chunk-KPE4KKYM.js} +111 -14
- package/dist/{chunk-OLKTEYLS.js → chunk-ULZPJI5X.js} +103 -73
- package/dist/{chunk-VWIUN35X.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-content.d.ts +1 -25
- package/dist/source/components/naeil-header-content.js +0 -28
- package/dist/source/components/naeil-header.d.ts +8 -2
- package/dist/source/components/naeil-header.js +50 -24
- 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/README.md +3 -3
- package/docs/components/application-layout.md +48 -13
- package/docs/components/naeil-footer.md +2 -2
- package/docs/components/naeil-header.md +37 -7
- package/docs/design/v2-migration.md +11 -10
- package/docs/package-release.md +44 -14
- package/package.json +1 -1
- package/src/styles/components.css +113 -0
|
@@ -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
|
# Component usage guides
|
|
2
2
|
|
|
3
|
-
These guides
|
|
3
|
+
These maintained guides cover **published and registry-verified @naeil/ui 0.7.1**: canonical footer and release identity from the unpublished 0.7.0 candidate, plus shared account presentation. The current published baseline is 0.7.1; see [release policy and identities](../package-release.md#publication-and-deployment). [Setup, themes, fonts and shared responsibilities](common.md). Run `pnpm storybook` for the current 17 Docs pages (15 UI and 2 Brand) and choose Docs or Usage. The [hosted Storybook](https://naeil-dev.github.io/naeil-ui/) has recorded [0.5.0 verification](https://github.com/naeil-dev/naeil-ui/releases/tag/v0.5.0); each subsequent deployment requires its own receipt. Select chooses a value; DropdownMenu chooses an action.
|
|
4
4
|
|
|
5
5
|
Start application layouts with the [shared navigation/footer selection and frame guide](application-layout.md). It includes a starter, the 1600px frame contract and instructions to place in consumer repositories so agents reuse the shared implementation.
|
|
6
6
|
|
|
@@ -22,8 +22,8 @@ Start application layouts with the [shared navigation/footer selection and frame
|
|
|
22
22
|
| Navigation | [Navigation](./navigation.md) | UI / Navigation / Usage |
|
|
23
23
|
| SiteFooter | [SiteFooter](./navigation.md) | UI / SiteFooter / Usage |
|
|
24
24
|
| NaeilFooter | [NaeilFooter](./naeil-footer.md) | Brand / NaeilHeader / ReferenceFull, ReferenceCompact |
|
|
25
|
-
| NaeilHeader | [NaeilHeader](./naeil-header.md) | Brand / NaeilHeader / Usage, JapaneseApplication |
|
|
25
|
+
| NaeilHeader and account control | [NaeilHeader](./naeil-header.md) | Brand / NaeilHeader / Usage, JapaneseApplication, AccountStates |
|
|
26
26
|
|
|
27
27
|
[Field, native radio, Select and action composition](./composition.md) explains consumer state and semantic choices. [Demand decision](../design/component-demand.md) records why these two families were added.
|
|
28
28
|
|
|
29
|
-
Use opt-in `@naeil/ui/naeil-header` for the actual naeil.dev branded header
|
|
29
|
+
Use opt-in `@naeil/ui/naeil-header` for the actual naeil.dev branded header and its `NaeilHeaderAccountControl` in `authSlot`, paired with `@naeil/ui/naeil-footer` on owned services. Consumers supply routing/save guards, theme providers and authentication state/actions; official artwork remains URL-referenced. Canonical footer and account examples are part of existing stories, without additional Docs families. Use generic `Navigation` and `SiteFooter` from `/ui` for different products. [Navigation / SiteFooter](navigation.md) also documents the compatibility adapters.
|
|
@@ -1,18 +1,20 @@
|
|
|
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
|
|
|
7
7
|
| Application need | Use |
|
|
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
|
+
| 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` |
|
|
11
13
|
| Owned naeil service footer | `NaeilFooter` from `@naeil/ui/naeil-footer` |
|
|
12
14
|
| Different product footer or explicit content override | `SiteFooter` from `@naeil/ui/ui` |
|
|
13
15
|
| An existing Next/next-intl consumer | Keep its `Nav` / `Footer` adapters |
|
|
14
16
|
|
|
15
|
-
Choose one header per application frame. Supply menu data, links, route guards, authentication and supported locale state through props/slots. Local adapters are appropriate; copies of the menu, responsive sheet,
|
|
17
|
+
Choose one header per application frame. Supply menu data, links, route guards, authentication and supported locale state through props/slots. Owned services pass session state and guarded callbacks to the shared account control. Local adapters are appropriate; copies of the menu, responsive sheet, account presentation or footer implementation are not. Extend the shared component when a reusable behavior is missing.
|
|
16
18
|
|
|
17
19
|
## Width contract
|
|
18
20
|
|
|
@@ -39,30 +41,47 @@ This keeps a centered frame from shifting when a classic scrollbar appears. The
|
|
|
39
41
|
|
|
40
42
|
```tsx
|
|
41
43
|
import type { ReactNode } from "react";
|
|
42
|
-
import { NaeilHeader
|
|
44
|
+
import { NaeilHeader, NaeilHeaderAccountControl,
|
|
45
|
+
type NaeilHeaderAccount, type NaeilHeaderProps } from "@naeil/ui/naeil-header";
|
|
43
46
|
import { NaeilFooter } from "@naeil/ui/naeil-footer";
|
|
44
47
|
|
|
45
48
|
// Render inside the existing next-themes ThemeProvider.
|
|
46
|
-
//
|
|
47
|
-
|
|
49
|
+
// The app owns session state, its guarded logout and pending request lock.
|
|
50
|
+
type ApplicationLayoutProps = {
|
|
51
|
+
children: ReactNode;
|
|
52
|
+
locale: NaeilHeaderProps["locale"];
|
|
53
|
+
Link: NaeilHeaderProps["Link"];
|
|
54
|
+
currentHref: string;
|
|
55
|
+
account: NaeilHeaderAccount;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export function ApplicationLayout({ children, locale, Link, currentHref,
|
|
59
|
+
account }: ApplicationLayoutProps) {
|
|
48
60
|
return <>
|
|
49
|
-
<NaeilHeader locale=
|
|
61
|
+
<NaeilHeader locale={locale} Link={Link} currentHref={currentHref}
|
|
62
|
+
authSlot={<NaeilHeaderAccountControl locale={locale} Link={Link}
|
|
63
|
+
account={account} />} />
|
|
50
64
|
<main className="ui-app-frame"
|
|
51
65
|
style={{ marginTop: "calc(var(--ui-navigation-height) + 1px)" }}>
|
|
52
66
|
{children}
|
|
53
67
|
</main>
|
|
54
|
-
<NaeilFooter locale=
|
|
68
|
+
<NaeilFooter locale={locale} variant="compact" Link={Link} />
|
|
55
69
|
</>;
|
|
56
70
|
}
|
|
57
71
|
|
|
58
|
-
<ApplicationLayout
|
|
72
|
+
<ApplicationLayout locale="en" Link={GuardedLink} currentHref={pathname}
|
|
73
|
+
account={account}>
|
|
59
74
|
<section data-ui-layout="settings">{/* Settings form */}</section>
|
|
60
75
|
</ApplicationLayout>
|
|
61
76
|
```
|
|
62
77
|
|
|
63
|
-
`
|
|
78
|
+
`GuardedLink`, `pathname` and `account` come from the consumer. Use `{ status: 'signed-out', loginHref }` or `{ status: 'signed-in', onLogout, busy, displayName, email }` for `account`; the app handles async cancellation, failure and retry. Keep pending state and the request lock above `NaeilHeader`, since desktop/mobile slot contents mount separately. See the [account contract](naeil-header.md#account-presentation).
|
|
64
79
|
|
|
65
|
-
|
|
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.
|
|
81
|
+
|
|
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.
|
|
66
85
|
|
|
67
86
|
## Deliberate overrides
|
|
68
87
|
|
|
@@ -88,16 +107,32 @@ Put this in the **consumer application's** `AGENTS.md` (or equivalent repository
|
|
|
88
107
|
## Shared application layout
|
|
89
108
|
- Read the installed @naeil/ui/DESIGN.md and docs/components/application-layout.md before UI layout work.
|
|
90
109
|
- Use @naeil/ui/naeil-header for the canonical naeil header; use Navigation from @naeil/ui/ui for a different product menu. Use NaeilFooter from @naeil/ui/naeil-footer for owned service footers; SiteFooter remains the generic primitive. Keep existing Next Nav/Footer compatibility adapters when applicable.
|
|
110
|
+
- Put NaeilHeaderAccountControl from @naeil/ui/naeil-header in authSlot for owned apps. Supply real session state, the busy flag and guarded logout callback. Keep pending state and the request lock above the header so desktop/mobile copies share them; do not copy account/avatar/menu markup.
|
|
91
111
|
- Local wrappers supply menu data, routing/guards, locale, auth and slots. Do not copy the menu, mobile sheet, controls or footer implementation. Extend the shared component for reusable missing behavior.
|
|
92
112
|
- Use ui-app-frame on main; navigation/footer own their internal frames. Keep reading/settings/short-form sections inside that frame. Apply frame overrides consistently across routes.
|
|
93
113
|
- Check the installed package version and CSS before creating a replacement for an apparently missing component.
|
|
94
|
-
- Verify actual shared navigation/footer rendering and stable anchors across routes, themes and supported sizes. An import
|
|
114
|
+
- Verify actual shared navigation/footer rendering and stable anchors across routes, themes and supported sizes, including signed-out, signed-in, menu, busy, cancellation/failure and retry states. An import or anonymous-only check is not adoption evidence. Record intentional route exceptions.
|
|
95
115
|
```
|
|
96
116
|
|
|
97
117
|
## Adoption checks
|
|
98
118
|
|
|
99
119
|
For each consumer, record its installed version, layout adapter, shared imports, CSS entrypoints and intentional exceptions. In that consumer's browser CI, check that expected application routes render `[data-slot="navigation"]` and `[data-slot="site-footer"]`, and that anchors remain stable between wide lists and narrow forms. Include narrow/zoomed screens, localized labels and tall content that introduces a scrollbar. Keep table overflow local.
|
|
100
120
|
|
|
121
|
+
Owned-service checks also verify canonical header/footer destinations, rendered package/contract identity and shared account states. Inspect both signed-in and signed-out chrome, open the account menu, and exercise busy, cancel/failure and retry without duplicate requests when the mobile sheet is reopened or the viewport changes.
|
|
122
|
+
|
|
101
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.
|
|
102
124
|
|
|
103
|
-
See [canonical footer content and save interception](naeil-footer.md). 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.
|
|
126
|
+
|
|
127
|
+
## Shared naeil.dev login (services on `*.naeil.dev`)
|
|
128
|
+
|
|
129
|
+
Live since 2026-10-22 for naeil.dev, abestimate.naeil.dev and jev.naeil.dev. A new service joins the same login instead of running its own session, relay or password. Home Console (`:2455` on the tailnet) is outside the cookie scope.
|
|
130
|
+
|
|
131
|
+
- **Session:** read the default `@supabase/ssr` 0.9.0 cookie `sb-dathsxdsaxooifnxjket-auth-token` (and its `.0`, `.1` chunks) with `Domain=.naeil.dev; Path=/; Secure; SameSite=Lax`. Write refreshed chunks with the same options; never set a host-only copy.
|
|
132
|
+
- **Identity and access:** on the server call `supabase.auth.getUser()`, then `rpc('access_decision', { p_service_id: '<service>' })` with that session's access token. `allowed` → continue; `not_granted` / `member_inactive` / `service_inactive` → 403 page; terminal session errors → treat as signed out; other failures → 503.
|
|
133
|
+
- **Signed out:** redirect (307) to `https://naeil.dev/login?next=<absolute URL>`. Add the service origin to the site's `canonicalRedirect` allowlist (`src/lib/auth/redirect.ts`).
|
|
134
|
+
- **Logout:** `signOut({ scope: 'local' })`, then expire every shared cookie chunk with the same Domain/Path. All services see the user as signed out on the next request.
|
|
135
|
+
- **Header:** `NaeilHeaderAccountControl` in `authSlot`, linked to the login URL and the logout callback.
|
|
136
|
+
- **Onboarding:** register the service id in the central catalog, mark it verified, and grant users from Home Console (`console_set_access`).
|
|
137
|
+
|
|
138
|
+
Reference code: Estimate `src/server/naeil-shared-session.ts`; site `src/lib/auth/site-session.ts` and `cookie-domain.ts`. Commit `c076391` handles a stale pre-2026-10-17 host-only apex cookie that otherwise breaks the OAuth callback.
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# NaeilFooter — canonical service footer
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Included in **published and registry-verified 0.7.1**, from the unpublished 0.7.0 candidate. Install exact `@naeil/ui@0.7.1` from npm; hosted Docs and service activation have separate receipts. See [release identities and publication status](../package-release.md#publication-and-deployment). React-only opt-in entry:
|
|
4
4
|
|
|
5
5
|
```tsx
|
|
6
6
|
import { NaeilFooter } from '@naeil/ui/naeil-footer';
|
|
7
7
|
<NaeilFooter locale="ja" variant="compact" Link={GuardedLink} />
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
-
Use with [NaeilHeader](naeil-header.md) on owned user-facing services. Both `full` and `compact` contain the .naeil home link, Projects, Blog and the official naeil GitHub link. Full also displays the existing tagline. Compact reduces spacing/description, not the destinations. No social account or legal claim is invented. The website's former generic X homepage icon is not a canonical account link.
|
|
10
|
+
Use with [NaeilHeader and its account control](naeil-header.md) on owned user-facing services. Both `full` and `compact` contain the .naeil home link, Projects, Blog and the official naeil GitHub link. Full also displays the existing tagline. Compact reduces spacing/description, not the destinations. No social account or legal claim is invented. The website's former generic X homepage icon is not a canonical account link.
|
|
11
11
|
|
|
12
12
|
`locale` selects `en`, `ko`, or `ja`. `resolveHref(path)` defaults to official localized naeil.dev destinations; a local website router may resolve unlocalized paths itself. `Link` handles **every** canonical anchor, including absolute GitHub URLs. It must forward href, target, rel, onClick, className and children. GitHub opens a new tab with noopener/noreferrer. The shared implementation does not itself trigger navigation or await saves.
|
|
13
13
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# NaeilHeader — branded whole-header composition
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The published registry baseline is **@naeil/ui 0.7.2**. This guide also describes the **unpublished 0.8.0 candidate** APIs below. Install exact `@naeil/ui@0.7.2` for the published API; the new Home props require the reviewed 0.8.0 archive. Hosted Docs and service activation have separate receipts. See [release identities and publication status](../package-release.md#publication-and-deployment). Import this opt-in React-only entry when an application inherits the actual naeil.dev header. Generic `Navigation` from `/ui` remains available for different product menus.
|
|
4
4
|
|
|
5
5
|
```tsx
|
|
6
6
|
import { NaeilHeader, NaeilHeaderAccountControl } from '@naeil/ui/naeil-header';
|
|
@@ -16,13 +16,13 @@ import '@naeil/ui/globals.css';
|
|
|
16
16
|
|
|
17
17
|
Load shared styles once. Apps that already own their Tailwind/base styles may import both `@naeil/ui/theme.css` and `@naeil/ui/components.css` instead of globals. Tailwind 4 consumers must register the installed `dist` with `@source`; see [setup](../design/v2-migration.md#시작하기).
|
|
18
18
|
|
|
19
|
-
The module owns the brand,
|
|
19
|
+
The module owns the brand, single-project naeil-ui catalog and header-only translations, project artwork references/status, theme/optional locale controls, toolbar order, fixed positioning, branded frame and Radix desktop/mobile behavior. Do not copy menu JSX or styles into a consumer. The website's `NavWrapper` imports this same module and supplies locale/router/auth state; estimate consumes the built package entry. Legacy `Nav` retains its Next/next-intl compatibility API.
|
|
20
20
|
|
|
21
21
|
## Consumer contract
|
|
22
22
|
|
|
23
23
|
Consumers provide an existing next-themes provider, routing/dirty-save guard, current locale and real authentication behavior. Default destinations point to `https://naeil.dev/{locale}`; the site's localized Link instead uses `resolveHref={path => path}`. A supplied `Link` handles default official destinations, including cross-origin URLs. `onNavigate` can intercept brand and menu links; honor modified clicks and target behavior. Explicit `external: true` project overrides and `projectsExternal` use native anchors, so intercept them with `onNavigate` when a dirty guard is required.
|
|
24
24
|
|
|
25
|
-
Only provide `onLocaleChange` when the application's interface supports all three canonical locales (`en`, `ko`, `ja`). Omitting it removes the locale control; the component does not create application translations or shared preferences. `authSlot` uses `NaeilHeaderAccountControl` in owned applications: a
|
|
25
|
+
Only provide `onLocaleChange` when the application's interface supports all three canonical locales (`en`, `ko`, `ja`). Omitting it removes the locale control; the component does not create application translations or shared preferences. `authSlot` uses `NaeilHeaderAccountControl` in owned applications: a shared 44px neutral account trigger and localized Radix logout menu. Consumers supply their real session state/callbacks, not alternative account markup. The legacy slot remains public for deliberate compatibility overrides. It does not integrate site SSO, share credentials or create accounts. Theme preferences remain consumer-origin state. Without a working provider, rendering alone does not establish theme switching.
|
|
26
26
|
|
|
27
27
|
| Prop | Contract |
|
|
28
28
|
| --- | --- |
|
|
@@ -33,6 +33,10 @@ Only provide `onLocaleChange` when the application's interface supports all thre
|
|
|
33
33
|
| `authSlot` | Canonical `NaeilHeaderAccountControl`, with consumer-owned auth state/actions. Legacy arbitrary markup remains supported. |
|
|
34
34
|
| `brandHref`, `brandSlot`, `brandLinkProps`, `brandLinkComponent` | Explicit brand destination/content/anchor overrides; default accessible brand name is `.naeil`. |
|
|
35
35
|
| `projects`, `projectsExternal`, `showProjects`, `showBlog` | Legacy catalog/visibility/native-link overrides; defaults use the complete canonical catalog. |
|
|
36
|
+
| `appNavigation` (0.8.0) | `NavigationItem[]` passed unchanged after catalog/blog and before flat `subNav`. Owned product menus use this with `showProjects={false} showBlog={false}`. |
|
|
37
|
+
| `brandSuffix` (0.8.0) | Product qualifier inside the brand link; the package owns its 14px type, 4px gap and optical alignment. Ignored when `brandSlot` replaces the complete content. Set `brandLinkProps["aria-label"]` for a product name. |
|
|
38
|
+
| `statusSlot` (0.8.0) | Exactly once, outside and 8px after the brand link at every width. The consumer owns the content and accessible status. No wrapper is added when omitted. |
|
|
39
|
+
| `mobileOpen`, `onMobileOpenChange` (0.8.0) | Controlled/uncontrolled sheet state, also on generic Navigation. Omit `mobileOpen` for internal state; a callback alone observes it. Accepted transitions notify once. In controlled mode callbacks request a change against the committed prop, so another close request is reported if the parent rejects or delays the previous one; an external prop update emits no request. Route changes, ordinary links, close/overlay/Escape dismiss. Desktop crossing dismisses when the desktop navigation gains width; an empty menu retains B1's open sheet until Escape/close/overlay/route change. Modified/middle clicks preserve it; nested menus consume the first Escape. |
|
|
36
40
|
| `subNav`, `currentProjectKey`, `currentProjectHref` | Legacy extra links and current-project destination. |
|
|
37
41
|
| `translations`, `labels` | Legacy header/project lookup overrides and navigation/menu/close accessible labels. |
|
|
38
42
|
| `toolbarSlot` | Replaces the complete default theme/locale/auth toolbar. |
|
|
@@ -47,20 +51,46 @@ Standard header attributes and the remaining inherited [Navigation props](naviga
|
|
|
47
51
|
- `{ status: 'signed-out', loginHref }` renders the shared login link through that adapter.
|
|
48
52
|
- `{ status: 'signed-in', onLogout, busy?, displayName?, email? }` renders the same account icon in every app; optional identity appears only inside its menu. There is no shared identity or avatar fetch.
|
|
49
53
|
- `onLogout` starts the app's guarded action; the app handles async errors, cancellation, session endpoints and redirects. `busy` disables the menu action while keeping the trigger available, with `aria-busy`, for inspection and focus restoration. Cancel/failure clears busy and permits retry.
|
|
50
|
-
-
|
|
54
|
+
- Desktop and uncontrolled-mobile account controls retain B1: `onLogout` runs during selection, then Radix closes the account menu and restores its trigger; the mobile sheet may remain open during a failed action. A controlled mobile sheet instead defers the callback until its account menu and sheet finish closing, focuses a surviving visible header control, and invokes the callback once. Reopening the sheet or account menu, a new sheet dismissal request, or unmounting the header cancels the queued action. The account menu uses the existing Radix overlay/inert implementation.
|
|
55
|
+
|
|
56
|
+
While a controlled parent keeps `mobileOpen=true`, its rejection and delayed acceptance are indistinguishable. A later `false` prop without an intervening cancellation accepts the queued selection; assigning `true` when it is already `true` creates no observable reopen transition.
|
|
51
57
|
|
|
52
58
|
Keep errors visible in the consumer's recovery UI or toast. The package never calls an authentication endpoint. Compare signed-in and signed-out render states separately; anonymous-only acceptance cannot establish authenticated parity. `AccountStates` is a synthetic Storybook example for pending/cancel/error/retry.
|
|
53
59
|
|
|
60
|
+
Own pending state and the in-flight request lock above `NaeilHeader`. Navigation mounts desktop and mobile slot contents separately, and closing the mobile sheet unmounts its copy. Pass the same controller to both through `authSlot`; test reopening the sheet and changing viewport during a pending request. State inside an independently mounted slot component can enable duplicate requests.
|
|
61
|
+
|
|
54
62
|
## Frame and content clearance
|
|
55
63
|
|
|
56
64
|
The fixed header is 65px: the current 64px navigation token plus a 1px border. Clear it in the application's main, sticky regions and focus/anchor scroll margins with `calc(var(--ui-navigation-height) + 1px)`. The header, main and footer now share a 1600px maximum outer frame, including responsive `clamp(16px, 2.4vw, 40px)` gutters. Put `ui-app-frame` on main and reading/settings or short-form sections inside it; the header and shared footer apply their own internal frame. Do not wrap them in another padded frame. Apply deliberate `--ui-width-frame` / `--ui-frame-gutter` overrides on a common ancestor. Remove old `max-w-7xl` or `px-6` overrides to adopt the new defaults. This width update supersedes the previous 1280px/24px branded frame. See the [application layout starter](application-layout.md).
|
|
57
65
|
|
|
58
66
|
## Artwork and package boundary
|
|
59
67
|
|
|
60
|
-
The
|
|
68
|
+
The 0.7.2 default catalog contains naeil-ui and references only `https://naeil.dev/images/whale.png`. Rendering that decorative image requires network access to the official origin. Its bytes are not redistributed in npm; [third-party notices](../../THIRD_PARTY_NOTICES.md) retain unresolved artwork provenance and redistribution limits. Brand marks are not relicensed by the repository MIT license. Captions and links remain usable when images fail.
|
|
61
69
|
|
|
62
|
-
This entry requires no Next.js, next-intl, site session/auth backend, full application message catalog, hero or site public folder. Generic `/ui` imports do not import its catalog.
|
|
70
|
+
This entry requires no Next.js, next-intl, site session/auth backend, full application message catalog, hero or site public folder. Generic `/ui` imports do not import its catalog. Storybook provides **Brand / NaeilHeader / Docs, Usage, JapaneseApplication, AccountStates, ReferenceFull, ReferenceCompact**; the Japanese example omits unsupported locale switching and supplies application state to the shared account control. Local examples do not establish hosted deployment.
|
|
63
71
|
|
|
64
72
|
Verify actual packed consumption, Storybook behavior and rendered comparisons at matching viewport/language/theme. Source imports alone do not prove visual parity. Native Safari, devices, assistive technology, full OAuth and unresolved accessibility observations retain the [documented support limits](../design/public-ui-support.md).
|
|
65
73
|
|
|
66
|
-
Pair owned-service headers with [NaeilFooter](naeil-footer.md). The 0.7.
|
|
74
|
+
Pair owned-service headers with [NaeilFooter](naeil-footer.md). The 0.7.2 source is `40a3928e24cd2f0141be3e605eed0a75260938fd`. Both 0.7.2 and the 0.8.0 candidate emit package version and chrome contract `1`; archive identities, maintained guidance and hosted Docs remain separate evidence.
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
## Host typography and mobile handoff (0.8.0 candidate)
|
|
78
|
+
|
|
79
|
+
Import either `globals.css` or `theme.css` plus `components.css`, and register the installed `dist` with Tailwind `@source`. Header/footer roots and the actual navigation popover, sheet, account menu and locale menu Content elements carry `data-naeil-chrome` and `lang=locale`; the locale Select Content also carries `data-naeil-locale-menu`. Generic Navigation/Select/DropdownMenu do not acquire these markers. Do not identify chrome by option text or by the presence of a Japanese body option.
|
|
80
|
+
|
|
81
|
+
Marker-and-language-scoped final typography uses a package-local font stack and explicit sizes, weights, line heights, letter spacing and smoothing. It supports a host `@theme` / `@theme inline` with different text tokens/font stacks and unlayered element/role rules at Home's specificity, including `(0,3,0)` `:root[attr] [role="menuitem"]` and Home's `[role="dialog"] p` rules. Host rules can load before or after either supported package CSS entry; owned text declarations outrank them. Rules with `!important` or IDs aimed at chrome are outside this guarantee. Ordinary body Select/menu/dialog typography remains host-owned; custom slot content remains consumer-owned. Font files are still consumer-supplied; Japanese chrome and portals use Noto Sans JP even when the document language differs.
|
|
82
|
+
|
|
83
|
+
A controlled mobile sheet defers its shared account action until the account menu and sheet finish closing and restore a surviving header control. The consumer still owns dirty state, confirmation, logout, busy state and the request lock; `onLogout` must tolerate this asynchronous handoff. When a confirmation saves its opener, its restoration policy must also handle a viewport change hiding that opener. Verify normal and reduced motion, cancellation after a desktop transition, pending requests, reopen and retry in the actual application; the candidate's unresolved acceptance gates are not a production guarantee.
|
|
84
|
+
|
|
85
|
+
The empty-menu desktop exception is intentional B1 compatibility: a zero-width desktop navigation cannot signal the crossing, so the sheet stays open. Escape, its close button, the overlay or a `currentHref` change still close it. Home owns the confirmation's desktop cancel fallback to the visible row account trigger; the package supplies a visible, focusable control and does not implement the confirmation's restoration policy.
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
<NaeilHeader locale={locale} currentHref={pathname} Link={GuardedLink}
|
|
89
|
+
showProjects={false} showBlog={false} appNavigation={navigationItems}
|
|
90
|
+
brandHref="/" brandSuffix="Home" brandLinkProps={{ 'aria-label': 'Naeil Home' }}
|
|
91
|
+
statusSlot={readinessStatus} mobileOpen={mobileOpen} onMobileOpenChange={setMobileOpen}
|
|
92
|
+
onLocaleChange={changeLanguage}
|
|
93
|
+
authSlot={<NaeilHeaderAccountControl locale={locale} account={account} />} />
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Keep the default theme/locale/separator/account toolbar for this profile; `toolbarSlot` is a compatibility override, not the Home migration path. Omitting all new props preserves the 0.7.2 canonical profile, apart from markers, language correction, release identity and Radix-generated IDs. SVG/Logo IDs and consumer-supplied IDs are not normalized away. See the `docs/superpowers/specs/2026-10-28-canonical-home-chrome.md` acceptance contract for gates and the four allowed Home differences.
|
|
@@ -2,21 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
이 안내는 `@naeil/ui`의 기본 모양과 재사용 규칙을 설명한다. 패키지 소비와 소비 사이트 배포는 별도 작업이다.
|
|
4
4
|
|
|
5
|
-
이 안내는
|
|
5
|
+
**0.7.1 npm 게시·registry 검증 완료 — 2026-10-23.** 현재 게시된 기준 버전은 **0.7.1**이다. 이 유지보수 안내는 canonical `NaeilFooter`, `NaeilHeaderAccountControl`, `/release` 식별자와 기존 1600px 프레임을 함께 다룬다. 0.7.0은 미게시 후보이며, 처음 게시된 0.7.x 버전은 0.7.1이다. 독립된 새 React/Next registry 설치에서 동결 파일 139개와 타입·프로덕션 빌드·브라우저 검증이 통과했다. hosted Docs와 상시 배포 활성화는 별도 실행 기록으로 확인한다. UI v2는 디자인 세대 이름이며 npm 2.0을 뜻하지 않는다. 동결 패키지 소스·archive, 별도 검토한 Docs 소스와 실제 게시·배포 결과는 [릴리스 정책](../package-release.md#publication-and-deployment)에서 구분한다.
|
|
6
6
|
|
|
7
7
|
## 시작하기
|
|
8
8
|
|
|
9
|
-
React 19/ReactDOM 19와 Tailwind 4 앱에서 `/ui` 또는 선택적 `/naeil-header`를 사용한다. API 키와 Next.js는 필요 없다.
|
|
9
|
+
React 19/ReactDOM 19와 Tailwind 4 앱에서 `/ui` 또는 선택적 `/naeil-header`·`/naeil-footer`를 사용한다. API 키와 Next.js는 필요 없다. 검증한 registry 버전을 고정해 설치한다.
|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
|
-
pnpm add --save-exact @naeil/ui@0.
|
|
12
|
+
pnpm add --save-exact @naeil/ui@0.7.1
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
registry의 0.7.1 archive는 동결 릴리스 archive와 바이트가 일치한다. archive에 포함된 안내는 준비 시점의 기록이므로 현재 설치에는 이 유지보수 안내를 따른다. 로컬 개발에서는 저장소에서 `pnpm build:pkg`와 `npm pack --ignore-scripts --pack-destination /tmp`를 실행하고 `/tmp/naeil-ui-0.7.1.tgz`를 설치한다. 현재 소스의 개발용 pack은 동결 릴리스 archive와 다르므로 게시 파일을 대체하지 않는다. [독립 React 예제](https://github.com/naeil-dev/naeil-ui/tree/main/examples/react)와 [검증/릴리스 정책](../package-release.md)을 참고한다.
|
|
16
16
|
|
|
17
17
|
공통 UI 소비에는 Supabase, 21st 또는 다른 API 키가 필요하지 않다. React/ReactDOM 19는 소비 앱의 peer이며, Next 15/16과 next-intl 4는 호환 경로에 필요한 선택적 peer다. next-themes 0.4와 tailwind-merge 3은 공통 런타임 의존성으로 설치된다. 사이트의 Supabase·MDX·Three 의존성과 코드는 공통 소비 경계에서 제외했다.
|
|
18
18
|
|
|
19
|
-
버전을 생략한 설치는 registry의 latest 태그를 따른다.
|
|
19
|
+
버전을 생략한 설치는 registry의 latest 태그를 따른다. 2026-10-23에 latest는 0.7.1로 확인했다. 검증한 버전을 유지하려면 0.7.1을 명시한다. 업그레이드 전에 [변경 기록](../../CHANGELOG.md)을 확인한다.
|
|
20
20
|
|
|
21
21
|
Tailwind 4를 사용하는 앱의 전역 CSS에서 공통 스타일을 한 번 불러온다.
|
|
22
22
|
|
|
@@ -80,7 +80,7 @@ Next.js 소비자는 layout의 metadata title 기본값/템플릿과 페이지
|
|
|
80
80
|
|
|
81
81
|
## 앱 전체 틀과 콘텐츠 폭
|
|
82
82
|
|
|
83
|
-
실제 naeil.dev 브랜드 헤더를 이어받는 앱은 선택적 React 전용 `@naeil/ui/naeil-header`의 `NaeilHeader`를
|
|
83
|
+
실제 naeil.dev 브랜드 헤더를 이어받는 앱은 선택적 React 전용 `@naeil/ui/naeil-header`의 `NaeilHeader`를 사용하고, 소유 서비스 푸터는 `@naeil/ui/naeil-footer`의 `NaeilFooter`로 구성한다. 전체 프로젝트 메뉴·헤더 번역·테마/선택적 언어 컨트롤·toolbar·반응형 스타일과 canonical 푸터 목적지를 소비 앱에 다시 복사하지 않는다. 소비 앱은 기존 theme provider, 라우팅·저장 guard와 현재 언어를 연결한다. `authSlot`에는 같은 모듈의 `NaeilHeaderAccountControl`을 두고 실제 세션 상태·busy·guarded logout 콜백을 전달한다. pending 상태와 요청 잠금은 헤더 위에서 소유해 데스크톱/모바일 슬롯이 공유한다. 언어 변경을 지원하지 않으면 `onLocaleChange`를 생략한다. [전체 헤더 계약](../components/naeil-header.md)과 [푸터 계약](../components/naeil-footer.md)을 참조한다.
|
|
84
84
|
|
|
85
85
|
2026-10-16 승인한 기본 프레임은 최대 1600px(여백 포함), 좌우 `clamp(16px, 2.4vw, 40px)`다. 브랜드 헤더·범용 Navigation·SiteFooter·본문 `.ui-app-frame`이 `--ui-width-frame`, `--ui-frame-gutter`를 함께 사용한다. 브랜드 헤더의 고정 높이는 65px이므로 main·sticky 영역과 초점/anchor 이동에 `calc(var(--ui-navigation-height) + 1px)`만큼 여유를 둔다. 다섯 장식 이미지는 공식 `https://naeil.dev/images/*.png` URL을 참조하므로 네트워크가 필요하다. 이미지 바이트는 npm에 포함하지 않으며 재배포 출처·권리 미해결 사항은 [제3자 고지](../../THIRD_PARTY_NOTICES.md)에 유지한다.
|
|
86
86
|
|
|
@@ -120,6 +120,7 @@ import { Navigation, SiteFooter } from "@naeil/ui/ui";
|
|
|
120
120
|
|
|
121
121
|
- 주요 경로를 같은 조건에서 비교한다. 메뉴 좌표와 여백, 제목·행동의 정렬을 확인하고 의도적인 차이는 이유를 남긴다.
|
|
122
122
|
- 제품이 지원하는 언어, 긴 이름, 적은/많은 데이터, 영향을 받는 로딩·오류 상태를 포함한다. 공유 컴포넌트 예시는 기존 한글·영문·일문 기준을 유지한다.
|
|
123
|
+
- 인증 전·후, 계정 메뉴, 로그아웃 busy·취소·실패·재시도를 확인한다. pending 중 모바일 메뉴 재열기나 화면 폭 변경으로 중복 요청이 발생하지 않아야 한다. 익명 화면만으로 인증된 헤더의 일치를 주장하지 않는다.
|
|
123
124
|
- 페이지 넘침과 함께 표 내부 잘림·줄바꿈·조작 영역을 확인한다. 내부 스크롤 자체를 페이지 넘침과 혼동하지 않는다.
|
|
124
125
|
- 기능 검사, 공통 기준 준수, 실제 화면 구성을 별도로 기록한다. 기존 대비·중첩 조작·터치 크기 문제는 현재 증거가 생기기 전까지 미해결로 남긴다.
|
|
125
126
|
|
|
@@ -156,7 +157,7 @@ Storybook의 로컬 폰트 import는 예시다. 그대로 앱 전역에 모든
|
|
|
156
157
|
|
|
157
158
|
## 개별 컴포넌트 안내
|
|
158
159
|
|
|
159
|
-
[컴포넌트 사용 안내](../components/README.md)는 13개 primitive와 Navigation·SiteFooter·전체 NaeilHeader
|
|
160
|
+
[컴포넌트 사용 안내](../components/README.md)는 13개 primitive와 Navigation·SiteFooter·NaeilFooter·전체 NaeilHeader 및 계정 컨트롤의 실제 API·기본값, 키보드, 상태, 레이블·오류 연결, 재정의 책임을 설명한다. Storybook 소스는 UI 15개와 Brand 2개, 총 17개 Docs를 제공한다. canonical 푸터 전체/축약형과 계정 상태는 기존 NaeilHeader/SiteFooter stories에 포함된다. 각 `Docs`·`Usage`와 `Brand / NaeilHeader / JapaneseApplication, ReferenceFull, ReferenceCompact, AccountStates`에서 안내와 실행 예시를 확인한다. 이는 새 hosted Docs 배포 완료를 뜻하지 않는다. [지원 범위와 수동 점검](public-ui-support.md)은 자동 검사와 실제 보조 기술·운영체제 검증의 차이를 기록한다.
|
|
160
161
|
|
|
161
162
|
## 확인 방법
|
|
162
163
|
|
|
@@ -169,7 +170,7 @@ Storybook의 로컬 폰트 import는 예시다. 그대로 앱 전역에 모든
|
|
|
169
170
|
|
|
170
171
|
## 0.3 package boundary
|
|
171
172
|
|
|
172
|
-
The heading retains the original 0.3 migration anchor; the
|
|
173
|
+
The heading retains the original 0.3 migration anchor; the boundary below includes published and registry-verified 0.7.1. `/ui` is the React core. Opt-in `/naeil-header` provides the canonical complete branded header and `NaeilHeaderAccountControl`; `/naeil-footer` provides canonical full/compact footers. Both are React-only. `/release` exports server-safe `uiRelease` metadata, without application artifact or environment data. Root imports preserve Nav, Footer, Logo, LocaleSwitcher, ThemeProvider, ThemeToggle, ThemeToggleIcon, PageTitle, SectionTitle and their class helpers, but root has static Next/next-intl dependencies. React-only apps must use the dedicated entries and appropriate shared deep modules. `next-themes` is a React runtime library; it does not require Next.js. Fonts remain consumer-owned.
|
|
173
174
|
|
|
174
175
|
Next.js `^15 || ^16` and next-intl `^4` are optional peers. Install both when using root, Nav, Footer or `i18n/routing`. This is an existing compatibility contract, not a claim that every peer combination has browser validation; the clean Next fixture checks Next 16.1.7. React/ReactDOM `^19` come from the host. Tailwind 4 is the documented styling pipeline. Explicit `className`, Radix props, Toaster options and theme-provider props remain consumer overrides.
|
|
175
176
|
|
|
@@ -206,7 +207,7 @@ i18n/config
|
|
|
206
207
|
i18n/routing
|
|
207
208
|
```
|
|
208
209
|
|
|
209
|
-
`components/index` has the same framework requirements as root. `/utils` and `lib/utils` remain aliases for `cn`. `i18n/config` preserves the compatibility locale names; products may instead own their locale list and routing. There are no open-ended component/lib/i18n export patterns. Root, `/ui`, `/utils`, `/theme.css`, `/globals.css` and `/components.css` remain public
|
|
210
|
+
`components/index` has the same framework requirements as root. `/utils` and `lib/utils` remain aliases for `cn`. `i18n/config` preserves the compatibility locale names; products may instead own their locale list and routing. There are no open-ended component/lib/i18n export patterns. Root, `/ui`, `/utils`, `/theme.css`, `/globals.css` and `/components.css` remain public. `/naeil-header`, `/naeil-footer` and `/release` are explicit dedicated entries; they do not create open-ended component export patterns.
|
|
210
211
|
|
|
211
212
|
Every excluded source path stays in this repository for the brand/example website. Move any local-source consumer use into the consumer application before upgrading:
|
|
212
213
|
|
|
@@ -214,7 +215,7 @@ Every excluded source path stays in this repository for the brand/example websit
|
|
|
214
215
|
| --- | --- |
|
|
215
216
|
| `components/nav-wrapper`, `components/nav-server-wrapper` | Use `/naeil-header` for the canonical whole header, or preserve Nav compatibility through your own adapter; your app loads users/sessions. |
|
|
216
217
|
| `components/footer-wrapper` | Compose preserved Footer with your app's Link. |
|
|
217
|
-
| `components/auth-slot` |
|
|
218
|
+
| `components/auth-slot` | The app owns session state and guarded logout. Owned-service headers render `NaeilHeaderAccountControl` from `/naeil-header` in `authSlot`; legacy custom Nav slots remain compatible. |
|
|
218
219
|
| `components/hero-scene`, `components/hero-section`, `components/paraglider-cursor` | Website scene/cursor source, assets and Three dependencies stay website-owned. |
|
|
219
220
|
| `components/project-layout`, `components/workflow-diagram`, `components/accent-picker` | Website showcase/data/theme editing composition; use shared primitives in product-owned compositions. |
|
|
220
221
|
| `components/blog/MarkdownRenderer`, `lib/blog` | Application-owned content rendering, filesystem and MDX/remark pipeline. |
|