@naeil/ui 0.12.0 → 0.13.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 (77) hide show
  1. package/CHANGELOG.md +31 -3
  2. package/DESIGN.md +10 -2
  3. package/README.md +13 -11
  4. package/THIRD_PARTY_NOTICES.md +4 -2
  5. package/dist/{chunk-XLISHUJZ.js → chunk-5BXZPDHZ.js} +68 -8
  6. package/dist/{chunk-DBE7R4MR.js → chunk-A2DCQVTP.js} +1 -1
  7. package/dist/{chunk-KPE4KKYM.js → chunk-Y5LW3ADW.js} +24 -15
  8. package/dist/components/index.d.ts +3 -3
  9. package/dist/components/index.js +4 -4
  10. package/dist/components/naeil-footer.d.ts +2 -2
  11. package/dist/components/naeil-footer.js +1 -1
  12. package/dist/components/naeil-header.d.ts +23 -8
  13. package/dist/components/naeil-header.js +7 -5
  14. package/dist/components/ui/index.d.ts +2 -2
  15. package/dist/components/ui/index.js +6 -6
  16. package/dist/lib/ui-release.d.ts +1 -1
  17. package/dist/lib/ui-release.js +1 -1
  18. package/dist/lib/utils.js +1 -7
  19. package/dist/naeil-locale/checker.js +1 -1
  20. package/dist/naeil-locale/chrome.d.ts +25 -8
  21. package/dist/naeil-locale/chrome.js +91 -22
  22. package/dist/naeil-locale/{chunk-4U64K4J3.js → chunk-24IL53QO.js} +1 -1
  23. package/dist/naeil-locale/{chunk-U65OQHZY.js → chunk-WT6QXR7O.js} +14 -8
  24. package/dist/naeil-locale/client.js +45 -15
  25. package/dist/naeil-locale/index.js +1 -1
  26. package/dist/naeil-locale/messages.js +2 -2
  27. package/dist/naeil-locale/server.js +6 -6
  28. package/dist/{navigation-D8N1tMTu.d.ts → navigation-CXZapKac.d.ts} +3 -1
  29. package/dist/{site-footer-s113FZgC.d.ts → site-footer-C4jgkGDU.d.ts} +1 -1
  30. package/dist/source/components/naeil-header-core.d.ts +23 -8
  31. package/dist/source/components/naeil-header-core.js +66 -6
  32. package/dist/source/components/naeil-header.d.ts +6 -4
  33. package/dist/source/components/naeil-header.js +4 -2
  34. package/dist/source/components/typography.d.ts +1 -1
  35. package/dist/source/components/typography.js +1 -1
  36. package/dist/source/components/ui/button.js +7 -7
  37. package/dist/source/components/ui/card.js +1 -1
  38. package/dist/source/components/ui/dialog.js +2 -2
  39. package/dist/source/components/ui/navigation.d.ts +3 -1
  40. package/dist/source/components/ui/navigation.js +17 -8
  41. package/dist/source/components/ui/sonner.js +2 -2
  42. package/dist/source/lib/ui-release.d.ts +1 -1
  43. package/dist/source/lib/ui-release.js +1 -1
  44. package/docs/components/README.md +3 -1
  45. package/docs/components/application-layout.md +23 -27
  46. package/docs/components/avatar.md +1 -1
  47. package/docs/components/badge.md +3 -1
  48. package/docs/components/button.md +7 -1
  49. package/docs/components/card.md +5 -1
  50. package/docs/components/checkbox.md +1 -1
  51. package/docs/components/common.md +16 -1
  52. package/docs/components/dialog.md +6 -2
  53. package/docs/components/dropdown-menu.md +1 -1
  54. package/docs/components/input.md +1 -1
  55. package/docs/components/naeil-footer.md +1 -1
  56. package/docs/components/naeil-header.md +41 -12
  57. package/docs/components/naeil-locale.md +36 -0
  58. package/docs/components/navigation.md +10 -4
  59. package/docs/components/select.md +1 -1
  60. package/docs/components/sheet.md +1 -1
  61. package/docs/components/sonner.md +7 -1
  62. package/docs/components/switch.md +1 -1
  63. package/docs/components/tabs.md +1 -1
  64. package/docs/components/textarea.md +1 -1
  65. package/docs/design/public-ui-support.md +3 -1
  66. package/docs/design/v2-migration.md +11 -6
  67. package/docs/package-release.md +21 -11
  68. package/package.json +7 -10
  69. package/src/styles/components.css +29 -0
  70. package/src/styles/globals.css +6 -2
  71. package/src/styles/theme.css +5 -0
  72. package/licenses/Nunito-Sans-OFL.txt +0 -93
  73. package/licenses/React-MIT.txt +0 -21
  74. package/licenses/Storybook-MIT.txt +0 -21
  75. package/licenses/popper-MIT.txt +0 -25
  76. package/licenses/store2-MIT.txt +0 -22
  77. package/licenses/toggle-selection-MIT.txt +0 -21
@@ -13,6 +13,10 @@ import { NextIntlClientProvider } from "next-intl";
13
13
 
14
14
  // src/naeil-locale/routing.ts
15
15
  var naeilLocales = ["en", "ko", "ja"];
16
+ function escapeRegExp(value) {
17
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
18
+ }
19
+ var localePrefix = new RegExp(`^/(?:${naeilLocales.map(escapeRegExp).join("|")})(?:/|$)`);
16
20
  function isNaeilLocale(value) {
17
21
  return typeof value === "string" && naeilLocales.includes(value);
18
22
  }
@@ -27,7 +31,7 @@ var origins = {
27
31
  estimate: "https://abestimate.naeil.dev"
28
32
  };
29
33
  var protocolPrefixes = ["/api", "/auth", "/_next", "/_vercel", "/.well-known", "/health", "/invite", "/return", "/webhook", "/images", "/fonts"];
