@naeil/ui 0.12.0 → 0.13.1
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 +38 -3
- package/DESIGN.md +10 -2
- package/README.md +13 -11
- package/THIRD_PARTY_NOTICES.md +4 -2
- package/dist/{chunk-XLISHUJZ.js → chunk-ESTQITQO.js} +68 -8
- package/dist/{chunk-DBE7R4MR.js → chunk-L25A4OKY.js} +1 -1
- package/dist/{chunk-KPE4KKYM.js → chunk-Y5LW3ADW.js} +24 -15
- package/dist/components/index.d.ts +3 -3
- package/dist/components/index.js +4 -4
- package/dist/components/naeil-footer.d.ts +2 -2
- package/dist/components/naeil-footer.js +1 -1
- package/dist/components/naeil-header.d.ts +23 -8
- package/dist/components/naeil-header.js +7 -5
- package/dist/components/ui/index.d.ts +2 -2
- package/dist/components/ui/index.js +6 -6
- package/dist/lib/ui-release.d.ts +1 -1
- package/dist/lib/ui-release.js +1 -1
- package/dist/lib/utils.js +1 -7
- package/dist/naeil-locale/checker.js +1 -1
- package/dist/naeil-locale/chrome.d.ts +25 -8
- package/dist/naeil-locale/chrome.js +91 -22
- package/dist/naeil-locale/{chunk-4U64K4J3.js → chunk-24IL53QO.js} +1 -1
- package/dist/naeil-locale/{chunk-U65OQHZY.js → chunk-WT6QXR7O.js} +14 -8
- package/dist/naeil-locale/client.js +45 -15
- package/dist/naeil-locale/index.js +1 -1
- package/dist/naeil-locale/messages.js +2 -2
- package/dist/naeil-locale/server.js +6 -6
- package/dist/{navigation-D8N1tMTu.d.ts → navigation-CXZapKac.d.ts} +3 -1
- package/dist/{site-footer-s113FZgC.d.ts → site-footer-C4jgkGDU.d.ts} +1 -1
- package/dist/source/components/naeil-header-core.d.ts +23 -8
- package/dist/source/components/naeil-header-core.js +66 -6
- package/dist/source/components/naeil-header.d.ts +6 -4
- package/dist/source/components/naeil-header.js +4 -2
- package/dist/source/components/typography.d.ts +1 -1
- package/dist/source/components/typography.js +1 -1
- package/dist/source/components/ui/button.js +7 -7
- package/dist/source/components/ui/card.js +1 -1
- package/dist/source/components/ui/dialog.js +2 -2
- package/dist/source/components/ui/navigation.d.ts +3 -1
- package/dist/source/components/ui/navigation.js +17 -8
- package/dist/source/components/ui/sonner.js +2 -2
- 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 -1
- package/docs/components/application-layout.md +23 -27
- package/docs/components/avatar.md +1 -1
- package/docs/components/badge.md +3 -1
- package/docs/components/button.md +7 -1
- package/docs/components/card.md +5 -1
- package/docs/components/checkbox.md +1 -1
- package/docs/components/common.md +16 -1
- package/docs/components/dialog.md +6 -2
- package/docs/components/dropdown-menu.md +1 -1
- package/docs/components/input.md +1 -1
- package/docs/components/naeil-footer.md +1 -1
- package/docs/components/naeil-header.md +41 -12
- package/docs/components/naeil-locale.md +36 -0
- package/docs/components/navigation.md +10 -4
- package/docs/components/select.md +1 -1
- package/docs/components/sheet.md +1 -1
- package/docs/components/sonner.md +7 -1
- package/docs/components/switch.md +1 -1
- package/docs/components/tabs.md +1 -1
- package/docs/components/textarea.md +1 -1
- package/docs/design/public-ui-support.md +3 -1
- package/docs/design/v2-migration.md +11 -6
- package/docs/package-release.md +36 -12
- package/package.json +7 -10
- package/src/styles/components.css +29 -0
- package/src/styles/globals.css +6 -2
- package/src/styles/theme.css +5 -0
- package/licenses/Nunito-Sans-OFL.txt +0 -93
- package/licenses/React-MIT.txt +0 -21
- package/licenses/Storybook-MIT.txt +0 -21
- package/licenses/popper-MIT.txt +0 -25
- package/licenses/store2-MIT.txt +0 -22
- package/licenses/toggle-selection-MIT.txt +0 -21
|
@@ -169,6 +169,7 @@ function Navigation(_a) {
|
|
|
169
169
|
toolbar,
|
|
170
170
|
mobileToolbar,
|
|
171
171
|
mobileFooter,
|
|
172
|
+
omitEmptyNavigation = false,
|
|
172
173
|
mobileOpen: controlledOpen,
|
|
173
174
|
onMobileOpenChange,
|
|
174
175
|
containerClassName,
|
|
@@ -183,6 +184,7 @@ function Navigation(_a) {
|
|
|
183
184
|
"toolbar",
|
|
184
185
|
"mobileToolbar",
|
|
185
186
|
"mobileFooter",
|
|
187
|
+
"omitEmptyNavigation",
|
|
186
188
|
"mobileOpen",
|
|
187
189
|
"onMobileOpenChange",
|
|
188
190
|
"containerClassName",
|
|
@@ -253,7 +255,8 @@ function Navigation(_a) {
|
|
|
253
255
|
setOpenGroup(null);
|
|
254
256
|
}, [currentHref, setMobileOpen]);
|
|
255
257
|
React.useEffect(() => {
|
|
256
|
-
|
|
258
|
+
var _a2, _b2;
|
|
259
|
+
const element = omitEmptyNavigation && !items.length ? (_b2 = (_a2 = desktop.current) == null ? void 0 : _a2.parentElement) == null ? void 0 : _b2.querySelector(".ui-navigation-toolbar") : desktop.current;
|
|
257
260
|
if (!element || typeof ResizeObserver === "undefined") return;
|
|
258
261
|
const observer = new ResizeObserver(() => {
|
|
259
262
|
if (element.getBoundingClientRect().width > 0) setMobileOpen(false);
|
|
@@ -261,7 +264,8 @@ function Navigation(_a) {
|
|
|
261
264
|
});
|
|
262
265
|
observer.observe(element);
|
|
263
266
|
return () => observer.disconnect();
|
|
264
|
-
}, [setMobileOpen]);
|
|
267
|
+
}, [setMobileOpen, omitEmptyNavigation, items.length]);
|
|
268
|
+
const DesktopElement = omitEmptyNavigation && !items.length ? "div" : "nav";
|
|
265
269
|
const active = (item) => {
|
|
266
270
|
var _a2;
|
|
267
271
|
return (_a2 = item.active) != null ? _a2 : !item.external && item.href === currentHref;
|
|
@@ -310,10 +314,12 @@ function Navigation(_a) {
|
|
|
310
314
|
children: /* @__PURE__ */ jsx("div", { className: cn("ui-navigation-frame", containerClassName), children: /* @__PURE__ */ jsxs("div", { className: "ui-navigation-row", children: [
|
|
311
315
|
/* @__PURE__ */ jsx("div", { className: "ui-navigation-brand", children: brand }),
|
|
312
316
|
/* @__PURE__ */ jsx(
|
|
313
|
-
|
|
317
|
+
DesktopElement,
|
|
314
318
|
{
|
|
315
|
-
ref:
|
|
316
|
-
|
|
319
|
+
ref: (element) => {
|
|
320
|
+
desktop.current = element;
|
|
321
|
+
},
|
|
322
|
+
"aria-label": omitEmptyNavigation && !items.length ? void 0 : labels.navigation,
|
|
317
323
|
className: "ui-navigation-desktop",
|
|
318
324
|
children: items.map(
|
|
319
325
|
(item) => "items" in item ? /* @__PURE__ */ jsx(
|
|
@@ -365,7 +371,7 @@ function Navigation(_a) {
|
|
|
365
371
|
className: "ui-navigation-sheet ui-popup",
|
|
366
372
|
"aria-describedby": void 0,
|
|
367
373
|
onCloseAutoFocus: (event) => {
|
|
368
|
-
var _a2, _b2;
|
|
374
|
+
var _a2, _b2, _c, _d;
|
|
369
375
|
if (controlledOpen !== void 0 && (!mounted.current || state.current.open)) {
|
|
370
376
|
event.preventDefault();
|
|
371
377
|
return;
|
|
@@ -379,7 +385,10 @@ function Navigation(_a) {
|
|
|
379
385
|
}
|
|
380
386
|
if (trigger.current && trigger.current.getBoundingClientRect().width === 0) {
|
|
381
387
|
event.preventDefault();
|
|
382
|
-
|
|
388
|
+
const target = (_a2 = desktop.current) == null ? void 0 : _a2.querySelector("button, a");
|
|
389
|
+
const toolbar2 = omitEmptyNavigation && !items.length ? (_c = (_b2 = desktop.current) == null ? void 0 : _b2.parentElement) == null ? void 0 : _c.querySelector(".ui-navigation-toolbar") : void 0;
|
|
390
|
+
const toolbarTarget = !target && toolbar2 ? Array.from(toolbar2.querySelectorAll("button, a[href], input, select, textarea, [tabindex]")).find((element) => element.tabIndex >= 0 && !element.matches(":disabled") && element.getBoundingClientRect().width > 0 && element.getBoundingClientRect().height > 0) : void 0;
|
|
391
|
+
(_d = target != null ? target : toolbarTarget) == null ? void 0 : _d.focus();
|
|
383
392
|
}
|
|
384
393
|
},
|
|
385
394
|
children: [
|
|
@@ -394,7 +403,7 @@ function Navigation(_a) {
|
|
|
394
403
|
}
|
|
395
404
|
)
|
|
396
405
|
] }),
|
|
397
|
-
/* @__PURE__ */ jsx(
|
|
406
|
+
(!omitEmptyNavigation || items.length > 0) && /* @__PURE__ */ jsx(
|
|
398
407
|
"nav",
|
|
399
408
|
{
|
|
400
409
|
"aria-label": labels.navigation,
|
|
@@ -39,11 +39,11 @@ import { useTheme } from "next-themes";
|
|
|
39
39
|
import { Toaster as Sonner } from "sonner";
|
|
40
40
|
const Toaster = (_a) => {
|
|
41
41
|
var props = __objRest(_a, []);
|
|
42
|
-
const { theme = "system" } = useTheme();
|
|
42
|
+
const { theme = "system", forcedTheme } = useTheme();
|
|
43
43
|
return /* @__PURE__ */ jsx(
|
|
44
44
|
Sonner,
|
|
45
45
|
__spreadValues({
|
|
46
|
-
theme,
|
|
46
|
+
theme: forcedTheme != null ? forcedTheme : theme,
|
|
47
47
|
className: "toaster group font-sans",
|
|
48
48
|
position: "bottom-right",
|
|
49
49
|
duration: 4e3,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Component usage guides
|
|
2
2
|
|
|
3
|
-
These maintained guides
|
|
3
|
+
These maintained guides describe the repository APIs. See [release policy and identities](../package-release.md#publication-and-deployment) for the current published release. [Setup, themes, fonts and shared responsibilities](common.md). Run `pnpm storybook` for the current 18 Docs pages (16 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
|
|
|
@@ -25,6 +25,8 @@ Start application layouts with the [shared navigation/footer selection and frame
|
|
|
25
25
|
| NaeilFooter | [NaeilFooter](./naeil-footer.md) | Brand / NaeilHeader / ReferenceFull, ReferenceCompact |
|
|
26
26
|
| NaeilHeader and account control | [NaeilHeader](./naeil-header.md) | Brand / NaeilHeader / Usage, JapaneseApplication, AccountStates |
|
|
27
27
|
|
|
28
|
+
[Owned-service language runtime](naeil-locale.md) documents `/naeil-locale`, `/client`, `/server`, `/messages`, `/checker` and `/chrome`. Owned Next services under the shared provider import header/account/footer from `/naeil-locale/chrome`; the standalone React adapters remain available.
|
|
29
|
+
|
|
28
30
|
[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.
|
|
29
31
|
|
|
30
32
|
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,6 +1,6 @@
|
|
|
1
1
|
# Application frame and shared navigation/footer
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Product navigation and controlled mobile APIs are available since 0.8.0; shared root reservation is available since 0.9.0. See [release identities and publication status](../package-release.md#publication-and-deployment) for the current published release; hosted Docs and service activation have separate receipts. See [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,7 @@ Exact **@naeil/ui 0.8.0 is published** (`next` tag; `latest` remains 0.7.2 at th
|
|
|
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) | Complete `NaeilHeader
|
|
11
|
+
| Owned naeil service with product menu data (0.8.0) | Complete `NaeilHeader` with `service` and its own `appNavigation` |
|
|
12
12
|
| A different product menu without canonical naeil chrome | `Navigation` from `@naeil/ui/ui` |
|
|
13
13
|
| Owned naeil service footer | `NaeilFooter` from `@naeil/ui/naeil-footer` |
|
|
14
14
|
| Different product footer or explicit content override | `SiteFooter` from `@naeil/ui/ui` |
|
|
@@ -30,7 +30,7 @@ Choose one header per application frame. Supply menu data, links, route guards,
|
|
|
30
30
|
|
|
31
31
|
Use `.ui-app-frame` on main. Shared header/footer already apply the frame internally; **do not wrap them in another padded frame**. Put purpose-specific sections inside main. At 1920px the default frame has 1520px available after its 40px gutters. Small screens shrink the frame and reduce gutters to at least 16px. A narrow form must not constrain or move navigation.
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
Since 0.9.0, the package supplies `html { scrollbar-gutter: stable }` once in `components.css`, reached by `globals.css` or `theme.css + components.css`. Classic mode reserves one browser-sized gutter on short and long pages; overlay mode reserves none. The shared lock correction removes duplicate Radix body right-margin/gap compensation and retains its scroll prevention, focus and nested cleanup. Keep header/main/footer and their ordinary content horizontally stable. Compare matching root policies and measure before any fullPage capture. See the [adopted acceptance contract](https://github.com/naeil-dev/naeil-ui/blob/434b7d07235316097ca3a55265ee02c1422b0cfd/docs/superpowers/specs/2026-10-29-page-root-scrollbar-gutter.md).
|
|
34
34
|
|
|
35
35
|
For a different document policy, declare this on the root **before first paint**:
|
|
36
36
|
|
|
@@ -57,28 +57,33 @@ Frame and control tokens stay unchanged. At 1920px with a 15px classic gutter th
|
|
|
57
57
|
|
|
58
58
|
## Canonical naeil application starter
|
|
59
59
|
|
|
60
|
+
Owned Next services under `NaeilLocaleProvider` import header/account/footer chrome from `@naeil/ui/naeil-locale/chrome`; `/naeil-header` and `/naeil-footer` remain standalone React adapters. See [the owned-service language runtime](naeil-locale.md) for `/naeil-locale`, `/client`, `/server`, `/messages`, `/checker` and `/chrome` setup.
|
|
61
|
+
|
|
62
|
+
For the standalone React adapter:
|
|
63
|
+
|
|
60
64
|
Owned services use NaeilFooter's shared `full` default as-is; omit `variant`. `compact` is a deliberate, recorded exception (for example, a focused task screen), not the starter default.
|
|
61
65
|
|
|
62
66
|
```tsx
|
|
63
67
|
import type { ReactNode } from "react";
|
|
64
68
|
import { NaeilHeader, NaeilHeaderAccountControl,
|
|
65
|
-
type NaeilHeaderAccount, type NaeilHeaderProps } from "@naeil/ui/naeil-header";
|
|
69
|
+
type NaeilHeaderAccount, type NaeilHeaderProps, type NaeilService } from "@naeil/ui/naeil-header";
|
|
66
70
|
import { NaeilFooter } from "@naeil/ui/naeil-footer";
|
|
67
71
|
|
|
68
72
|
// Render inside the existing next-themes ThemeProvider.
|
|
69
73
|
// The app owns session state, its guarded logout and pending request lock.
|
|
70
74
|
type ApplicationLayoutProps = {
|
|
71
75
|
children: ReactNode;
|
|
76
|
+
service: NaeilService;
|
|
72
77
|
locale: NaeilHeaderProps["locale"];
|
|
73
78
|
Link: NaeilHeaderProps["Link"];
|
|
74
79
|
currentHref: string;
|
|
75
80
|
account: NaeilHeaderAccount;
|
|
76
81
|
};
|
|
77
82
|
|
|
78
|
-
export function ApplicationLayout({ children, locale, Link, currentHref,
|
|
83
|
+
export function ApplicationLayout({ children, service, locale, Link, currentHref,
|
|
79
84
|
account }: ApplicationLayoutProps) {
|
|
80
85
|
return <>
|
|
81
|
-
<NaeilHeader locale={locale} Link={Link} currentHref={currentHref}
|
|
86
|
+
<NaeilHeader service={service} locale={locale} Link={Link} currentHref={currentHref}
|
|
82
87
|
authSlot={<NaeilHeaderAccountControl locale={locale} Link={Link}
|
|
83
88
|
account={account} />} />
|
|
84
89
|
<main className="ui-app-frame"
|
|
@@ -89,23 +94,23 @@ export function ApplicationLayout({ children, locale, Link, currentHref,
|
|
|
89
94
|
</>;
|
|
90
95
|
}
|
|
91
96
|
|
|
92
|
-
<ApplicationLayout locale="en" Link={GuardedLink} currentHref={pathname}
|
|
97
|
+
<ApplicationLayout service={service} locale="en" Link={GuardedLink} currentHref={pathname}
|
|
93
98
|
account={account}>
|
|
94
99
|
<section data-ui-layout="settings">{/* Settings form */}</section>
|
|
95
100
|
</ApplicationLayout>
|
|
96
101
|
```
|
|
97
102
|
|
|
98
|
-
`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).
|
|
103
|
+
`service`, `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).
|
|
99
104
|
|
|
100
|
-
`NaeilHeader` is fixed; reserve its 65px height and account for it in sticky areas and focus/anchor scroll margins.
|
|
105
|
+
`NaeilHeader` is fixed; reserve its 65px height and account for it in sticky areas and focus/anchor scroll margins. The service brand defaults to `/`; footer destinations remain 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.
|
|
101
106
|
|
|
102
|
-
For owned naeil product navigation,
|
|
107
|
+
For owned naeil product navigation, set the matching `service` id and pass only product menu data through `appNavigation`; see the [Home profile and host conditions](naeil-header.md#host-typography-and-mobile-handoff-080). `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/account control, narrow form and NaeilFooter together.
|
|
103
108
|
|
|
104
|
-
|
|
109
|
+
Without `service`, an empty desktop menu preserves B1's limitation: resizing alone leaves the sheet open; Escape/close/overlay/route change dismiss it. Service profiles dismiss on desktop crossing even with an empty center; the mobile sheet omits empty navigation. 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.
|
|
105
110
|
|
|
106
111
|
## Deliberate overrides
|
|
107
112
|
|
|
108
|
-
Apply overrides on a common ancestor or document root so header, main and footer inherit them together.
|
|
113
|
+
Apply overrides on a common ancestor or document root so header, main and footer inherit them together. Legacy (no `service`) `containerClassName` overrides still work; the owned-service profile rejects header class/style overrides. Do not give each route its own header cap or gutter.
|
|
109
114
|
|
|
110
115
|
```css
|
|
111
116
|
/* Product exception: record why this cap is needed. */
|
|
@@ -128,8 +133,12 @@ Put this in the **consumer application's** `AGENTS.md` (or equivalent repository
|
|
|
128
133
|
- Read the installed @naeil/ui/DESIGN.md and docs/components/application-layout.md before UI layout work.
|
|
129
134
|
- 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.
|
|
130
135
|
- 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.
|
|
131
|
-
-
|
|
132
|
-
-
|
|
136
|
+
- For owned Next services under NaeilLocaleProvider, import header/account/footer from @naeil/ui/naeil-locale/chrome. Read docs/components/naeil-locale.md for the root controller, provider, server routing, message loading and checker. /naeil-header and /naeil-footer remain standalone React adapters.
|
|
137
|
+
- Owned services must set service="abstorm" / "home" / "estimate" / "jev". The DS renders logo → .naeil → lowercase id; no statusSlot, status dot or extra indicator beside the brand.
|
|
138
|
+
- Supply only the service's own appNavigation tabs; never copy naeil.dev Projects/Blog. Single-screen services leave appNavigation empty; the mobile menu still exposes theme/locale/account without empty navigation.
|
|
139
|
+
- No header escape hatches: brandSuffix/brandSlot/toolbarSlot/subNav/brandLinkComponent, catalog/translations overrides, brandLinkProps/mobileFooter, className/containerClassName/style or data-slot/data-naeil-* marker overrides. Keep the shared tab and toolbar styles/states.
|
|
140
|
+
- Local wrappers supply menu data, routing/guards, locale and shared auth state. Do not copy the menu, mobile sheet, controls or footer implementation. Extend the shared component for reusable missing behavior.
|
|
141
|
+
- Since 0.9.0, shared CSS reserves a stable root gutter through globals or theme+components; opt out on html with data-naeil-scrollbar-gutter="auto" before first paint to disable both root reservation and lock correction. Compare classic/overlay and short/long body content through menus/dialogs; measure before fullPage screenshots.
|
|
133
142
|
- 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.
|
|
134
143
|
- Check the installed package version and CSS before creating a replacement for an apparently missing component.
|
|
135
144
|
- 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.
|
|
@@ -144,16 +153,3 @@ Owned-service checks also verify canonical header/footer destinations, rendered
|
|
|
144
153
|
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.
|
|
145
154
|
|
|
146
155
|
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.
|
|
147
|
-
|
|
148
|
-
## Shared naeil.dev login (services on `*.naeil.dev`)
|
|
149
|
-
|
|
150
|
-
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.
|
|
151
|
-
|
|
152
|
-
- **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.
|
|
153
|
-
- **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.
|
|
154
|
-
- **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`).
|
|
155
|
-
- **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.
|
|
156
|
-
- **Header:** `NaeilHeaderAccountControl` in `authSlot`, linked to the login URL and the logout callback.
|
|
157
|
-
- **Onboarding:** register the service id in the central catalog, mark it verified, and grant users from Home Console (`console_set_access`).
|
|
158
|
-
|
|
159
|
-
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.
|
|
@@ -10,7 +10,7 @@ Avatar forwards Radix Root span props and defaults **size="default"**; sizes `sm
|
|
|
10
10
|
|
|
11
11
|
Inherited [Avatar 1.1.10 declaration](https://unpkg.com/@radix-ui/react-avatar@1.1.10/dist/index.d.ts).
|
|
12
12
|
|
|
13
|
-
The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/
|
|
13
|
+
The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/main/src/components/ui/avatar.tsx) is the API authority.
|
|
14
14
|
|
|
15
15
|
## Composition
|
|
16
16
|
|
package/docs/components/badge.md
CHANGED
|
@@ -6,7 +6,9 @@ Use Badge for concise status/category text. Prefer readable body/helper text for
|
|
|
6
6
|
|
|
7
7
|
Exports: `Badge`, `badgeVariants`. Native span props plus `variant` and `asChild`. Defaults: **variant="default", asChild=false**. Variants: `default`, `secondary`, `outline`, `destructive`, `success`, `warning`, `error`, `info`. asChild uses Slot.Root; no disabled/loading or announcement API. Default text is intentionally small and single-line.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
**Server Components:** To call `badgeVariants` on the server, import it from `@naeil/ui/components/ui/badge`. The `@naeil/ui/ui` component barrel remains a client boundary. Import `cn` from `@naeil/ui/utils` or the RSC-safe deep path `@naeil/ui/lib/utils`.
|
|
10
|
+
|
|
11
|
+
The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/main/src/components/ui/badge.tsx) is the API authority.
|
|
10
12
|
|
|
11
13
|
## Composition
|
|
12
14
|
|
|
@@ -6,6 +6,8 @@ Use Button for an action, a native anchor for navigation, and Select for a value
|
|
|
6
6
|
|
|
7
7
|
Exports: `Button`, `buttonVariants`.
|
|
8
8
|
|
|
9
|
+
**Server Components:** To call `buttonVariants` on the server, import it from `@naeil/ui/components/ui/button`. The `@naeil/ui/ui` component barrel remains a client boundary. Import `cn` from `@naeil/ui/utils` or the RSC-safe deep path `@naeil/ui/lib/utils`.
|
|
10
|
+
|
|
9
11
|
| Prop | Contract / wrapper default |
|
|
10
12
|
| --- | --- |
|
|
11
13
|
| `variant` | `default` (default), `destructive`, `outline`, `secondary`, `ghost`, `link` |
|
|
@@ -15,7 +17,7 @@ Exports: `Button`, `buttonVariants`.
|
|
|
15
17
|
|
|
16
18
|
`size` names are preserved; shared CSS controls actual height (40/36px; lg adds 4px; mobile minimum 44px). `xs` does not imply an extra-small touch target. `loading` is not an API.
|
|
17
19
|
|
|
18
|
-
The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/
|
|
20
|
+
The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/main/src/components/ui/button.tsx) is the API authority.
|
|
19
21
|
|
|
20
22
|
## Composition
|
|
21
23
|
|
|
@@ -39,3 +41,7 @@ Use `variant="destructive"` for the filled dangerous-action pair and `text-error
|
|
|
39
41
|
Demonstrate normal, disabled, saving and recovery; no-data/error belong to the operation. Do not nest Button in a link or treat variant="link" as navigation. With asChild anchors, own href/activation/disabled policy. For long translated actions use `className="h-auto min-h-11 whitespace-normal py-2"` and check both densities.
|
|
40
42
|
|
|
41
43
|
Check this example in light/dark, comfortable/compact and mobile, including long Korean, English and Japanese text. Consumer overrides remain supported; follow the [shared override and acceptance guidance](common.md#styling-themes-and-overrides). Actual engine results and manual limits are in the [support matrix](../design/public-ui-support.md).
|
|
44
|
+
|
|
45
|
+
## Pressed state and compatibility (2026-11-12)
|
|
46
|
+
|
|
47
|
+
Use `aria-pressed={selected}` for toggle actions. Every variant now shows a persistent 2px inset indicator; default uses its hover surface, and outline/secondary/ghost/link use `surface-selected` with foreground text. The destructive variant retains its dangerous-action fill. Filled variants inset the indicator 2px from the edge so it is surrounded by the contrasting fill. Indicators meet 3:1 against their adjacent fill and text meets 4.5:1 in both themes, including destructive hover. This changes the appearance of existing toggle buttons. Consumer `aria-pressed:*` classes still merge last; override the indicator and its contrast deliberately. `aria-pressed={false}` and ordinary actions retain their existing appearance.
|
package/docs/components/card.md
CHANGED
|
@@ -6,7 +6,7 @@ Use Card to group related information or a form. Use plain sections/headings for
|
|
|
6
6
|
|
|
7
7
|
Exports: `Card`, `CardElevated`, `CardHeader`, `CardFooter`, `CardTitle`, `CardAction`, `CardDescription`, `CardContent`. All accept `React.ComponentProps<"div">`; no variant/size/asChild/loading API. Card and CardElevated use `data-slot="card"`, default panel spacing and consumer className; CardElevated adds shadow/elevated dark surface. CardTitle is a **div**, not a semantic heading. CardAction occupies the second header column.
|
|
8
8
|
|
|
9
|
-
The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/
|
|
9
|
+
The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/main/src/components/ui/card.tsx) is the API authority.
|
|
10
10
|
|
|
11
11
|
## Composition
|
|
12
12
|
|
|
@@ -30,3 +30,7 @@ Static cards are not tab stops. Put real buttons/links inside and a semantic hea
|
|
|
30
30
|
Loading/empty/error are consumer content with status/retry actions, not Card props; disabled is inapplicable to the static surface. Keep CardAction concise, wrap long localized headings and use `w-full max-w-*` instead of fixed widths. Avoid nested cards and implicit clickable-card behavior; an entire link card must contain no nested controls.
|
|
31
31
|
|
|
32
32
|
Check this example in light/dark, comfortable/compact and mobile, including long Korean, English and Japanese text. Consumer overrides remain supported; follow the [shared override and acceptance guidance](common.md#styling-themes-and-overrides). Actual engine results and manual limits are in the [support matrix](../design/public-ui-support.md).
|
|
33
|
+
|
|
34
|
+
## Typography and theme compatibility (2026-11-12)
|
|
35
|
+
|
|
36
|
+
CardTitle now uses the component-title role (16/25px), replacing `leading-none`; CardDescription uses supporting text (14/22px). Wrapped titles and descriptions can make cards taller. Explicit `className` typography/leading overrides still win. CardElevated now also uses its elevated dark surface under OS dark with no root theme class when importing globals.css.
|
|
@@ -8,7 +8,7 @@ Export: `Checkbox`. Forwards Radix Root props: `checked`/`defaultChecked`/`onChe
|
|
|
8
8
|
|
|
9
9
|
Inherited [Checkbox 1.3.3 declaration](https://unpkg.com/@radix-ui/react-checkbox@1.3.3/dist/index.d.ts).
|
|
10
10
|
|
|
11
|
-
The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/
|
|
11
|
+
The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/main/src/components/ui/checkbox.tsx) is the API authority.
|
|
12
12
|
|
|
13
13
|
## Composition
|
|
14
14
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Shared usage contract
|
|
2
2
|
|
|
3
|
-
These maintained guides
|
|
3
|
+
These maintained guides describe the repository APIs; for the current published release, see [release identities and status](../package-release.md#publication-and-deployment). The [hosted Docs](https://naeil-dev.github.io/naeil-ui/) were last verified for [0.5.0 on 2026-10-15](https://github.com/naeil-dev/naeil-ui/releases/tag/v0.5.0), while `pnpm storybook` shows this checkout. Start with the [consumer setup and migration](../design/v2-migration.md). Primitives and generic Navigation/SiteFooter are exported from `@naeil/ui/ui`; the actual branded whole header uses the opt-in [NaeilHeader](naeil-header.md) entry `@naeil/ui/naeil-header`. Both are React-only. The legacy root needs the optional Next/next-intl peers. Examples import real wrappers; product state, validation and network requests belong to consumers.
|
|
4
4
|
|
|
5
5
|
## Styling, themes and overrides
|
|
6
6
|
|
|
@@ -35,3 +35,18 @@ Run each affected composition under matching light/dark, comfortable/compact, mo
|
|
|
35
35
|
## Exact inherited contracts
|
|
36
36
|
|
|
37
37
|
Native wrappers use React 19 element props: inspect your installed `@types/react/index.d.ts`, or infer `React.ComponentProps<typeof Component>`. Radix wrappers use `React.ComponentProps<typeof Primitive.Part>`; inspect the installed declarations reached through `radix-ui/dist/index.d.ts`. Per-family guides link pinned declarations inspected locally during this work. Those links describe the inherited contract, not a promise that all primitive configurations have been tested. Wrapper defaults below are separate from primitive defaults; omitted primitive defaults are not invented here. Consumer examples and browser checks demonstrate only their recorded configuration.
|
|
38
|
+
|
|
39
|
+
## Theme and typography compatibility (2026-11-12)
|
|
40
|
+
|
|
41
|
+
The `dark:` variant in globals.css now follows the same conditions as generated dark tokens: `.dark` on an element or ancestor, plus OS dark under `:root:not(.light)` (including the root itself). Root `.light` opts out of OS dark; a deliberately nested `.dark` still opts into dark. Use only one root theme class. `.light` is a root OS opt-out, not a nested light-theme reset. Consumers composing theme.css + components.css with their own Tailwind entry must copy this variant into that entry to match the token contract:
|
|
42
|
+
|
|
43
|
+
```css
|
|
44
|
+
@custom-variant dark {
|
|
45
|
+
&:where(.dark, .dark *) { @slot; }
|
|
46
|
+
@media (prefers-color-scheme: dark) {
|
|
47
|
+
&:where(:root:not(.light), :root:not(.light) *) { @slot; }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Generated font-size utilities now include the approved line heights: `text-3xl` 32/42px, `text-xl` 20/29px, `text-base` 16/25px (UI body, component title and controls), `text-sm` 14/22px and `text-xs` 13/20px at a 16px root. Reading body uses `text-base leading-relaxed` (16/30px). PageTitle, DialogTitle and CardTitle no longer override their role line heights. This changes wrapping and vertical rhythm; recheck constrained layouts. Explicit consumer `leading-*` utilities remain supported. Numeric values live in typography.json; regenerate with `pnpm build:tokens`.
|
|
@@ -10,7 +10,7 @@ Root forwards Radix `open`/`defaultOpen`/`onOpenChange`/`modal`; wrapper adds no
|
|
|
10
10
|
|
|
11
11
|
Inherited [Dialog 1.1.15 declaration](https://unpkg.com/@radix-ui/react-dialog@1.1.15/dist/index.d.ts).
|
|
12
12
|
|
|
13
|
-
The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/
|
|
13
|
+
The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/main/src/components/ui/dialog.tsx) is the API authority.
|
|
14
14
|
|
|
15
15
|
## Compatibility decision (2026-11-09)
|
|
16
16
|
|
|
@@ -42,6 +42,10 @@ For the modal example, opening moves focus inside, Tab/Shift+Tab stay inside, Es
|
|
|
42
42
|
|
|
43
43
|
## States, resilience and mistakes
|
|
44
44
|
|
|
45
|
-
Disabled applies to triggers/actions; loading/error/empty are content state. Keep failed input inside the dialog and enable retry. Long/mobile content
|
|
45
|
+
Disabled applies to triggers/actions; loading/error/empty are content state. Keep failed input inside the dialog and enable retry. Long/mobile content is scrollable within the dynamic viewport by default; preserve that behavior and do not hide all close actions. Consumer outside-dismissal overrides need keyboard/focus review. Do not nest modal roots casually or prevent Escape without an accessible exit.
|
|
46
46
|
|
|
47
47
|
Check this example in light/dark, comfortable/compact and mobile, including long Korean, English and Japanese text. Consumer overrides remain supported; follow the [shared override and acceptance guidance](common.md#styling-themes-and-overrides). Actual engine results and manual limits are in the [support matrix](../design/public-ui-support.md).
|
|
48
|
+
|
|
49
|
+
## Scroll default compatibility (2026-11-12)
|
|
50
|
+
|
|
51
|
+
Content now defaults to `max-h-[min(calc(100%-2rem),calc(100dvh-2rem))] overflow-y-auto`, keeping tall content and footer actions reachable inside the dynamic viewport. The percentage cap also respects CSS zoom, where viewport units alone can exceed the visible area. Remove equivalent consumer classes. To own a different scroll layout, pass a replacement max-height/overflow class; `className="max-h-[none] overflow-y-visible"` opts out (the consumer must keep all actions reachable). Title now follows the section-title 20/29px typography role.
|
|
@@ -10,7 +10,7 @@ Root: primitive `open`/`defaultOpen`/`onOpenChange`, `modal`, `dir`; no wrapper
|
|
|
10
10
|
|
|
11
11
|
Inherited [DropdownMenu 2.1.16 declaration](https://unpkg.com/@radix-ui/react-dropdown-menu@2.1.16/dist/index.d.ts), including its inherited Menu types.
|
|
12
12
|
|
|
13
|
-
The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/
|
|
13
|
+
The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/main/src/components/ui/dropdown-menu.tsx) is the API authority.
|
|
14
14
|
|
|
15
15
|
## Composition
|
|
16
16
|
|
package/docs/components/input.md
CHANGED
|
@@ -6,7 +6,7 @@ Use Input for native single-line text/value entry. Use [Textarea](./textarea.md)
|
|
|
6
6
|
|
|
7
7
|
Export: `Input`. Accepts `React.ComponentProps<"input">`; no wrapper default for `type`, value, disabled or validation. Native omitted type resolves to text. Important props: `value` + `onChange` or `defaultValue`, `name`, `type`, `required`, `readOnly`, `disabled`, `autoComplete`, `inputMode`, `min`/`max`, `minLength`/`maxLength`, `pattern`, form and ARIA props. No built-in label, error text or async validation API.
|
|
8
8
|
|
|
9
|
-
The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/
|
|
9
|
+
The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/main/src/components/ui/input.tsx) is the API authority.
|
|
10
10
|
|
|
11
11
|
## Composition
|
|
12
12
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# NaeilFooter — canonical service footer
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Available since **0.7.1**, the first published release containing this entry. See [release identities and publication status](../package-release.md#publication-and-deployment) for the current published release; hosted Docs and service activation have separate receipts. React-only opt-in entry:
|
|
4
4
|
|
|
5
5
|
```tsx
|
|
6
6
|
import { NaeilFooter } from '@naeil/ui/naeil-footer';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# NaeilHeader — branded whole-header composition
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Product navigation and controlled mobile APIs are available since 0.8.0; shared root reservation is available since 0.9.0. See [release identities and publication status](../package-release.md#publication-and-deployment) for the current published release; hosted Docs and service activation have separate receipts.
|
|
4
4
|
|
|
5
5
|
```tsx
|
|
6
6
|
import { NaeilHeader, NaeilHeaderAccountControl } from '@naeil/ui/naeil-header';
|
|
@@ -26,6 +26,7 @@ Only provide `onLocaleChange` when the application's interface supports all thre
|
|
|
26
26
|
|
|
27
27
|
| Prop | Contract |
|
|
28
28
|
| --- | --- |
|
|
29
|
+
| `service` | Opt-in `NaeilService`: `abstorm`, `home`, `estimate`, `jev`; exported readonly `naeilServices` lists these ids. Owns lowercase suffix/name, local `/` brand default and product-only tabs. |
|
|
29
30
|
| `locale` | Required `en`, `ko` or `ja`; selects header-only copy. |
|
|
30
31
|
| `resolveHref` | Resolves canonical unlocalized paths; defaults to the official localized site. |
|
|
31
32
|
| `Link`, `currentHref`, `onNavigate` | Routing adapter, active destination and navigation interception inherited from Navigation. |
|
|
@@ -33,16 +34,45 @@ Only provide `onLocaleChange` when the application's interface supports all thre
|
|
|
33
34
|
| `authSlot` | Canonical `NaeilHeaderAccountControl`, with consumer-owned auth state/actions. Legacy arbitrary markup remains supported. |
|
|
34
35
|
| `brandHref`, `brandSlot`, `brandLinkProps`, `brandLinkComponent` | Explicit brand destination/content/anchor overrides; default accessible brand name is `.naeil`. |
|
|
35
36
|
| `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
|
|
37
|
+
| `appNavigation` (0.8.0) | `NavigationItem[]` passed unchanged after catalog/blog and before flat `subNav`. Owned services set `service` and supply only their own tabs. |
|
|
37
38
|
| `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
39
|
| `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. |
|
|
40
|
+
| `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; without `service`, 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. |
|
|
40
41
|
| `subNav`, `currentProjectKey`, `currentProjectHref` | Legacy extra links and current-project destination. |
|
|
41
42
|
| `translations`, `labels` | Legacy header/project lookup overrides and navigation/menu/close accessible labels. |
|
|
42
43
|
| `toolbarSlot` | Replaces the complete default theme/locale/auth toolbar. |
|
|
43
44
|
| `mobileFooter`, `className`, `containerClassName` | Inherited mobile slot and style overrides; recheck frame/behavior after changes. |
|
|
44
45
|
|
|
45
|
-
Standard header attributes and
|
|
46
|
+
Standard header attributes and appropriate inherited [Navigation props](navigation.md) remain available. Omitting `service` preserves all legacy overrides and output.
|
|
47
|
+
|
|
48
|
+
## Owned-service profiles
|
|
49
|
+
|
|
50
|
+
| Profile | Left brand | Center | Brand default |
|
|
51
|
+
| --- | --- | --- | --- |
|
|
52
|
+
| naeil.dev (no `service`) | Logo + `.naeil` | Projects / Blog | Official localized site |
|
|
53
|
+
| `service="abstorm"` | Logo + `.naeil` + `abstorm` | Service's own tabs | `/` |
|
|
54
|
+
| `service="home"` | Logo + `.naeil` + `home` | Service's own tabs | `/` |
|
|
55
|
+
| `service="estimate"` | Logo + `.naeil` + `estimate` | Service's own flat/grouped tabs | `/` |
|
|
56
|
+
| `service="jev"` | Logo + `.naeil` + `jev` | Empty for this single-screen service | `/` |
|
|
57
|
+
|
|
58
|
+
The accessible brand name is `.naeil <id>`. `brandHref` can override the local destination. No status dot or extra brand indicator is allowed. The DS fixes tab shape, position, size, spacing and all interaction states. Theme / locale / account retain the canonical presentation; the app supplies routing/guards, locale and shared account state.
|
|
59
|
+
|
|
60
|
+
`NaeilHeaderProps` remains an extensible legacy interface with `service?: undefined`; `NaeilServiceHeaderProps` is the strict service type. Both are exported from the standalone and provider chrome entries. The component overloads accept the strict service type first and the legacy interface last, so `ComponentProps<typeof NaeilHeader>` remains `NaeilHeaderProps` for existing wrappers. Type service props explicitly as `NaeilServiceHeaderProps`.
|
|
61
|
+
|
|
62
|
+
The `service` discriminated branch rejects `brandSuffix`, `statusSlot`, `brandSlot`, `toolbarSlot`, `subNav`, `brandLinkComponent`, `projects`, `projectsExternal`, `showProjects`, `showBlog`, `translations`, `className` and `containerClassName`. It also rejects inline `style`, `brandLinkProps`, `mobileFooter` and the inherited empty-navigation policy override. JavaScript/cast attempts are ignored, with one warning per prop name in development and none in production. Root `data-slot`, `data-global-header` and `data-naeil-*` markers cannot be replaced. Root attributes such as `id`, `aria-*` and `dir`, plus the consumer `Link` and `authSlot` markup, remain consumer-owned and must not be used to restyle shared chrome. Labels, currentHref/onNavigate, brandHref, appNavigation, locale callbacks and controlled mobile state remain available. Unknown, empty or null runtime service values fall back to the legacy profile without a `service` attribute or stripped legacy props, with one development-only warning.
|
|
63
|
+
|
|
64
|
+
`statusSlot`, `brandSuffix`, `brandSlot`, `toolbarSlot`, `subNav` and `brandLinkComponent` are deprecated for owned services and retained for legacy compatibility. They remain functional without `service`.
|
|
65
|
+
|
|
66
|
+
With no `appNavigation`, the desktop center stays empty and the toolbar remains right-aligned. The mobile menu button stays available; its sheet contains the toolbar without an empty navigation landmark/list. Desktop crossing restores focus to a visible toolbar control when the center has no links/buttons. Resizing to desktop dismisses a service sheet by observing the desktop toolbar, including an empty-center profile. Legacy empty-menu headers retain the B1 resize exception described below.
|
|
67
|
+
|
|
68
|
+
## Standalone theme and locale controls
|
|
69
|
+
|
|
70
|
+
`NaeilHeaderThemeControl` and `NaeilHeaderLocaleControl` are public exports from `@naeil/ui/naeil-header` and `@naeil/ui/naeil-locale/chrome`. The latter uses the shared provider’s selected messages; the standalone entry supplies its own common dictionaries. Prefer the complete header’s default toolbar.
|
|
71
|
+
|
|
72
|
+
- `NaeilHeaderThemeControl({ locale })` accepts `en | ko | ja`, reads the consumer’s next-themes provider and toggles its resolved light/dark theme. The localized 44px icon button is disabled until mount.
|
|
73
|
+
- `NaeilHeaderLocaleControl({ locale, onLocaleChange })` requires the same locale union and a `(locale) => void` callback. It renders the shared three-language Select; routing, persistence and supported application translations belong to the consumer.
|
|
74
|
+
|
|
75
|
+
See [the locale runtime](naeil-locale.md) before using provider-backed chrome in an owned Next service.
|
|
46
76
|
|
|
47
77
|
## Account presentation
|
|
48
78
|
|
|
@@ -67,7 +97,7 @@ The fixed header is 65px: the current 64px navigation token plus a 1px border. C
|
|
|
67
97
|
|
|
68
98
|
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.
|
|
69
99
|
|
|
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.
|
|
100
|
+
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, Abstorm, Home, Estimate, Jev, 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.
|
|
71
101
|
|
|
72
102
|
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).
|
|
73
103
|
|
|
@@ -82,20 +112,19 @@ Marker-and-language-scoped final typography uses a package-local font stack and
|
|
|
82
112
|
|
|
83
113
|
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; package verification does not replace actual product acceptance.
|
|
84
114
|
|
|
85
|
-
|
|
115
|
+
Without `service`, 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
116
|
|
|
87
117
|
```tsx
|
|
88
|
-
<NaeilHeader locale={locale} currentHref={pathname} Link={GuardedLink}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
statusSlot={readinessStatus} mobileOpen={mobileOpen} onMobileOpenChange={setMobileOpen}
|
|
118
|
+
<NaeilHeader service="home" locale={locale} currentHref={pathname} Link={GuardedLink}
|
|
119
|
+
appNavigation={navigationItems}
|
|
120
|
+
mobileOpen={mobileOpen} onMobileOpenChange={setMobileOpen}
|
|
92
121
|
onLocaleChange={changeLanguage}
|
|
93
122
|
authSlot={<NaeilHeaderAccountControl locale={locale} account={account} />} />
|
|
94
123
|
```
|
|
95
124
|
|
|
96
|
-
Keep the
|
|
125
|
+
Keep the shared theme/locale/separator/account toolbar; the service profile rejects toolbar replacement. 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.
|
|
97
126
|
|
|
98
|
-
## Page-root frame policy (0.9.0
|
|
127
|
+
## Page-root frame policy (since 0.9.0)
|
|
99
128
|
|
|
100
129
|
Both supported CSS paths obtain the stable root rule and scoped Radix duplicate-compensation correction from `components.css`. Theme-only and JS-only imports do not set document scrolling. Declare `<html data-naeil-scrollbar-gutter="auto">` before first paint to disable both pieces for a custom policy. See [application layout](application-layout.md) for custom-body/scroller/helper-class limits, fallback and the [adopted contract](https://github.com/naeil-dev/naeil-ui/blob/434b7d07235316097ca3a55265ee02c1422b0cfd/docs/superpowers/specs/2026-10-29-page-root-scrollbar-gutter.md) for required probes and capture ordering.
|
|
101
130
|
|