@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.
Files changed (35) hide show
  1. package/CHANGELOG.md +10 -1
  2. package/README.md +15 -10
  3. package/dist/{chunk-QQQQZEPV.js → chunk-KPE4KKYM.js} +111 -14
  4. package/dist/{chunk-OLKTEYLS.js → chunk-ULZPJI5X.js} +103 -73
  5. package/dist/{chunk-VWIUN35X.js → chunk-WA5D7UI2.js} +1 -1
  6. package/dist/components/index.d.ts +2 -2
  7. package/dist/components/index.js +3 -3
  8. package/dist/components/naeil-footer.d.ts +2 -2
  9. package/dist/components/naeil-footer.js +3 -1
  10. package/dist/components/naeil-header.d.ts +8 -2
  11. package/dist/components/naeil-header.js +3 -3
  12. package/dist/components/ui/index.d.ts +2 -2
  13. package/dist/components/ui/index.js +1 -1
  14. package/dist/lib/ui-release.d.ts +1 -1
  15. package/dist/lib/ui-release.js +1 -1
  16. package/dist/{navigation-CgPeL9tF.d.ts → navigation-D8N1tMTu.d.ts} +4 -2
  17. package/dist/{site-footer-vXDa_6yr.d.ts → site-footer-s113FZgC.d.ts} +1 -1
  18. package/dist/source/components/naeil-footer.js +2 -0
  19. package/dist/source/components/naeil-header-account.js +54 -19
  20. package/dist/source/components/naeil-header-content.d.ts +1 -25
  21. package/dist/source/components/naeil-header-content.js +0 -28
  22. package/dist/source/components/naeil-header.d.ts +8 -2
  23. package/dist/source/components/naeil-header.js +50 -24
  24. package/dist/source/components/ui/navigation.d.ts +8 -2
  25. package/dist/source/components/ui/navigation.js +112 -15
  26. package/dist/source/lib/ui-release.d.ts +1 -1
  27. package/dist/source/lib/ui-release.js +1 -1
  28. package/docs/components/README.md +3 -3
  29. package/docs/components/application-layout.md +48 -13
  30. package/docs/components/naeil-footer.md +2 -2
  31. package/docs/components/naeil-header.md +37 -7
  32. package/docs/design/v2-migration.md +11 -10
  33. package/docs/package-release.md +44 -14
  34. package/package.json +1 -1
  35. package/src/styles/components.css +113 -0
package/CHANGELOG.md CHANGED
@@ -1,9 +1,18 @@
1
1
  # Changelog
2
2
 