30
- var protocolFiles = ["/favicon.ico", "/icon.svg", "/apple-icon.png", "/robots.txt", "/sitemap.xml"];
34
+ var protocolFiles = ["/favicon.ico", "/favicon.svg", "/icon.svg", "/apple-icon.png", "/robots.txt", "/sitemap.xml"];
31
35
  function defineLocaleService(service) {
32
36
  var _a, _b, _c, _d;
33
37
  if (origins[service.id] !== service.origin) throw new Error("Service origin must match the exact owned origin");
@@ -35,7 +39,7 @@ function defineLocaleService(service) {
35
39
  if (!paths.length || new Set(paths).size !== paths.length) throw new Error("Route inventory is empty or duplicated");
36
40
  for (const path of paths) {
37
41
  if (!/^\/(?:[A-Za-z0-9._~-]+|\[[A-Za-z][A-Za-z0-9_]*\])(?:\/(?:[A-Za-z0-9._~-]+|\[[A-Za-z][A-Za-z0-9_]*\]))*$/.test(path) && path !== "/") throw new Error(`Invalid registered route: ${path}`);
38
- if (/^\/(?:en|ko|ja)(?:\/|$)/.test(path) || isProtocolPath(path, service) && !((_a = service.uiProtocolExceptions) == null ? void 0 : _a.includes(path)) || path.split("/").some((segment) => segment === "." || segment === "..")) throw new Error(`Locale/protocol path cannot be a UI route: ${path}`);
42
+ if (localePrefix.test(path) || isProtocolPath(path, service) && !((_a = service.uiProtocolExceptions) == null ? void 0 : _a.includes(path)) || path.split("/").some((segment) => segment === "." || segment === "..")) throw new Error(`Locale/protocol path cannot be a UI route: ${path}`);
39
43
  }
40
44
  for (const exception of (_b = service.uiProtocolExceptions) != null ? _b : []) if (exception.includes("[") || !paths.includes(exception)) throw new Error("Protocol UI exceptions must be exact registered routes");
41
45
  return Object.freeze(__spreadProps(__spreadValues({}, service), { routes: Object.freeze(service.routes.map((route) => typeof route === "string" ? route : Object.freeze(__spreadValues({}, route)))), protocolPrefixes: Object.freeze([...(_c = service.protocolPrefixes) != null ? _c : []]), uiProtocolExceptions: Object.freeze([...(_d = service.uiProtocolExceptions) != null ? _d : []]) }));
@@ -71,7 +75,7 @@ function invalid(reason, status = 404, locale) {
71
75
  return __spreadValues({ kind: "invalid", status, reason }, locale ? { locale } : {});
72
76
  }
73
77
  function splitInput(input, origin) {
74
- if (!input || /[\u0000-\u0020\u007f\\]/.test(input)) return;
78
+ if (!input || /[\u0000-\u0020\u007f]/.test(input)) return;
75
79
  let path = input;
76
80
  if (!path.startsWith("/")) {
77
81
  if (!path.startsWith(origin + "/") && path !== origin) return;
@@ -84,12 +88,14 @@ function splitInput(input, origin) {
84
88
  const queryAt = withoutHash.indexOf("?");
85
89
  return { pathname: queryAt < 0 ? withoutHash : withoutHash.slice(0, queryAt), search: queryAt < 0 ? "" : withoutHash.slice(queryAt), hash };
86
90
  }
87
- function safePath(path) {
91
+ function safePath(path, service) {
92
+ var _a;
88
93
  if (!path.startsWith("/") || path.includes("//")) return false;
94
+ const reserved = /* @__PURE__ */ new Set([...naeilLocales, ...[...protocolPrefixes, ...(_a = service.protocolPrefixes) != null ? _a : []].flatMap((prefix) => prefix.split("/").filter(Boolean))]);
89
95
  return path.split("/").every((segment) => {
90
96
  try {
91
97
  const decoded = decodeURIComponent(segment);
92
- return !/[\u0000-\u001f\u007f/\\%?#]/.test(decoded) && decoded !== "." && decoded !== ".." && (!/^(?:en|ko|ja|api|auth|_next|invite|return|webhook)$/.test(decoded) || decoded === segment);
98
+ return !/[\u0000-\u001f\u007f/\\%?#]/.test(decoded) && decoded !== "." && decoded !== ".." && (!reserved.has(decoded) || decoded === segment);
93
99
  } catch (e) {
94
100
  return false;
95
101
  }
@@ -112,7 +118,7 @@ function resolveLocaleUrl(inputService, input) {
112
118
  var _a, _b;
113
119
  const service = getLocaleService(inputService);
114
120
  const parts = splitInput(input, service.origin);
115
- if (!parts || !safePath(parts.pathname)) return invalid("Malformed or encoded path");
121
+ if (!parts || !safePath(parts.pathname, service)) return invalid("Malformed or encoded path");
116
122
  const rawPathname = parts.pathname;
117
123
  const pathname = rawPathname !== "/" && rawPathname.endsWith("/") ? rawPathname.slice(0, -1) : rawPathname;
118
124
  if (isProtocolPath(pathname, service) && !((_a = service.uiProtocolExceptions) == null ? void 0 : _a.includes(pathname))) return { kind: "protocol", pathname };
@@ -120,7 +126,7 @@ function resolveLocaleUrl(inputService, input) {
120
126
  const explicit = isNaeilLocale(segments[1]);
121
127
  const pathLocale = explicit ? segments[1] : void 0;
122
128
  const logicalPath = explicit ? "/" + segments.slice(2).join("/") : pathname;
123
- if (/^\/(?:en|ko|ja)(?:\/|$)/.test(logicalPath) || isProtocolPath(logicalPath, service) && !((_b = service.uiProtocolExceptions) == null ? void 0 : _b.includes(logicalPath))) return invalid("Nested locale or localized protocol path", 404, pathLocale);
129
+ if (localePrefix.test(logicalPath) || isProtocolPath(logicalPath, service) && !((_b = service.uiProtocolExceptions) == null ? void 0 : _b.includes(logicalPath))) return invalid("Nested locale or localized protocol path", 404, pathLocale);
124
130
  const route = matchRoute(service, logicalPath);
125
131
  if (!route) return invalid("Unregistered UI route", 404, pathLocale);
126
132
  const legacy = service.id !== "main";
@@ -136,7 +142,7 @@ function localePath(inputService, unlocalizedRoute, locale) {
136
142
  assertLocale(locale);
137
143
  const service = getLocaleService(inputService);
138
144
  const parts = splitInput(unlocalizedRoute, service.origin);
139
- if (!parts || /^\/(?:en|ko|ja)(?:\/|$)/.test(parts.pathname)) throw new Error("localePath requires an unlocalized route");
145
+ if (!parts || localePrefix.test(parts.pathname)) throw new Error("localePath requires an unlocalized route");
140
146
  const result = resolveLocaleUrl(service, parts.pathname);
141
147
  if (result.kind !== "ui") throw new Error("Unregistered UI destination");
142
148
  return prefixPath(result.logicalPath, locale) + (service.id === "main" ? parts.search : stripLegacyLang(parts.search)) + parts.hash;
@@ -236,6 +242,7 @@ function NaeilLocaleController({ service, children, preferenceWritesEnabled = fa
236
242
  const [intent, setIntent] = useState();
237
243
  const [error, setError] = useState();
238
244
  const lastRequest = useRef(void 0);
245
+ const timedOutIntent = useRef(void 0);
239
246
  const focus = useRef(void 0);
240
247
  const timer = useRef(void 0);
241
248
  const guards = useRef(/* @__PURE__ */ new Set());
@@ -245,6 +252,7 @@ function NaeilLocaleController({ service, children, preferenceWritesEnabled = fa
245
252
  }, []);
246
253
  const cancel = useCallback(() => {
247
254
  gate.cancel();
255
+ timedOutIntent.current = void 0;
248
256
  clearTimer();
249
257
  setIntent(void 0);
250
258
  setError(void 0);
@@ -253,6 +261,8 @@ function NaeilLocaleController({ service, children, preferenceWritesEnabled = fa
253
261
  const fail = useCallback((token, reason) => {
254
262
  var _a;
255
263
  if (((_a = gate.current()) == null ? void 0 : _a.token) !== token) return;
264
+ timedOutIntent.current = reason === "timeout" ? gate.current() : void 0;
265
+ focus.current = void 0;
256
266
  gate.cancel(token);
257
267
  clearTimer();
258
268
  setIntent(void 0);
@@ -262,12 +272,17 @@ function NaeilLocaleController({ service, children, preferenceWritesEnabled = fa
262
272
  var _a;
263
273
  const locale = assertLocale(language);
264
274
  if (gate.current()) return false;
275
+ lastRequest.current = { locale, options: options ? __spreadValues({}, options) : void 0 };
276
+ timedOutIntent.current = void 0;
265
277
  const current = resolveLocaleUrl(service, browserHref());
266
278
  if (current.kind !== "ui") {
267
279
  setError("unsupported-route");
268
280
  return false;
269
281
  }
270
- if (current.locale === locale && !(options == null ? void 0 : options.route) && !(options == null ? void 0 : options.href)) return false;
282
+ if (current.locale === locale && !(options == null ? void 0 : options.route) && !(options == null ? void 0 : options.href)) {
283
+ setError(void 0);
284
+ return false;
285
+ }
271
286
  const suffix = current.canonicalPath.slice(localePath(service, current.logicalPath, current.locale).length);
272
287
  let href;
273
288
  try {
@@ -283,7 +298,6 @@ function NaeilLocaleController({ service, children, preferenceWritesEnabled = fa
283
298
  }
284
299
  const request = gate.begin(href, locale);
285
300
  if (!request) return false;
286
- lastRequest.current = { locale, options };
287
301
  focus.current = captureFocus();
288
302
  setIntent(request);
289
303
  setError(void 0);
@@ -296,11 +310,26 @@ function NaeilLocaleController({ service, children, preferenceWritesEnabled = fa
296
310
  return false;
297
311
  }
298
312
  }, [service, gate, router, fail, transitionTimeoutMs]);
299
- const retry = useCallback(() => lastRequest.current ? changeLocale(lastRequest.current.locale, lastRequest.current.options) : false, [changeLocale]);
313
+ const retry = useCallback(() => {
314
+ const request = lastRequest.current;
315
+ if (!request || gate.current()) return false;
316
+ const current = resolveLocaleUrl(service, browserHref());
317
+ if (current.kind === "ui" && current.locale === request.locale) {
318
+ setError(void 0);
319
+ return false;
320
+ }
321
+ return changeLocale(request.locale, request.options);
322
+ }, [service, gate, changeLocale]);
300
323
  const arrive = useCallback((token, locale, committedPathname) => {
301
- if (token === void 0 || committedPathname !== window.location.pathname) return;
302
- const committed = gate.arrive(token, browserHref(), locale);
324
+ if (committedPathname !== window.location.pathname) return;
325
+ let committed = token === void 0 ? void 0 : gate.arrive(token, browserHref(), locale);
326
+ const late = timedOutIntent.current;
327
+ if (!committed && !gate.current() && late && (token === void 0 || token === late.token)) {
328
+ const current = resolveLocaleUrl(service, browserHref());
329
+ if (late.locale === locale && current.kind === "ui" && !current.alias && current.locale === locale && current.canonicalPath === late.href) committed = late;
330
+ }
303
331
  if (!committed) return;
332
+ timedOutIntent.current = void 0;
304
333
  clearTimer();
305
334
  setIntent(void 0);
306
335
  setError(void 0);
@@ -310,7 +339,7 @@ function NaeilLocaleController({ service, children, preferenceWritesEnabled = fa
310
339
  const savedFocus = focus.current;
311
340
  focus.current = void 0;
312
341
  requestAnimationFrame(() => restoreFocus(savedFocus));
313
- }, [gate, clearTimer, preferenceWritesEnabled]);
342
+ }, [service, gate, clearTimer, preferenceWritesEnabled]);
314
343
  const registerDepartureGuard = useCallback((guard) => {
315
344
  guards.current.add(guard);
316
345
  return () => {
@@ -341,7 +370,8 @@ function NaeilLocaleController({ service, children, preferenceWritesEnabled = fa
341
370
  };
342
371
  }, [cancel, gate, clearTimer]);
343
372
  useEffect(() => {
344
- const pending = gate.current();
373
+ var _a;
374
+ const pending = (_a = gate.current()) != null ? _a : timedOutIntent.current;
345
375
  if (!pending) return;
346
376
  const target = pending.href.split(/[?#]/, 1)[0];
347
377
  const current = resolveLocaleUrl(service, pathname != null ? pathname : "/");
@@ -11,7 +11,7 @@ import {
11
11
  naeilServices,
12
12
  resolveLocaleUrl,
13
13
  sameLogicalRoute
14
- } from "./chunk-U65OQHZY.js";
14
+ } from "./chunk-WT6QXR7O.js";
15
15
  import "./chunk-N3ETBM74.js";
16
16
 
17
17
  // src/naeil-locale/preference.ts
@@ -3,8 +3,8 @@ import {
3
3
  loadSharedMessages,
4
4
  mergeLocaleMessages,
5
5
  recoverMissingMessages
6
- } from "./chunk-4U64K4J3.js";
7
- import "./chunk-U65OQHZY.js";
6
+ } from "./chunk-24IL53QO.js";
7
+ import "./chunk-WT6QXR7O.js";
8
8
  import "./chunk-N3ETBM74.js";
9
9
  export {
10
10
  createLocaleMessageLoader,
@@ -2,7 +2,7 @@ import {
2
2
  createLocaleMessageLoader,
3
3
  loadSharedMessages,
4
4
  recoverMissingMessages
5
- } from "./chunk-4U64K4J3.js";
5
+ } from "./chunk-24IL53QO.js";
6
6
  import {
7
7
  assertLocale,
8
8
  getLocaleService,
@@ -11,7 +11,7 @@ import {
11
11
  naeilLocales,
12
12
  naeilRouting,
13
13
  resolveLocaleUrl
14
- } from "./chunk-U65OQHZY.js";
14
+ } from "./chunk-WT6QXR7O.js";
15
15
  import "./chunk-N3ETBM74.js";
16
16
 
17
17
  // src/naeil-locale/server.ts
@@ -62,10 +62,10 @@ function stripMarkers(headers) {
62
62
  return sanitized;
63
63
  }
64
64
  function sanitizeProtocolPassThrough(response, requestHeaders) {
65
- var _a;
66
65
  if (response.headers.get("x-middleware-next") !== "1" && !response.headers.has("x-middleware-rewrite")) return response;
67
- const forwarded = new Headers(requestHeaders);
68
- for (const key of ((_a = response.headers.get("x-middleware-override-headers")) != null ? _a : "").split(",")) {
66
+ const overrides = response.headers.get("x-middleware-override-headers");
67
+ const forwarded = new Headers(overrides === null ? requestHeaders : void 0);
68
+ for (const key of (overrides != null ? overrides : "").split(",").map((name) => name.trim())) {
69
69
  if (!key) continue;
70
70
  const value = response.headers.get(`x-middleware-request-${key}`);
71
71
  if (value !== null) forwarded.set(key, value);
@@ -79,7 +79,7 @@ function sanitizeProtocolPassThrough(response, requestHeaders) {
79
79
  names.push(key);
80
80
  response.headers.set(`x-middleware-request-${key}`, value);
81
81
  }
82
- response.headers.set("x-middleware-override-headers", names.join(","));
82
+ response.headers.set("x-middleware-override-headers", [...names, ...privateHeaders].join(","));
83
83
  return response;
84
84
  }
85
85
  function markPrivate(response) {
@@ -25,6 +25,8 @@ interface NavigationGroup {
25
25
  }
26
26
  type NavigationItem = NavigationLink | NavigationGroup;
27
27
  interface NavigationProps extends Omit<React.ComponentPropsWithoutRef<"header">, "children" | "onSelect"> {
28
+ /** Omit empty landmarks and dismiss toolbar-only sheets on desktop crossing. */
29
+ omitEmptyNavigation?: boolean;
28
30
  brand: React.ReactNode;
29
31
  items: NavigationItem[];
30
32
  /** Translated landmark/dialog/close labels are supplied by the application. */
@@ -45,6 +47,6 @@ interface NavigationProps extends Omit<React.ComponentPropsWithoutRef<"header">,
45
47
  /** Preserve modified clicks; applications may preventDefault for client routing/guards. */
46
48
  onNavigate?: (event: React.MouseEvent<HTMLAnchorElement>, item: NavigationLink) => void;
47
49
  }
48
- declare function Navigation({ brand, items, labels, Link, currentHref, toolbar, mobileToolbar, mobileFooter, mobileOpen: controlledOpen, onMobileOpenChange, containerClassName, onNavigate, className, ...props }: NavigationProps): react_jsx_runtime.JSX.Element;
50
+ declare function Navigation({ brand, items, labels, Link, currentHref, toolbar, mobileToolbar, mobileFooter, omitEmptyNavigation, mobileOpen: controlledOpen, onMobileOpenChange, containerClassName, onNavigate, className, ...props }: NavigationProps): react_jsx_runtime.JSX.Element;
49
51
 
50
52
  export { type NavigationLink as N, type NavigationProps as a, type NavigationLinkComponent as b, type NavigationItem as c, type NavigationLinkProps as d, Navigation as e, type NavigationGroup as f };
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
- import { N as NavigationLink, b as NavigationLinkComponent, a as NavigationProps } from './navigation-D8N1tMTu.js';
3
+ import { N as NavigationLink, b as NavigationLinkComponent, a as NavigationProps } from './navigation-CXZapKac.js';
4
4
 
5
5
  interface SiteFooterProps extends React.ComponentPropsWithoutRef<"footer"> {
6
6
  brand?: React.ReactNode;
@@ -1,18 +1,20 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { ReactNode } from 'react';
2
+ import { ReactNode, JSX } from 'react';
3
3
  import { NavigationProps, NavigationItem, NavigationLinkProps, NavigationLink, NavigationLinkComponent } from './ui/navigation.js';
4
4
  import { NaeilHeaderLocale } from './naeil-header-data.js';
5
5
  export { NaeilHeaderAccount, NaeilHeaderAccountControl, NaeilHeaderAccountControlProps } from './naeil-header-account-core.js';
6
6
 
7
7
  interface NaeilHeaderProps extends Omit<NavigationProps, "brand" | "items" | "toolbar" | "mobileToolbar" | "labels"> {
8
+ service?: undefined;
8
9
  locale: NaeilHeaderLocale;
9
10
  /** Resolve canonical unlocalized paths. Defaults to the official localized site. */
10
11
  resolveHref?: (path: string) => string;
11
12
  brandHref?: string;
13
+ /** @deprecated Owned services must use service; retained for compatibility. */
12
14
  brandSlot?: ReactNode;
13
- /** Ignored when brandSlot replaces the complete brand content. */
15
+ /** @deprecated Owned services must use service; retained for compatibility. Ignored with brandSlot. */
14
16
  brandSuffix?: string;
15
- /** After the brand link, outside it; rendered once at every width. */
17
+ /** @deprecated Owned services must use service with no indicator; retained for compatibility. */
16
18
  statusSlot?: ReactNode;
17
19
  /** Product menu data, after catalog/blog and before flat subNav links. */
18
20
  appNavigation?: NavigationItem[];
@@ -33,17 +35,32 @@ interface NaeilHeaderProps extends Omit<NavigationProps, "brand" | "items" | "to
33
35
  };
34
36
  showProjects?: boolean;
35
37
  showBlog?: boolean;
38
+ /** @deprecated Owned services must use service and appNavigation; retained for compatibility. */
36
39
  subNav?: Array<{
37
40
  label: string;
38
41
  href: string;
39
42
  }>;
40
43
  currentProjectKey?: string;
41
44
  currentProjectHref?: string;
42
- /** Compatibility escape hatch replacing the default theme/locale/auth toolbar. */
45
+ /** @deprecated Owned services must use service and authSlot; retained for compatibility. */
43
46
  toolbarSlot?: ReactNode;
44
- /** Compatibility escape hatch for the legacy native external brand link. */
47
+ /** @deprecated Owned services must use service and Link; retained for compatibility. */
45
48
  brandLinkComponent?: NavigationLinkComponent;
46
49
  }
50
+ /** Canonical owned-service identifiers; their spelling is the visible brand suffix. */
51
+ declare const naeilServices: readonly ["abstorm", "home", "estimate", "jev"];
52
+ type NaeilService = typeof naeilServices[number];
53
+ declare const serviceCompatibilityProps: readonly ["brandSuffix", "statusSlot", "brandSlot", "toolbarSlot", "subNav", "brandLinkComponent", "projects", "projectsExternal", "showProjects", "showBlog", "translations", "className", "containerClassName", "style", "brandLinkProps", "mobileFooter", "omitEmptyNavigation"];
54
+ type ServiceCompatibilityProp = typeof serviceCompatibilityProps[number];
55
+ /** Strict owned-service props; the legacy interface remains independently extensible. */
56
+ type NaeilServiceHeaderProps = Omit<NaeilHeaderProps, ServiceCompatibilityProp | "service"> & {
57
+ service: NaeilService;
58
+ } & {
59
+ [Prop in ServiceCompatibilityProp]?: never;
60
+ };
61
+ /** Opt into the owned-service contract without changing the legacy composition. */
62
+ declare function NaeilHeader(props: NaeilServiceHeaderProps): JSX.Element;
63
+ declare function NaeilHeader(props: NaeilHeaderProps): JSX.Element;
47
64
  /** The actual header theme control; reads only the consumer's theme provider. */
48
65
  declare function NaeilHeaderThemeControl({ locale }: {
49
66
  locale: NaeilHeaderLocale;
@@ -53,7 +70,5 @@ declare function NaeilHeaderLocaleControl({ locale, onLocaleChange }: {
53
70
  locale: NaeilHeaderLocale;
54
71
  onLocaleChange: (locale: NaeilHeaderLocale) => void;
55
72
  }): react_jsx_runtime.JSX.Element;
56
- /** One complete, opt-in branded composition shared by naeil.dev and applications. */
57
- declare function NaeilHeader({ locale, resolveHref, brandHref, brandSlot, brandSuffix, statusSlot, appNavigation, brandLinkProps, brandLinkComponent, onLocaleChange, authSlot, toolbarSlot, projects, projectsExternal, translations, showProjects, showBlog, subNav, currentProjectKey, currentProjectHref, labels, Link, currentHref, onNavigate, className, containerClassName, ...props }: NaeilHeaderProps): react_jsx_runtime.JSX.Element;
58
73
 
59
- export { NaeilHeader, NaeilHeaderLocale, NaeilHeaderLocaleControl, type NaeilHeaderProps, NaeilHeaderThemeControl };
74
+ export { NaeilHeader, NaeilHeaderLocale, NaeilHeaderLocaleControl, type NaeilHeaderProps, NaeilHeaderThemeControl, type NaeilService, type NaeilServiceHeaderProps, naeilServices };
@@ -44,6 +44,58 @@ import { uiRelease } from "../lib/ui-release.js";
44
44
  import { headerLocales, headerProjects } from "./naeil-header-data.js";
45
45
  import { useNaeilMessages } from "./naeil-messages-context.js";
46
46
  import { NaeilHeaderAccountControl } from "./naeil-header-account-core.js";
47
+ const naeilServices = Object.freeze(["abstorm", "home", "estimate", "jev"]);
48
+ const serviceCompatibilityProps = [
49
+ "brandSuffix",
50
+ "statusSlot",
51
+ "brandSlot",
52
+ "toolbarSlot",
53
+ "subNav",
54
+ "brandLinkComponent",
55
+ "projects",
56
+ "projectsExternal",
57
+ "showProjects",
58
+ "showBlog",
59
+ "translations",
60
+ "className",
61
+ "containerClassName",
62
+ "style",
63
+ "brandLinkProps",
64
+ "mobileFooter",
65
+ "omitEmptyNavigation"
66
+ ];
67
+ const warnedServiceProps = /* @__PURE__ */ new Set();
68
+ function isDevelopment() {
69
+ try {
70
+ return process.env.NODE_ENV === "development";
71
+ } catch (e) {
72
+ return false;
73
+ }
74
+ }
75
+ function warnServiceProp(name, message) {
76
+ if (!isDevelopment() || warnedServiceProps.has(name)) return;
77
+ warnedServiceProps.add(name);
78
+ console.warn(message);
79
+ }
80
+ function isNaeilService(value) {
81
+ return naeilServices.includes(value);
82
+ }
83
+ function NaeilHeader(input) {
84
+ if (input.service === void 0) return /* @__PURE__ */ jsx(HeaderComposition, __spreadValues({}, input));
85
+ if (!isNaeilService(input.service)) {
86
+ warnServiceProp("service", "NaeilHeader: invalid service; rendering the legacy profile.");
87
+ const legacy = __spreadProps(__spreadValues({}, input), { service: void 0 });
88
+ delete legacy.service;
89
+ return /* @__PURE__ */ jsx(HeaderComposition, __spreadValues({}, legacy));
90
+ }
91
+ const props = __spreadValues({}, input);
92
+ for (const name of Object.keys(props)) {
93
+ if (!serviceCompatibilityProps.includes(name) && name !== "data-slot" && name !== "data-global-header" && !name.startsWith("data-naeil-")) continue;
94
+ delete props[name];
95
+ warnServiceProp(name, `NaeilHeader: service profile ignores ${name}; use the shared service composition.`);
96
+ }
97
+ return /* @__PURE__ */ jsx(HeaderComposition, __spreadProps(__spreadValues({}, props), { service: input.service }));
98
+ }
47
99
  const subscribe = () => () => {
48
100
  };
49
101
  function NaeilHeaderThemeControl({ locale }) {
@@ -80,8 +132,9 @@ function NaeilHeaderLocaleControl({ locale, onLocaleChange }) {
80
132
  ] });
81
133
  }
82
134
  const NativeLink = (props) => /* @__PURE__ */ jsx("a", __spreadValues({}, props));
83
- function NaeilHeader(_a) {
135
+ function HeaderComposition(_a) {
84
136
  var _b = _a, {
137
+ service,
85
138
  locale,
86
139
  resolveHref = (path) => `https://naeil.dev/${locale}${path === "/" ? "" : path}`,
87
140
  brandHref,
@@ -109,6 +162,7 @@ function NaeilHeader(_a) {
109
162
  className,
110
163
  containerClassName
111
164
  } = _b, props = __objRest(_b, [
165
+ "service",
112
166
  "locale",
113
167
  "resolveHref",
114
168
  "brandHref",
@@ -137,6 +191,11 @@ function NaeilHeader(_a) {
137
191
  "containerClassName"
138
192
  ]);
139
193
  var _b2, _c;
194
+ if (service) {
195
+ brandSuffix = service;
196
+ showProjects = false;
197
+ showBlog = false;
198
+ }
140
199
  const content = useNaeilMessages(locale).header;
141
200
  const projectLinks = !showProjects ? [] : projects != null ? projects : headerProjects.map((project) => {
142
201
  var _a3, _b3, _c2;
@@ -155,7 +214,7 @@ function NaeilHeader(_a) {
155
214
  badge: project.status === "coming" ? /* @__PURE__ */ jsx(Badge, { variant: "outline", children: (_c2 = translations == null ? void 0 : translations.project("status.coming")) != null ? _c2 : content.projects.status.coming }) : void 0
156
215
  };
157
216
  });
158
- const brandDestination = brandHref != null ? brandHref : resolveHref("/");
217
+ const brandDestination = brandHref != null ? brandHref : service ? "/" : resolveHref("/");
159
218
  const BrandLink = brandLinkComponent != null ? brandLinkComponent : Link;
160
219
  const _a2 = brandLinkProps != null ? brandLinkProps : {}, { onClick: onBrandClick } = _a2, brandProps = __objRest(_a2, ["onClick"]);
161
220
  const toolbar = /* @__PURE__ */ jsx(
@@ -181,11 +240,11 @@ function NaeilHeader(_a) {
181
240
  __spreadProps(__spreadValues({
182
241
  href: brandDestination,
183
242
  "data-app-brand": true,
184
- "aria-label": brandSlot == null ? ".naeil" : void 0
243
+ "aria-label": service ? `.naeil ${service}` : brandSlot == null ? ".naeil" : void 0
185
244
  }, brandProps), {
186
245
  onClick: (event) => {
187
246
  onBrandClick == null ? void 0 : onBrandClick(event);
188
- onNavigate == null ? void 0 : onNavigate(event, { id: "brand", href: brandDestination, label: ".naeil" });
247
+ onNavigate == null ? void 0 : onNavigate(event, { id: "brand", href: brandDestination, label: service ? `.naeil ${service}` : ".naeil" });
189
248
  },
190
249
  children: brandSlot != null ? brandSlot : brandSuffix ? /* @__PURE__ */ jsxs(Fragment, { children: [
191
250
  logo,
@@ -195,7 +254,7 @@ function NaeilHeader(_a) {
195
254
  );
196
255
  return /* @__PURE__ */ jsx(
197
256
  Navigation,
198
- __spreadProps(__spreadValues({}, props), {
257
+ __spreadProps(__spreadValues(__spreadValues({}, props), service ? { "data-slot": "navigation", omitEmptyNavigation: true } : {}), {
199
258
  "data-global-header": true,
200
259
  "data-naeil-header": true,
201
260
  "data-naeil-chrome": "",
@@ -227,5 +286,6 @@ export {
227
286
  NaeilHeader,
228
287
  NaeilHeaderAccountControl,
229
288
  NaeilHeaderLocaleControl,
230
- NaeilHeaderThemeControl
289
+ NaeilHeaderThemeControl,
290
+ naeilServices
231
291
  };
@@ -1,12 +1,14 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { NaeilHeaderProps } from './naeil-header-core.js';
2
+ import { JSX } from 'react';
3
+ import { NaeilServiceHeaderProps, NaeilHeaderProps } from './naeil-header-core.js';
4
+ export { NaeilService, naeilServices } from './naeil-header-core.js';
3
5
  import { NaeilHeaderLocale } from './naeil-header-data.js';
4
6
  export { NaeilHeaderAccountControl } from './naeil-header-account.js';
5
7
  export { NaeilHeaderAccount, NaeilHeaderAccountControlProps } from './naeil-header-account-core.js';
6
- import 'react';
7
8
  import './ui/navigation.js';
8
9
 
9
- declare function NaeilHeader(props: NaeilHeaderProps): react_jsx_runtime.JSX.Element;
10
+ declare function NaeilHeader(props: NaeilServiceHeaderProps): JSX.Element;
11
+ declare function NaeilHeader(props: NaeilHeaderProps): JSX.Element;
10
12
  declare function NaeilHeaderThemeControl(props: {
11
13
  locale: NaeilHeaderLocale;
12
14
  }): react_jsx_runtime.JSX.Element;
@@ -15,4 +17,4 @@ declare function NaeilHeaderLocaleControl(props: {
15
17
  onLocaleChange: (locale: NaeilHeaderLocale) => void;
16
18
  }): react_jsx_runtime.JSX.Element;
17
19
 
18
- export { NaeilHeader, NaeilHeaderLocale, NaeilHeaderLocaleControl, NaeilHeaderProps, NaeilHeaderThemeControl };
20
+ export { NaeilHeader, NaeilHeaderLocale, NaeilHeaderLocaleControl, NaeilHeaderProps, NaeilHeaderThemeControl, NaeilServiceHeaderProps };
@@ -18,9 +18,10 @@ var __spreadValues = (a, b) => {
18
18
  import { jsx } from "react/jsx-runtime";
19
19
  import { NaeilHeader as Header, NaeilHeaderThemeControl as ThemeControl, NaeilHeaderLocaleControl as LocaleControl } from "./naeil-header-core.js";
20
20
  import { NaeilStandaloneMessages } from "./naeil-standalone-messages.js";
21
+ import { naeilServices } from "./naeil-header-core.js";
21
22
  import { NaeilHeaderAccountControl } from "./naeil-header-account.js";
22
23
  function NaeilHeader(props) {
23
- return /* @__PURE__ */ jsx(NaeilStandaloneMessages, { locale: props.locale, children: /* @__PURE__ */ jsx(Header, __spreadValues({}, props)) });
24
+ return /* @__PURE__ */ jsx(NaeilStandaloneMessages, { locale: props.locale, children: props.service === void 0 ? /* @__PURE__ */ jsx(Header, __spreadValues({}, props)) : /* @__PURE__ */ jsx(Header, __spreadValues({}, props)) });
24
25
  }
25
26
  function NaeilHeaderThemeControl(props) {
26
27
  return /* @__PURE__ */ jsx(NaeilStandaloneMessages, { locale: props.locale, children: /* @__PURE__ */ jsx(ThemeControl, __spreadValues({}, props)) });
@@ -32,5 +33,6 @@ export {
32
33
  NaeilHeader,
33
34
  NaeilHeaderAccountControl,
34
35
  NaeilHeaderLocaleControl,
35
- NaeilHeaderThemeControl
36
+ NaeilHeaderThemeControl,
37
+ naeilServices
36
38
  };
@@ -4,7 +4,7 @@ import * as react from 'react';
4
4
  type HeadingProps = react.HTMLAttributes<HTMLHeadingElement> & {
5
5
  children: react.ReactNode;
6
6
  };
7
- declare const pageTitleClass = "text-foreground text-3xl font-semibold leading-tight";
7
+ declare const pageTitleClass = "text-foreground text-3xl font-semibold";
8
8
  declare const sectionTitleClass = "text-foreground text-xl font-semibold";
9
9
  declare function PageTitle({ className, children, ...props }: HeadingProps): react_jsx_runtime.JSX.Element;
10
10
  declare function SectionTitle({ className, children, ...props }: HeadingProps): react_jsx_runtime.JSX.Element;
@@ -31,7 +31,7 @@ var __objRest = (source, exclude) => {
31
31
  };
32
32
  import { jsx } from "react/jsx-runtime";
33
33
  import { cn } from "../lib/utils.js";
34
- const pageTitleClass = "text-foreground text-3xl font-semibold leading-tight";
34
+ const pageTitleClass = "text-foreground text-3xl font-semibold";
35
35
  const sectionTitleClass = "text-foreground text-xl font-semibold";
36
36
  function PageTitle(_a) {
37
37
  var _b = _a, { className, children } = _b, props = __objRest(_b, ["className", "children"]);
@@ -31,16 +31,16 @@ import { cva } from "class-variance-authority";
31
31
  import { Slot } from "radix-ui";
32
32
  import { cn } from "../../lib/utils.js";
33
33
  const buttonVariants = cva(
34
- "inline-flex items-center justify-center gap-2 whitespace-nowrap ui-control ui-focus rounded-md text-base font-medium transition-colors duration-(--ui-motion-fast) cursor-pointer disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 ui-focus aria-invalid:border-error",
34
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap ui-control ui-focus rounded-md text-base font-medium transition-colors duration-(--ui-motion-fast) cursor-pointer disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 ui-focus aria-invalid:border-error aria-pressed:inset-ring-2",
35
35
  {
36
36
  variants: {
37
37
  variant: {
38
- default: "bg-primary text-primary-foreground hover:bg-primary-hover",
39
- destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90 ",
40
- outline: "border border-border-strong bg-background hover:bg-surface-subtle",
41
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
42
- ghost: "hover:bg-surface-subtle",
43
- link: "text-primary underline-offset-4 hover:underline"
38
+ default: "bg-primary text-primary-foreground hover:bg-primary-hover aria-pressed:bg-primary-hover aria-pressed:inset-ring-4 aria-pressed:inset-ring-primary-foreground aria-pressed:inset-shadow-[0_0_0_2px_var(--primary-hover)]",
39
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90 aria-pressed:inset-ring-4 aria-pressed:inset-ring-destructive-foreground aria-pressed:inset-shadow-[0_0_0_2px_var(--destructive)]",
40
+ outline: "border border-border-strong bg-background hover:bg-surface-subtle aria-pressed:bg-surface-selected aria-pressed:text-foreground aria-pressed:inset-ring-foreground",
41
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-pressed:bg-surface-selected aria-pressed:text-foreground aria-pressed:inset-ring-foreground",
42
+ ghost: "hover:bg-surface-subtle aria-pressed:bg-surface-selected aria-pressed:text-foreground aria-pressed:inset-ring-foreground",
43
+ link: "text-primary underline-offset-4 hover:underline aria-pressed:bg-surface-selected aria-pressed:text-foreground aria-pressed:inset-ring-foreground"
44
44
  },
45
45
  size: {
46
46
  default: "px-4 has-[>svg]:px-3",
@@ -73,7 +73,7 @@ function CardTitle(_g) {
73
73
  "div",
74
74
  __spreadValues({
75
75
  "data-slot": "card-title",
76
- className: cn("leading-none font-semibold", className)
76
+ className: cn("text-base font-semibold", className)
77
77
  }, props)
78
78
  );
79
79
  }
@@ -84,7 +84,7 @@ function DialogContent(_g) {
84
84
  __spreadProps(__spreadValues({
85
85
  "data-slot": "dialog-content",
86
86
  className: cn(
87
- "bg-popover ui-popup fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-xl border border-border-strong p-6 sm:max-w-lg",
87
+ "bg-popover ui-popup fixed top-[50%] left-[50%] z-50 grid w-full max-h-[min(calc(100%-2rem),calc(100dvh-2rem))] overflow-y-auto max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-xl border border-border-strong p-6 sm:max-w-lg",
88
88
  className
89
89
  )
90
90
  }, props), {
@@ -154,7 +154,7 @@ function DialogTitle(_m) {
154
154
  DialogPrimitive.Title,
155
155
  __spreadValues({
156
156
  "data-slot": "dialog-title",
157
- className: cn("text-xl leading-normal font-semibold", className)
157
+ className: cn("text-xl font-semibold", className)
158
158
  }, props)
159
159
  );
160
160
  }
@@ -25,6 +25,8 @@ interface NavigationGroup {
25
25
  }
26
26
  type NavigationItem = NavigationLink | NavigationGroup;
27
27
  interface NavigationProps extends Omit<react.ComponentPropsWithoutRef<"header">, "children" | "onSelect"> {
28
+ /** Omit empty landmarks and dismiss toolbar-only sheets on desktop crossing. */
29
+ omitEmptyNavigation?: boolean;
28
30
  brand: react.ReactNode;
29
31
  items: NavigationItem[];
30
32
  /** Translated landmark/dialog/close labels are supplied by the application. */
@@ -49,6 +51,6 @@ declare const NavigationSheetAction: react.Context<((action: () => void) => {
49
51
  menuClosed: () => void;
50
52
  cancel: () => void;
51
53
  }) | undefined>;
52
- declare function Navigation({ brand, items, labels, Link, currentHref, toolbar, mobileToolbar, mobileFooter, mobileOpen: controlledOpen, onMobileOpenChange, containerClassName, onNavigate, className, ...props }: NavigationProps): react_jsx_runtime.JSX.Element;
54
+ declare function Navigation({ brand, items, labels, Link, currentHref, toolbar, mobileToolbar, mobileFooter, omitEmptyNavigation, mobileOpen: controlledOpen, onMobileOpenChange, containerClassName, onNavigate, className, ...props }: NavigationProps): react_jsx_runtime.JSX.Element;
53
55
 
54
56
  export { Navigation, type NavigationGroup, type NavigationItem, type NavigationLink, type NavigationLinkComponent, type NavigationLinkProps, type NavigationProps, NavigationSheetAction };