3
- ## 0.7.1 — account presentation correction (candidate)
3
+ ## 0.7.2 — retired project catalog (candidate)
4
+
5
+ - Narrow the canonical `NaeilHeader` project catalog to naeil-ui. Content Collector, PKM, Sustainability Analyzer and Baby Agent are retired from naeil.dev; their site pages move to `src/app/_archive/` and old URLs temporarily redirect to the project list. The project menu uses one column for a single entry. `projects`, `translations` and `currentProjectKey` overrides are unchanged.
6
+ - Release class `dom`: the standing worker adopts it only through an owner-approved `reviewedMigration` of this exact release; ordinary fix-only patches are unaffected.
7
+
8
+ ## 0.7.1 — canonical service chrome (published 2026-10-23)
9
+
10
+ Published to npm at `2026-10-23T01:14:11.387Z`; `latest` was verified as `0.7.1`. This first published 0.7.x release includes the unpublished 0.7.0 scope below. Frozen package source and tag `v0.7.1`: `32108fd8e1cb1db6dae24a09f3ca72ac9a91f618`; archive SHA-256: `ecd656c595fd63516417eef58a5e2624d1f1bf928ba0465761292d05502ab3f8`. Independent download and fresh React/Next registry consumers matched the archive and all 139 files, with types, production builds and browser checks passing. Hosted Docs and standing activation require separate receipts; see the [release policy](docs/package-release.md#publication-and-deployment).
4
11
 
5
12
  - Add canonical `NaeilHeaderAccountControl` for shared login/account/logout presentation while consumers retain session and save guards.
13
+ - Include React-only `NaeilFooter`, canonical full/compact destinations, save-navigation interception and server-safe `/release` package/contract identity from the unpublished 0.7.0 candidate.
6
14
  - Preserve existing header slots and imports; include authenticated menu/busy/failure coverage and packed consumer checks.
15
+ - Preserve the approved 1600px/clamp frame, generic Navigation/SiteFooter and legacy Nav/Footer APIs. Bundled guides retain their frozen preparation snapshot; maintained guides and hosted Docs use a separately reviewed source.
7
16
 
8
17
  ## 0.7.0 — unpublished candidate
9
18
 
package/README.md CHANGED
@@ -2,24 +2,26 @@
2
2
 
3
3
  React components, semantic design tokens, and shared styles for naeil products. The approved system uses a neutral light/dark palette, Pretendard, 16px controls, comfortable or compact density, purpose-specific content widths, and restrained motion. Radix supplies interaction behavior.
4
4
 
5
+ **0.7.1 published and registry-verified — 2026-10-23.** The current published baseline is **0.7.1**. This first published 0.7.x release combines canonical `NaeilFooter`, server-safe `/release` identity and `NaeilHeaderAccountControl`; 0.7.0 remains an unpublished candidate. The frozen package source and `v0.7.1` tag are `32108fd8e1cb1db6dae24a09f3ca72ac9a91f618`. Fresh React/Next registry consumers matched all 139 frozen files and passed types, production builds and browser checks. Maintained guides use separately reviewed source; hosted Docs and standing activation require separate receipts. See the [release policy and receipts](docs/package-release.md#publication-and-deployment).
6
+
5
7
  This repository also contains the **naeil.dev brand/example website** and an **optional agent reference workflow**. Choose the path you need:
6
8
 
7
9
  | Path | Start here | Tools / keys |
8
10
  | --- | --- | --- |
9
- | Use the UI in an app | [Consumer guide](docs/design/v2-migration.md) | React 19, Tailwind 4; **no Next.js or API keys** for `/ui` or `/naeil-header` |
11
+ | Use the UI in an app | [Consumer guide](docs/design/v2-migration.md) | React 19, Tailwind 4; **no Next.js or API keys** for shared React entries |
10
12
  | Develop the shared UI | Commands below and [contributing](https://github.com/naeil-dev/naeil-ui/blob/81fd395031dc5b20074c4d915a8585360ce2d28a/CONTRIBUTING.md) | Node.js 22+, pnpm; no Supabase or 21st key |
11
13
  | Use frontend references with an agent | [Optional workflow setup](https://github.com/naeil-dev/naeil-ui/blob/81fd395031dc5b20074c4d915a8585360ce2d28a/docs/design/frontend-tooling.md) | Supported agent; Python for bundled helpers; your own 21st key only for optional MCP |
12
14
  | Develop the brand/example website | [Site setup](https://github.com/naeil-dev/naeil-ui/blob/81fd395031dc5b20074c4d915a8585360ce2d28a/docs/site-development.md) | Next.js and a separate Supabase project for site authentication |
13
15
 
14
16
  ## Use the UI
15
17
 
16
- This guide targets **@naeil/ui 0.6.0** with React 19 / ReactDOM 19 and Tailwind 4. UI v2 names the design generation independently of npm versioning. Install the matching release in your app:
18
+ Use React 19 / ReactDOM 19 and Tailwind 4. UI v2 names the design generation independently of npm versioning. Install the verified registry release:
17
19
 
18
20
  ```sh
19
- pnpm add --save-exact @naeil/ui@0.6.0
21
+ pnpm add --save-exact @naeil/ui@0.7.1
20
22
  ```
21
23
 
22
- For a local candidate, run `pnpm build:pkg` and `npm pack --ignore-scripts --pack-destination /tmp`, then install `/tmp/naeil-ui-0.6.0.tgz`. The [release record](https://github.com/naeil-dev/naeil-ui/releases/tag/v0.6.0) tracks archive identity, publication and deployment separately.
24
+ The [npm 0.7.1 archive](https://registry.npmjs.org/@naeil/ui/-/ui-0.7.1.tgz) is byte-identical to the frozen release archive. Its bundled guides retain the preparation snapshot; use these maintained instructions for current installation. For local development, `pnpm build:pkg` and `npm pack --ignore-scripts --pack-destination /tmp` produce `/tmp/naeil-ui-0.7.1.tgz`. A fresh pack of maintained source differs from the frozen release archive; use the exact frozen file for release verification. The [0.6.0 release record](https://github.com/naeil-dev/naeil-ui/releases/tag/v0.6.0) retains its archive, publication and deployment evidence.
23
25
 
24
26
  The preceding [0.5.0 release](https://github.com/naeil-dev/naeil-ui/releases/tag/v0.5.0) was published on 2026-10-15 and introduced the whole shared header. Version 0.6.0 adds the shared 1600px frame; existing applications must upgrade and adopt its layout composition.
25
27
 
@@ -30,11 +32,13 @@ Choose the shared application composition before building a header or footer:
30
32
  | Application need | Shared component |
31
33
  | --- | --- |
32
34
  | Actual naeil.dev brand, project catalog and controls | `NaeilHeader` from `@naeil/ui/naeil-header` |
35
+ | Owned-service login, account and logout presentation (0.7.1) | `NaeilHeaderAccountControl` from `@naeil/ui/naeil-header`, passed in `authSlot` |
36
+ | Owned naeil service footer (0.7.1) | `NaeilFooter` from `@naeil/ui/naeil-footer` |
33
37
  | A product's own menu | `Navigation` from `@naeil/ui/ui` |
34
- | Full or compact application footer | `SiteFooter` from `@naeil/ui/ui` |
38
+ | Different product footer or deliberate content override | `SiteFooter` from `@naeil/ui/ui` |
35
39
  | Existing Next/next-intl integration | Preserve `Nav` / `Footer` compatibility adapters |
36
40
 
37
- Supply application routing, guards, authentication and menu data through adapters and slots. Keep the shared menu, mobile sheet, controls and footer implementation in the package. The [application layout guide](docs/components/application-layout.md) provides a starter and consumer-agent instructions. Header, main and footer share a 1600px maximum frame with `clamp(16px, 2.4vw, 40px)` gutters; use `ui-app-frame` on main and keep 640px reading/settings or 480px short-form sections inside it.
41
+ Pair `NaeilHeader` and `NaeilFooter` in owned services. Supply application routing, guards, authentication and menu data through adapters and slots. `NaeilHeaderAccountControl` receives session state, a busy flag and the guarded logout callback; keep pending state and its request lock above the header so desktop/mobile copies share them. Keep the shared menu, mobile sheet, account presentation and footer implementation in the package. The [application layout guide](docs/components/application-layout.md) provides a starter and consumer-agent instructions. Header, main and footer share a 1600px maximum frame with `clamp(16px, 2.4vw, 40px)` gutters; use `ui-app-frame` on main and keep 640px reading/settings or 480px short-form sections inside it.
38
42
 
39
43
  Import primitives from the dedicated entrypoint:
40
44
 
@@ -73,9 +77,9 @@ Mark Japanese content with `lang="ja"`. Keep font OFL notices with redistributed
73
77
 
74
78
  Shared CSS follows the OS theme unless the document root has `class="light"` or `class="dark"`. For compact spacing, set `data-ui-density="compact"` on that root; omit it for comfortable spacing. Density changes spacing while retaining 16px control text and minimum mobile touch sizes. React apps can also use `ThemeProvider` from `@naeil/ui/components/theme-provider` with `attribute="class"`.
75
79
 
76
- The [component guides](docs/components/README.md) cover exact APIs, state composition and accessibility responsibilities. Each has a built Storybook Docs page and runnable Usage example. The [consumer guide](docs/design/v2-migration.md) covers peer dependencies, fonts, themes, density, Select versus DropdownMenu, overrides, and layout ownership. Fonts are delivered by the consumer; shared CSS does not fetch them automatically. `/ui`, `/utils` and the opt-in `/naeil-header` work without Next.js. The legacy root remains a framework compatibility entry with static Next/next-intl imports; install those optional peers when using it, Nav, Footer or i18n/routing. ThemeProvider/controls and Toaster retain the React library next-themes. Shared deep imports use the exact [allowlist and migration](docs/design/v2-migration.md#03-package-boundary); website-only paths belong in your application.
80
+ The [component guides](docs/components/README.md) cover exact APIs, state composition and accessibility responsibilities, with matching Storybook Docs and runnable examples. Canonical footer and account examples live in the existing NaeilHeader/SiteFooter stories. The [consumer guide](docs/design/v2-migration.md) covers peer dependencies, fonts, themes, density, Select versus DropdownMenu, overrides, and layout ownership. Fonts are delivered by the consumer; shared CSS does not fetch them automatically. `/ui`, `/utils`, `/naeil-header`, `/naeil-footer` and `/release` work without Next.js; the last two are included in 0.7.1. The legacy root remains a framework compatibility entry with static Next/next-intl imports; install those optional peers when using it, Nav, Footer or i18n/routing. ThemeProvider/controls and Toaster retain the React library next-themes. Shared deep imports use the exact [allowlist and migration](docs/design/v2-migration.md#03-package-boundary); website-only paths belong in your application.
77
81
 
78
- For the actual naeil.dev branded header, import `NaeilHeader` from `@naeil/ui/naeil-header`; generic `Navigation` from `/ui` remains available for different product menus. The whole header owns its catalog, header translations, controls and responsive styles. Supply routing/dirty guards, an existing next-themes provider and real authentication through its props instead of copying the header. Its five decorative project images reference official `naeil.dev` URLs and require network access; their bytes are excluded and unresolved redistribution provenance remains in [third-party notices](THIRD_PARTY_NOTICES.md). See [NaeilHeader](docs/components/naeil-header.md) for fixed positioning, the shared frame, supported locales and adapter contract.
82
+ For the actual naeil.dev branded header, import `NaeilHeader` and `NaeilHeaderAccountControl` from `@naeil/ui/naeil-header`; generic `Navigation` from `/ui` remains available for different product menus. The whole header owns its catalog, header translations, controls and responsive styles. Supply routing/dirty guards, an existing next-themes provider and real authentication state/actions through its props. Its five decorative project images reference official `naeil.dev` URLs and require network access; their bytes are excluded and unresolved redistribution provenance remains in [third-party notices](THIRD_PARTY_NOTICES.md). See [NaeilHeader](docs/components/naeil-header.md) for fixed positioning, the shared frame, supported locales and adapter contract, and [NaeilFooter](docs/components/naeil-footer.md) for canonical destinations and save interception.
79
83
 
80
84
  ## Develop the shared UI
81
85
 
@@ -92,7 +96,8 @@ Storybook uses the actual public components with simulated example data. It need
92
96
  ```sh
93
97
  pnpm build:pkg
94
98
  pnpm pack --pack-destination /tmp
95
- # In your consumer app: pnpm add /tmp/naeil-ui-0.6.0.tgz
99
+ # In your consumer app: pnpm add /tmp/naeil-ui-0.7.1.tgz
100
+ # This development pack does not replace the frozen release archive.
96
101
  ```
97
102
 
98
103
  | Command | Purpose |
@@ -104,7 +109,7 @@ pnpm pack --pack-destination /tmp
104
109
  | `pnpm build:pkg` / `pnpm check:package` | Build package / inspect package boundary and independently install/build packed React + Next consumers |
105
110
  | `pnpm dev` / `pnpm build` | Brand/example website development / build |
106
111
 
107
- A [reusable React consumer example](https://github.com/naeil-dev/naeil-ui/tree/main/examples/react) composes the packaged header, a settings form and footer. [Release policy](docs/package-release.md) and [changelog](CHANGELOG.md) distinguish implementation, publication and deployment. CI verifies the package and separate website/Storybook. Downloadable preview artifacts include notices and local fonts. The current local Storybook build contains 17 Docs pages (15 UI families and Brand / Logo plus NaeilHeader), including the whole-header Usage and JapaneseApplication examples. This does not establish a new hosted deployment. [Hosted Docs](https://naeil-dev.github.io/naeil-ui/) have recorded [0.5.0 verification](https://github.com/naeil-dev/naeil-ui/releases/download/v0.5.0/hosted-docs-verification.json). The current release record identifies the subsequent deployed source and its fresh checks. The same approved brand is live on [naeil.dev](https://naeil.dev/). See [preview instructions](docs/package-release.md#storybook-preview-artifact) and [support limits](docs/design/public-ui-support.md). Native/manual platform acceptance and adoption in a real consumer product remain open.
112
+ A [reusable React consumer example](https://github.com/naeil-dev/naeil-ui/tree/main/examples/react) composes the packaged header, a settings form and footer. [Release policy](docs/package-release.md) and [changelog](CHANGELOG.md) distinguish implementation, publication and deployment. CI verifies the package and separate website/Storybook. Downloadable preview artifacts include notices and local fonts. The current Storybook source defines 17 Docs pages (15 UI families and Brand / Logo plus NaeilHeader), including Usage, JapaneseApplication, ReferenceFull, ReferenceCompact and AccountStates examples. This does not establish a new hosted deployment. [Hosted Docs](https://naeil-dev.github.io/naeil-ui/) have recorded [0.5.0 verification](https://github.com/naeil-dev/naeil-ui/releases/download/v0.5.0/hosted-docs-verification.json); subsequent deployments require their own source and HTTP checks. The same approved brand is live on [naeil.dev](https://naeil.dev/). See [preview instructions](docs/package-release.md#storybook-preview-artifact) and [support limits](docs/design/public-ui-support.md). Native/manual platform acceptance retains its documented limits; service adoption and continuous rollout have separate [dated records](https://github.com/naeil-dev/naeil-ui/tree/main/docs/rollout).
108
113
 
109
114
  Read [DESIGN.md](DESIGN.md) before shared UI changes. Numeric values live in `src/tokens/`; generated CSS is not edited by hand. Shared styles live in `src/styles/`, site-only styles in `src/app/`. Hero art, 3D scenes, cursor effects, and site content belong to the example website.
110
115
 
@@ -343,13 +343,15 @@ var NativeLink = React.forwardRef(
343
343
  return /* @__PURE__ */ jsx4("a", __spreadValues({ ref }, props));
344
344
  }
345
345
  );
346
+ var NavigationSheetAction = React.createContext(void 0);
346
347
  function DesktopGroup({
347
348
  item,
348
349
  active,
349
350
  open,
350
351
  anotherGroupOpen,
351
352
  onOpenChange,
352
- children
353
+ children,
354
+ chrome
353
355
  }) {
354
356
  var _a;
355
357
  const hoverOpened = React.useRef(false);
@@ -428,7 +430,7 @@ function DesktopGroup({
428
430
  ),
429
431
  /* @__PURE__ */ jsx4(Popover.Portal, { children: /* @__PURE__ */ jsx4(
430
432
  Popover.Content,
431
- {
433
+ __spreadProps(__spreadValues({}, chrome), {
432
434
  ref: panel,
433
435
  "data-slot": "navigation-popover",
434
436
  className: "ui-navigation-popover ui-popup",
@@ -454,7 +456,7 @@ function DesktopGroup({
454
456
  event.preventDefault();
455
457
  },
456
458
  children: /* @__PURE__ */ jsx4("nav", { "aria-label": item.label, className: "ui-navigation-menu-grid", children })
457
- }
459
+ })
458
460
  ) })
459
461
  ]
460
462
  }
@@ -470,6 +472,8 @@ function Navigation(_a) {
470
472
  toolbar,
471
473
  mobileToolbar,
472
474
  mobileFooter,
475
+ mobileOpen: controlledOpen,
476
+ onMobileOpenChange,
473
477
  containerClassName,
474
478
  onNavigate,
475
479
  className
@@ -482,18 +486,75 @@ function Navigation(_a) {
482
486
  "toolbar",
483
487
  "mobileToolbar",
484
488
  "mobileFooter",
489
+ "mobileOpen",
490
+ "onMobileOpenChange",
485
491
  "containerClassName",
486
492
  "onNavigate",
487
493
  "className"
488
494
  ]);
489
- const [mobileOpen, setMobileOpen] = React.useState(false);
495
+ const [internalOpen, setInternalOpen] = React.useState(false);
496
+ const mobileOpen = controlledOpen != null ? controlledOpen : internalOpen;
497
+ const state = React.useRef({ open: mobileOpen, controlled: controlledOpen !== void 0, onChange: onMobileOpenChange });
498
+ const mounted = React.useRef(false);
499
+ const pendingAction = React.useRef(void 0);
500
+ const cancelAction = React.useCallback(() => {
501
+ var _a2;
502
+ clearTimeout((_a2 = pendingAction.current) == null ? void 0 : _a2.timer);
503
+ pendingAction.current = void 0;
504
+ }, []);
505
+ React.useLayoutEffect(() => {
506
+ const reopening = mobileOpen && !state.current.open;
507
+ state.current = { open: mobileOpen, controlled: controlledOpen !== void 0, onChange: onMobileOpenChange };
508
+ if (reopening) cancelAction();
509
+ });
510
+ React.useLayoutEffect(() => {
511
+ mounted.current = true;
512
+ return () => {
513
+ mounted.current = false;
514
+ cancelAction();
515
+ };
516
+ }, [cancelAction]);
517
+ const setMobileOpen = React.useCallback((next, owner) => {
518
+ var _a2, _b2;
519
+ if (next) cancelAction();
520
+ if (next === state.current.open) return;
521
+ if (pendingAction.current !== owner) cancelAction();
522
+ if (!state.current.controlled) {
523
+ state.current.open = next;
524
+ setInternalOpen(next);
525
+ }
526
+ (_b2 = (_a2 = state.current).onChange) == null ? void 0 : _b2.call(_a2, next);
527
+ }, [cancelAction]);
528
+ const chrome = "data-naeil-chrome" in props ? { "data-naeil-chrome": "", lang: props.lang } : {};
490
529
  const [openGroup, setOpenGroup] = React.useState(null);
491
530
  const trigger = React.useRef(null);
492
531
  const desktop = React.useRef(null);
532
+ const sheetContent = React.useRef(null);
533
+ function settleAction(flow) {
534
+ if (!mounted.current || pendingAction.current !== flow || state.current.open || !flow.menuClosed || !flow.sheetClosed || flow.timer !== void 0) return;
535
+ flow.timer = setTimeout(() => {
536
+ var _a2, _b2;
537
+ flow.timer = void 0;
538
+ if (!mounted.current || pendingAction.current !== flow) return;
539
+ if (state.current.open) {
540
+ cancelAction();
541
+ return;
542
+ }
543
+ if (!flow.menuClosed || !flow.sheetClosed) return;
544
+ 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);
545
+ cancelAction();
546
+ if (!target) return;
547
+ target.focus();
548
+ flow.action();
549
+ }, 0);
550
+ }
551
+ const previousHref = React.useRef(currentHref);
493
552
  React.useEffect(() => {
553
+ if (previousHref.current === currentHref) return;
554
+ previousHref.current = currentHref;
494
555
  setMobileOpen(false);
495
556
  setOpenGroup(null);
496
- }, [currentHref]);
557
+ }, [currentHref, setMobileOpen]);
497
558
  React.useEffect(() => {
498
559
  const element = desktop.current;
499
560
  if (!element || typeof ResizeObserver === "undefined") return;
@@ -503,7 +564,7 @@ function Navigation(_a) {
503
564
  });
504
565
  observer.observe(element);
505
566
  return () => observer.disconnect();
506
- }, []);
567
+ }, [setMobileOpen]);
507
568
  const active = (item) => {
508
569
  var _a2;
509
570
  return (_a2 = item.active) != null ? _a2 : !item.external && item.href === currentHref;
@@ -523,8 +584,10 @@ function Navigation(_a) {
523
584
  ),
524
585
  onClick: (event) => {
525
586
  onNavigate == null ? void 0 : onNavigate(event, item);
526
- if (event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey)
527
- close == null ? void 0 : close();
587
+ if (event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey) {
588
+ if (close === "mobile") setMobileOpen(false);
589
+ else if (close === "group") setOpenGroup(null);
590
+ }
528
591
  },
529
592
  children: [
530
593
  item.icon && /* @__PURE__ */ jsx4("span", { className: "ui-navigation-icon", "aria-hidden": "true", children: item.icon }),
@@ -559,6 +622,7 @@ function Navigation(_a) {
559
622
  (item) => "items" in item ? /* @__PURE__ */ jsx4(
560
623
  DesktopGroup,
561
624
  {
625
+ chrome,
562
626
  item,
563
627
  active: item.items.some(active),
564
628
  open: openGroup === item.id,
@@ -567,7 +631,7 @@ function Navigation(_a) {
567
631
  (current) => open ? item.id : current === item.id ? null : current
568
632
  ),
569
633
  children: item.items.map(
570
- (child) => link(child, () => setOpenGroup(null), true)
634
+ (child) => link(child, "group", true)
571
635
  )
572
636
  },
573
637
  item.id
@@ -596,12 +660,26 @@ function Navigation(_a) {
596
660
  ),
597
661
  /* @__PURE__ */ jsxs2(
598
662
  Dialog.Content,
599
- {
663
+ __spreadProps(__spreadValues({}, chrome), {
664
+ ref: (element) => {
665
+ if (element) sheetContent.current = element;
666
+ },
600
667
  "data-slot": "navigation-sheet",
601
668
  className: "ui-navigation-sheet ui-popup",
602
669
  "aria-describedby": void 0,
603
670
  onCloseAutoFocus: (event) => {
604
671
  var _a2, _b2;
672
+ if (controlledOpen !== void 0 && (!mounted.current || state.current.open)) {
673
+ event.preventDefault();
674
+ return;
675
+ }
676
+ const flow = pendingAction.current;
677
+ if (flow && event.target === flow.sheet) {
678
+ event.preventDefault();
679
+ flow.sheetClosed = true;
680
+ settleAction(flow);
681
+ return;
682
+ }
605
683
  if (trigger.current && trigger.current.getBoundingClientRect().width === 0) {
606
684
  event.preventDefault();
607
685
  (_b2 = (_a2 = desktop.current) == null ? void 0 : _a2.querySelector("button, a")) == null ? void 0 : _b2.focus();
@@ -628,16 +706,34 @@ function Navigation(_a) {
628
706
  (item) => "items" in item ? /* @__PURE__ */ jsxs2("section", { "aria-label": item.label, children: [
629
707
  /* @__PURE__ */ jsx4("h3", { className: "ui-navigation-group-label", children: item.label }),
630
708
  item.items.map(
631
- (child) => link(child, () => setMobileOpen(false), true)
709
+ (child) => link(child, "mobile", true)
632
710
  )
633
- ] }, item.id) : link(item, () => setMobileOpen(false))
711
+ ] }, item.id) : link(item, "mobile")
634
712
  )
635
713
  }
636
714
  ),
637
- (mobileToolbar != null ? mobileToolbar : toolbar) && /* @__PURE__ */ jsx4("div", { className: "ui-navigation-mobile-toolbar", children: mobileToolbar != null ? mobileToolbar : toolbar }),
715
+ (mobileToolbar != null ? mobileToolbar : toolbar) && /* @__PURE__ */ jsx4("div", { className: "ui-navigation-mobile-toolbar", children: /* @__PURE__ */ jsx4(NavigationSheetAction.Provider, { value: controlledOpen === void 0 ? void 0 : (action) => {
716
+ if (!mounted.current) return { menuClosed: () => {
717
+ }, cancel: () => {
718
+ } };
719
+ cancelAction();
720
+ const flow = { action, sheet: sheetContent.current, menuClosed: false, sheetClosed: false };
721
+ pendingAction.current = flow;
722
+ return {
723
+ menuClosed: () => {
724
+ if (!mounted.current || pendingAction.current !== flow) return;
725
+ flow.menuClosed = true;
726
+ setMobileOpen(false, flow);
727
+ settleAction(flow);
728
+ },
729
+ cancel: () => {
730
+ if (pendingAction.current === flow) cancelAction();
731
+ }
732
+ };
733
+ }, children: mobileToolbar != null ? mobileToolbar : toolbar }) }),
638
734
  mobileFooter && /* @__PURE__ */ jsx4("div", { className: "ui-navigation-mobile-footer", children: mobileFooter })
639
735
  ]
640
- }
736
+ })
641
737
  )
642
738
  ] })
643
739
  ] })
@@ -894,6 +990,7 @@ export {
894
990
  SelectSeparator,
895
991
  SelectScrollUpButton,
896
992
  SelectScrollDownButton,
993
+ NavigationSheetAction,
897
994
  Navigation,
898
995
  DropdownMenu,
899
996
  DropdownMenuPortal,
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  Logo,
4
4
  uiRelease
5
- } from "./chunk-VWIUN35X.js";
5
+ } from "./chunk-WA5D7UI2.js";
6
6
  import {
7
7
  Badge,
8
8
  Button,
@@ -13,12 +13,13 @@ import {
13
13
  DropdownMenuSeparator,
14
14
  DropdownMenuTrigger,
15
15
  Navigation,
16
+ NavigationSheetAction,
16
17
  Select,
17
18
  SelectContent,
18
19
  SelectItem,
19
20
  SelectTrigger,
20
21
  SelectValue
21
- } from "./chunk-QQQQZEPV.js";
22
+ } from "./chunk-KPE4KKYM.js";
22
23
  import {
23
24
  __objRest,
24
25
  __spreadProps,
@@ -35,40 +36,12 @@ import { Moon, Sun } from "lucide-react";
35
36
  var headerLocales = ["en", "ko", "ja"];
36
37
  var headerLocaleNames = { en: "English", ko: "\uD55C\uAD6D\uC5B4", ja: "\u65E5\u672C\u8A9E" };
37
38
  var headerProjects = [
38
- {
39
- "key": "cc",
40
- "href": "/cc",
41
- "accent": "oklch(0.704 0.140 181)",
42
- "icon": "coral",
43
- "status": "active"
44
- },
45
- {
46
- "key": "pkm",
47
- "href": "/pkm",
48
- "accent": "oklch(0.702 0.183 293)",
49
- "icon": "jellyfish",
50
- "status": "coming"
51
- },
52
39
  {
53
40
  "key": "naeilUi",
54
41
  "href": "/design",
55
42
  "accent": "oklch(0.623 0.214 259)",
56
43
  "icon": "whale",
57
44
  "status": "active"
58
- },
59
- {
60
- "key": "sa",
61
- "href": "/sa",
62
- "accent": "oklch(0.723 0.219 149)",
63
- "icon": "fish",
64
- "status": "active"
65
- },
66
- {
67
- "key": "babyAgent",
68
- "href": "/baby-agent",
69
- "accent": "oklch(0.769 0.188 70.08)",
70
- "icon": "turtle",
71
- "status": "coming"
72
45
  }
73
46
  ];
74
47
  var headerContent = {
@@ -205,6 +178,7 @@ var headerContent = {
205
178
 
206
179
  // src/components/naeil-header-account.tsx
207
180
  import { UserRound } from "lucide-react";
181
+ import { useContext, useRef } from "react";
208
182
  import { jsx, jsxs } from "react/jsx-runtime";
209
183
  var labels = {
210
184
  en: { menu: "Account menu", account: "Account", login: "Log in", logout: "Log out", busy: "Logging out\u2026" },
@@ -214,10 +188,18 @@ var labels = {
214
188
  var NativeLink = (props) => /* @__PURE__ */ jsx("a", __spreadValues({}, props));
215
189
  function NaeilHeaderAccountControl({ locale, account, Link = NativeLink }) {
216
190
  const text = labels[locale];
191
+ const handoff = useContext(NavigationSheetAction);
192
+ const pendingAction = useRef(void 0);
217
193
  if (account.status === "signed-out") {
218
194
  return /* @__PURE__ */ jsx(Link, { href: account.loginHref, className: "ui-navigation-link ui-focus naeil-header-login", "data-naeil-account-state": "signed-out", children: text.login });
219
195
  }
220
- return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
196
+ return /* @__PURE__ */ jsxs(DropdownMenu, { onOpenChange: (open) => {
197
+ var _a;
198
+ if (open) {
199
+ (_a = pendingAction.current) == null ? void 0 : _a.cancel();
200
+ pendingAction.current = void 0;
201
+ }
202
+ }, children: [
221
203
  /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
222
204
  "button",
223
205
  {
@@ -230,24 +212,46 @@ function NaeilHeaderAccountControl({ locale, account, Link = NativeLink }) {
230
212
  children: /* @__PURE__ */ jsx("span", { className: "naeil-header-account-avatar", "aria-hidden": "true", children: /* @__PURE__ */ jsx(UserRound, {}) })
231
213
  }
232
214
  ) }),
233
- /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", className: "w-56 max-w-[calc(100vw-2rem)]", "data-naeil-account-menu": true, children: [
234
- /* @__PURE__ */ jsxs(DropdownMenuLabel, { className: "flex min-w-0 flex-col font-normal", children: [
235
- /* @__PURE__ */ jsx("span", { className: "truncate text-base font-medium", children: account.displayName || text.account }),
236
- account.email && /* @__PURE__ */ jsx("span", { className: "truncate text-sm text-muted-foreground", children: account.email })
237
- ] }),
238
- /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
239
- /* @__PURE__ */ jsx(
240
- DropdownMenuItem,
241
- {
242
- className: "min-h-11",
243
- disabled: account.busy,
244
- onSelect: () => {
245
- if (!account.busy) account.onLogout();
246
- },
247
- children: account.busy ? text.busy : text.logout
248
- }
249
- )
250
- ] })
215
+ /* @__PURE__ */ jsxs(
216
+ DropdownMenuContent,
217
+ {
218
+ align: "end",
219
+ className: "w-56 max-w-[calc(100vw-2rem)]",
220
+ "data-naeil-account-menu": true,
221
+ "data-naeil-chrome": "",
222
+ lang: locale,
223
+ onCloseAutoFocus: (event) => {
224
+ const flow = pendingAction.current;
225
+ if (!flow || event.target !== flow.menu) return;
226
+ event.preventDefault();
227
+ flow.menu = null;
228
+ flow.menuClosed();
229
+ },
230
+ children: [
231
+ /* @__PURE__ */ jsxs(DropdownMenuLabel, { className: "flex min-w-0 flex-col font-normal", children: [
232
+ /* @__PURE__ */ jsx("span", { className: "truncate text-base font-medium", children: account.displayName || text.account }),
233
+ account.email && /* @__PURE__ */ jsx("span", { className: "truncate text-sm text-muted-foreground", children: account.email })
234
+ ] }),
235
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
236
+ /* @__PURE__ */ jsx(
237
+ DropdownMenuItem,
238
+ {
239
+ className: "min-h-11",
240
+ disabled: account.busy,
241
+ onSelect: (event) => {
242
+ if (account.busy) return;
243
+ if (handoff) {
244
+ const menu = event.target.closest("[data-naeil-account-menu]");
245
+ if (!(menu == null ? void 0 : menu.isConnected)) return;
246
+ pendingAction.current = __spreadProps(__spreadValues({}, handoff(account.onLogout)), { menu });
247
+ } else account.onLogout();
248
+ },
249
+ children: account.busy ? text.busy : text.logout
250
+ }
251
+ )
252
+ ]
253
+ }
254
+ )
251
255
  ] });
252
256
  }
253
257
 
@@ -282,7 +286,7 @@ function NaeilHeaderLocaleControl({ locale, onLocaleChange }) {
282
286
  children: /* @__PURE__ */ jsx2(SelectValue, { children: locale.toUpperCase() })
283
287
  }
284
288
  ),
285
- /* @__PURE__ */ jsx2(SelectContent, { align: "end", children: headerLocales.map((value) => /* @__PURE__ */ jsx2(SelectItem, { value, className: "min-h-11", lang: value, children: headerLocaleNames[value] }, value)) })
289
+ /* @__PURE__ */ jsx2(SelectContent, { align: "end", "data-naeil-chrome": "", "data-naeil-locale-menu": "", lang: locale, children: headerLocales.map((value) => /* @__PURE__ */ jsx2(SelectItem, { value, className: "min-h-11", lang: value, children: headerLocaleNames[value] }, value)) })
286
290
  ] });
287
291
  }
288
292
  var NativeLink2 = (props) => /* @__PURE__ */ jsx2("a", __spreadValues({}, props));
@@ -292,6 +296,9 @@ function NaeilHeader(_a) {
292
296
  resolveHref = (path) => `https://naeil.dev/${locale}${path === "/" ? "" : path}`,
293
297
  brandHref,
294
298
  brandSlot,
299
+ brandSuffix,
300
+ statusSlot,
301
+ appNavigation,
295
302
  brandLinkProps,
296
303
  brandLinkComponent,
297
304
  onLocaleChange,
@@ -316,6 +323,9 @@ function NaeilHeader(_a) {
316
323
  "resolveHref",
317
324
  "brandHref",
318
325
  "brandSlot",
326
+ "brandSuffix",
327
+ "statusSlot",
328
+ "appNavigation",
319
329
  "brandLinkProps",
320
330
  "brandLinkComponent",
321
331
  "onLocaleChange",
@@ -358,41 +368,61 @@ function NaeilHeader(_a) {
358
368
  const brandDestination = brandHref != null ? brandHref : resolveHref("/");
359
369
  const BrandLink = brandLinkComponent != null ? brandLinkComponent : Link;
360
370
  const _a2 = brandLinkProps != null ? brandLinkProps : {}, { onClick: onBrandClick } = _a2, brandProps = __objRest(_a2, ["onClick"]);
361
- const toolbar = /* @__PURE__ */ jsx2("div", { "data-app-toolbar": true, className: "naeil-header-controls", children: toolbarSlot !== void 0 ? toolbarSlot : /* @__PURE__ */ jsxs2(Fragment, { children: [
362
- /* @__PURE__ */ jsx2(NaeilHeaderThemeControl, { locale }),
363
- onLocaleChange && /* @__PURE__ */ jsx2(NaeilHeaderLocaleControl, { locale, onLocaleChange }),
364
- authSlot && /* @__PURE__ */ jsxs2(Fragment, { children: [
365
- /* @__PURE__ */ jsx2("div", { "aria-hidden": "true", className: "naeil-header-separator" }),
366
- authSlot
367
- ] })
368
- ] }) });
371
+ const toolbar = /* @__PURE__ */ jsx2(
372
+ "div",
373
+ {
374
+ "data-app-toolbar": true,
375
+ className: "naeil-header-controls",
376
+ "data-naeil-chrome": toolbarSlot === void 0 ? "" : void 0,
377
+ lang: toolbarSlot === void 0 ? locale : void 0,
378
+ children: toolbarSlot !== void 0 ? toolbarSlot : /* @__PURE__ */ jsxs2(Fragment, { children: [
379
+ /* @__PURE__ */ jsx2(NaeilHeaderThemeControl, { locale }),
380
+ onLocaleChange && /* @__PURE__ */ jsx2(NaeilHeaderLocaleControl, { locale, onLocaleChange }),
381
+ authSlot && /* @__PURE__ */ jsxs2(Fragment, { children: [
382
+ /* @__PURE__ */ jsx2("div", { "aria-hidden": "true", className: "naeil-header-separator" }),
383
+ authSlot
384
+ ] })
385
+ ] })
386
+ }
387
+ );
388
+ const logo = /* @__PURE__ */ jsx2(Logo, { variant: "horizontal", size: 28, className: "block", label: null });
389
+ const brand = /* @__PURE__ */ jsx2(
390
+ BrandLink,
391
+ __spreadProps(__spreadValues({
392
+ href: brandDestination,
393
+ "data-app-brand": true,
394
+ "aria-label": brandSlot == null ? ".naeil" : void 0
395
+ }, brandProps), {
396
+ onClick: (event) => {
397
+ onBrandClick == null ? void 0 : onBrandClick(event);
398
+ onNavigate == null ? void 0 : onNavigate(event, { id: "brand", href: brandDestination, label: ".naeil" });
399
+ },
400
+ children: brandSlot != null ? brandSlot : brandSuffix ? /* @__PURE__ */ jsxs2(Fragment, { children: [
401
+ logo,
402
+ /* @__PURE__ */ jsx2("span", { className: "naeil-header-brand-suffix", children: brandSuffix })
403
+ ] }) : logo
404
+ })
405
+ );
369
406
  return /* @__PURE__ */ jsx2(
370
407
  Navigation,
371
408
  __spreadProps(__spreadValues({}, props), {
372
409
  "data-global-header": true,
373
410
  "data-naeil-header": true,
411
+ "data-naeil-chrome": "",
412
+ lang: locale,
374
413
  "data-naeil-ui-version": uiRelease.version,
375
414
  "data-naeil-ui-contract": uiRelease.contractVersion,
376
415
  Link,
377
416
  currentHref,
378
417
  onNavigate,
379
- brand: /* @__PURE__ */ jsx2(
380
- BrandLink,
381
- __spreadProps(__spreadValues({
382
- href: brandDestination,
383
- "data-app-brand": true,
384
- "aria-label": brandSlot == null ? ".naeil" : void 0
385
- }, brandProps), {
386
- onClick: (event) => {
387
- onBrandClick == null ? void 0 : onBrandClick(event);
388
- onNavigate == null ? void 0 : onNavigate(event, { id: "brand", href: brandDestination, label: ".naeil" });
389
- },
390
- children: brandSlot != null ? brandSlot : /* @__PURE__ */ jsx2(Logo, { variant: "horizontal", size: 28, className: "block", label: null })
391
- })
392
- ),
418
+ brand: statusSlot != null ? /* @__PURE__ */ jsxs2("div", { className: "naeil-header-brand-row", children: [
419
+ brand,
420
+ statusSlot
421
+ ] }) : brand,
393
422
  items: [
394
- ...showProjects ? [{ id: "projects", label: (_b2 = translations == null ? void 0 : translations.projects) != null ? _b2 : content.nav.projects, items: projectLinks, columns: 2 }] : [],
423
+ ...showProjects ? [{ id: "projects", label: (_b2 = translations == null ? void 0 : translations.projects) != null ? _b2 : content.nav.projects, items: projectLinks, columns: projectLinks.length > 1 ? 2 : 1 }] : [],
395
424
  ...showBlog ? [{ id: "blog", label: (_c = translations == null ? void 0 : translations.blog) != null ? _c : content.nav.blog, href: resolveHref("/blog"), external: projectsExternal }] : [],
425
+ ...appNavigation != null ? appNavigation : [],
396
426
  ...(subNav != null ? subNav : []).map((item) => __spreadProps(__spreadValues({}, item), { id: item.href }))
397
427
  ],
398
428
  labels: labels2 != null ? labels2 : { navigation: content.nav.navigation, menu: content.nav.menu, close: content.nav.closeMenu },
@@ -1165,7 +1165,7 @@ function Logo(_a) {
1165
1165
  }
1166
1166
 
1167
1167
  // src/lib/ui-release.ts
1168
- var uiRelease = { version: "0.7.1", contractVersion: "1" };
1168
+ var uiRelease = { version: "0.8.0", contractVersion: "1" };
1169
1169
 
1170
1170
  export {
1171
1171
  Logo,
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { N as NavigationLink, a as NavigationProps } from '../navigation-CgPeL9tF.js';
3
- import { S as SiteFooterProps } from '../site-footer-vXDa_6yr.js';
2
+ import { N as NavigationLink, a as NavigationProps } from '../navigation-D8N1tMTu.js';
3
+ import { S as SiteFooterProps } from '../site-footer-s113FZgC.js';
4
4
  import { ThemeProvider as ThemeProvider$1 } from 'next-themes';
5
5
  import * as React$1 from 'react';
6
6
  import { SVGProps } from 'react';