@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.
Files changed (77) hide show
  1. package/CHANGELOG.md +38 -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-ESTQITQO.js} +68 -8
  6. package/dist/{chunk-DBE7R4MR.js → chunk-L25A4OKY.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 +36 -12
  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
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { ReactNode } from 'react';
3
- import { b as NavigationLinkComponent, a as NavigationProps, c as NavigationItem, d as NavigationLinkProps, N as NavigationLink } from '../navigation-D8N1tMTu.js';
2
+ import { ReactNode, JSX } from 'react';
3
+ import { b as NavigationLinkComponent, a as NavigationProps, c as NavigationItem, d as NavigationLinkProps, N as NavigationLink } from '../navigation-CXZapKac.js';
4
4
  import { N as NaeilHeaderLocale } from '../naeil-header-data-BvISJ4RV.js';
5
5
 
6
6
  type NaeilHeaderAccount = {
@@ -22,14 +22,16 @@ interface NaeilHeaderAccountControlProps {
22
22
  }
23
23
 
24
24
  interface NaeilHeaderProps extends Omit<NavigationProps, "brand" | "items" | "toolbar" | "mobileToolbar" | "labels"> {
25
+ service?: undefined;
25
26
  locale: NaeilHeaderLocale;
26
27
  /** Resolve canonical unlocalized paths. Defaults to the official localized site. */
27
28
  resolveHref?: (path: string) => string;
28
29
  brandHref?: string;
30
+ /** @deprecated Owned services must use service; retained for compatibility. */
29
31
  brandSlot?: ReactNode;
30
- /** Ignored when brandSlot replaces the complete brand content. */
32
+ /** @deprecated Owned services must use service; retained for compatibility. Ignored with brandSlot. */
31
33
  brandSuffix?: string;
32
- /** After the brand link, outside it; rendered once at every width. */
34
+ /** @deprecated Owned services must use service with no indicator; retained for compatibility. */
33
35
  statusSlot?: ReactNode;
34
36
  /** Product menu data, after catalog/blog and before flat subNav links. */
35
37
  appNavigation?: NavigationItem[];
@@ -50,21 +52,34 @@ interface NaeilHeaderProps extends Omit<NavigationProps, "brand" | "items" | "to
50
52
  };
51
53
  showProjects?: boolean;
52
54
  showBlog?: boolean;
55
+ /** @deprecated Owned services must use service and appNavigation; retained for compatibility. */
53
56
  subNav?: Array<{
54
57
  label: string;
55
58
  href: string;
56
59
  }>;
57
60
  currentProjectKey?: string;
58
61
  currentProjectHref?: string;
59
- /** Compatibility escape hatch replacing the default theme/locale/auth toolbar. */
62
+ /** @deprecated Owned services must use service and authSlot; retained for compatibility. */
60
63
  toolbarSlot?: ReactNode;
61
- /** Compatibility escape hatch for the legacy native external brand link. */
64
+ /** @deprecated Owned services must use service and Link; retained for compatibility. */
62
65
  brandLinkComponent?: NavigationLinkComponent;
63
66
  }
67
+ /** Canonical owned-service identifiers; their spelling is the visible brand suffix. */
68
+ declare const naeilServices: readonly ["abstorm", "home", "estimate", "jev"];
69
+ type NaeilService = typeof naeilServices[number];
70
+ declare const serviceCompatibilityProps: readonly ["brandSuffix", "statusSlot", "brandSlot", "toolbarSlot", "subNav", "brandLinkComponent", "projects", "projectsExternal", "showProjects", "showBlog", "translations", "className", "containerClassName", "style", "brandLinkProps", "mobileFooter", "omitEmptyNavigation"];
71
+ type ServiceCompatibilityProp = typeof serviceCompatibilityProps[number];
72
+ /** Strict owned-service props; the legacy interface remains independently extensible. */
73
+ type NaeilServiceHeaderProps = Omit<NaeilHeaderProps, ServiceCompatibilityProp | "service"> & {
74
+ service: NaeilService;
75
+ } & {
76
+ [Prop in ServiceCompatibilityProp]?: never;
77
+ };
64
78
 
65
79
  declare function NaeilHeaderAccountControl(props: NaeilHeaderAccountControlProps): react_jsx_runtime.JSX.Element;
66
80
 
67
- declare function NaeilHeader(props: NaeilHeaderProps): react_jsx_runtime.JSX.Element;
81
+ declare function NaeilHeader(props: NaeilServiceHeaderProps): JSX.Element;
82
+ declare function NaeilHeader(props: NaeilHeaderProps): JSX.Element;
68
83
  declare function NaeilHeaderThemeControl(props: {
69
84
  locale: NaeilHeaderLocale;
70
85
  }): react_jsx_runtime.JSX.Element;
@@ -73,4 +88,4 @@ declare function NaeilHeaderLocaleControl(props: {
73
88
  onLocaleChange: (locale: NaeilHeaderLocale) => void;
74
89
  }): react_jsx_runtime.JSX.Element;
75
90
 
76
- export { NaeilHeader, type NaeilHeaderAccount, NaeilHeaderAccountControl, type NaeilHeaderAccountControlProps, NaeilHeaderLocale, NaeilHeaderLocaleControl, type NaeilHeaderProps, NaeilHeaderThemeControl };
91
+ export { NaeilHeader, type NaeilHeaderAccount, NaeilHeaderAccountControl, type NaeilHeaderAccountControlProps, NaeilHeaderLocale, NaeilHeaderLocaleControl, type NaeilHeaderProps, NaeilHeaderThemeControl, type NaeilService, type NaeilServiceHeaderProps, naeilServices };
@@ -4,14 +4,16 @@ import {
4
4
  NaeilHeader,
5
5
  NaeilHeaderAccountControl,
6
6
  NaeilHeaderLocaleControl,
7
- NaeilHeaderThemeControl
8
- } from "../chunk-XLISHUJZ.js";
9
- import "../chunk-DBE7R4MR.js";
10
- import "../chunk-KPE4KKYM.js";
7
+ NaeilHeaderThemeControl,
8
+ naeilServices
9
+ } from "../chunk-ESTQITQO.js";
10
+ import "../chunk-L25A4OKY.js";
11
+ import "../chunk-Y5LW3ADW.js";
11
12
  import "../chunk-JBTDRVZ7.js";
12
13
  export {
13
14
  NaeilHeader,
14
15
  NaeilHeaderAccountControl,
15
16
  NaeilHeaderLocaleControl,
16
- NaeilHeaderThemeControl
17
+ NaeilHeaderThemeControl,
18
+ naeilServices
17
19
  };
@@ -4,8 +4,8 @@ import { Avatar as Avatar$1, Dialog as Dialog$1, DropdownMenu as DropdownMenu$1,
4
4
  import * as class_variance_authority_types from 'class-variance-authority/types';
5
5
  import { VariantProps } from 'class-variance-authority';
6
6
  import { ToasterProps } from 'sonner';
7
- export { e as Navigation, f as NavigationGroup, c as NavigationItem, N as NavigationLink, b as NavigationLinkComponent, d as NavigationLinkProps, a as NavigationProps } from '../../navigation-D8N1tMTu.js';
8
- export { a as SiteFooter, S as SiteFooterProps } from '../../site-footer-s113FZgC.js';
7
+ export { e as Navigation, f as NavigationGroup, c as NavigationItem, N as NavigationLink, b as NavigationLinkComponent, d as NavigationLinkProps, a as NavigationProps } from '../../navigation-CXZapKac.js';
8
+ export { a as SiteFooter, S as SiteFooterProps } from '../../site-footer-C4jgkGDU.js';
9
9
 
10
10
  declare function Avatar({ className, size, ...props }: React.ComponentProps<typeof Avatar$1.Root> & {
11
11
  size?: "default" | "sm" | "lg";
@@ -33,7 +33,7 @@ import {
33
33
  SelectValue,
34
34
  badgeVariants,
35
35
  buttonVariants
36
- } from "../../chunk-KPE4KKYM.js";
36
+ } from "../../chunk-Y5LW3ADW.js";
37
37
  import {
38
38
  __objRest,
39
39
  __spreadProps,
@@ -189,7 +189,7 @@ function CardTitle(_a) {
189
189
  "div",
190
190
  __spreadValues({
191
191
  "data-slot": "card-title",
192
- className: cn("leading-none font-semibold", className)
192
+ className: cn("text-base font-semibold", className)
193
193
  }, props)
194
194
  );
195
195
  }
@@ -293,7 +293,7 @@ function DialogContent(_a) {
293
293
  __spreadProps(__spreadValues({
294
294
  "data-slot": "dialog-content",
295
295
  className: cn(
296
- "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",
296
+ "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",
297
297
  className
298
298
  )
299
299
  }, props), {
@@ -363,7 +363,7 @@ function DialogTitle(_a) {
363
363
  DialogPrimitive.Title,
364
364
  __spreadValues({
365
365
  "data-slot": "dialog-title",
366
- className: cn("text-xl leading-normal font-semibold", className)
366
+ className: cn("text-xl font-semibold", className)
367
367
  }, props)
368
368
  );
369
369
  }
@@ -547,11 +547,11 @@ import { Toaster as Sonner } from "sonner";
547
547
  import { jsx as jsx6 } from "react/jsx-runtime";
548
548
  var Toaster = (_a) => {
549
549
  var props = __objRest(_a, []);
550
- const { theme = "system" } = useTheme();
550
+ const { theme = "system", forcedTheme } = useTheme();
551
551
  return /* @__PURE__ */ jsx6(
552
552
  Sonner,
553
553
  __spreadValues({
554
- theme,
554
+ theme: forcedTheme != null ? forcedTheme : theme,
555
555
  className: "toaster group font-sans",
556
556
  position: "bottom-right",
557
557
  duration: 4e3,
@@ -1 +1 @@
1
- export declare const uiRelease: Readonly<{version: "0.12.0"; contractVersion: "1"}>;
1
+ export declare const uiRelease: Readonly<{version: "0.13.1"; contractVersion: "1"}>;
@@ -1 +1 @@
1
- export const uiRelease = Object.freeze({"version":"0.12.0","contractVersion":"1"});
1
+ export const uiRelease = Object.freeze({"version":"0.13.1","contractVersion":"1"});
package/dist/lib/utils.js CHANGED
@@ -1,7 +1 @@
1
- "use client";
2
- import {
3
- cn
4
- } from "../chunk-JBTDRVZ7.js";
5
- export {
6
- cn
7
- };
1
+ export { cn } from "../source/lib/utils.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  naeilLocales
3
- } from "./chunk-U65OQHZY.js";
3
+ } from "./chunk-WT6QXR7O.js";
4
4
  import "./chunk-N3ETBM74.js";
5
5
 
6
6
  // src/naeil-locale/checker.ts
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
- import { ReactNode } from 'react';
3
+ import { ReactNode, JSX } from 'react';
4
4
 
5
5
  type NavigationLinkProps = React.ComponentPropsWithoutRef<"a"> & {
6
6
  href: string;
@@ -26,6 +26,8 @@ interface NavigationGroup {
26
26
  }
27
27
  type NavigationItem = NavigationLink | NavigationGroup;
28
28
  interface NavigationProps extends Omit<React.ComponentPropsWithoutRef<"header">, "children" | "onSelect"> {
29
+ /** Omit empty landmarks and dismiss toolbar-only sheets on desktop crossing. */
30
+ omitEmptyNavigation?: boolean;
29
31
  brand: React.ReactNode;
30
32
  items: NavigationItem[];
31
33
  /** Translated landmark/dialog/close labels are supplied by the application. */
@@ -71,14 +73,16 @@ interface NaeilHeaderAccountControlProps {
71
73
  declare function NaeilHeaderAccountControl({ locale, account, Link }: NaeilHeaderAccountControlProps): react_jsx_runtime.JSX.Element;
72
74
 
73
75
  interface NaeilHeaderProps extends Omit<NavigationProps, "brand" | "items" | "toolbar" | "mobileToolbar" | "labels"> {
76
+ service?: undefined;
74
77
  locale: NaeilHeaderLocale;
75
78
  /** Resolve canonical unlocalized paths. Defaults to the official localized site. */
76
79
  resolveHref?: (path: string) => string;
77
80
  brandHref?: string;
81
+ /** @deprecated Owned services must use service; retained for compatibility. */
78
82
  brandSlot?: ReactNode;
79
- /** Ignored when brandSlot replaces the complete brand content. */
83
+ /** @deprecated Owned services must use service; retained for compatibility. Ignored with brandSlot. */
80
84
  brandSuffix?: string;
81
- /** After the brand link, outside it; rendered once at every width. */
85
+ /** @deprecated Owned services must use service with no indicator; retained for compatibility. */
82
86
  statusSlot?: ReactNode;
83
87
  /** Product menu data, after catalog/blog and before flat subNav links. */
84
88
  appNavigation?: NavigationItem[];
@@ -99,17 +103,32 @@ interface NaeilHeaderProps extends Omit<NavigationProps, "brand" | "items" | "to
99
103
  };
100
104
  showProjects?: boolean;
101
105
  showBlog?: boolean;
106
+ /** @deprecated Owned services must use service and appNavigation; retained for compatibility. */
102
107
  subNav?: Array<{
103
108
  label: string;
104
109
  href: string;
105
110
  }>;
106
111
  currentProjectKey?: string;
107
112
  currentProjectHref?: string;
108
- /** Compatibility escape hatch replacing the default theme/locale/auth toolbar. */
113
+ /** @deprecated Owned services must use service and authSlot; retained for compatibility. */
109
114
  toolbarSlot?: ReactNode;
110
- /** Compatibility escape hatch for the legacy native external brand link. */
115
+ /** @deprecated Owned services must use service and Link; retained for compatibility. */
111
116
  brandLinkComponent?: NavigationLinkComponent;
112
117
  }
118
+ /** Canonical owned-service identifiers; their spelling is the visible brand suffix. */
119
+ declare const naeilServices: readonly ["abstorm", "home", "estimate", "jev"];
120
+ type NaeilService = typeof naeilServices[number];
121
+ declare const serviceCompatibilityProps: readonly ["brandSuffix", "statusSlot", "brandSlot", "toolbarSlot", "subNav", "brandLinkComponent", "projects", "projectsExternal", "showProjects", "showBlog", "translations", "className", "containerClassName", "style", "brandLinkProps", "mobileFooter", "omitEmptyNavigation"];
122
+ type ServiceCompatibilityProp = typeof serviceCompatibilityProps[number];
123
+ /** Strict owned-service props; the legacy interface remains independently extensible. */
124
+ type NaeilServiceHeaderProps = Omit<NaeilHeaderProps, ServiceCompatibilityProp | "service"> & {
125
+ service: NaeilService;
126
+ } & {
127
+ [Prop in ServiceCompatibilityProp]?: never;
128
+ };
129
+ /** Opt into the owned-service contract without changing the legacy composition. */
130
+ declare function NaeilHeader(props: NaeilServiceHeaderProps): JSX.Element;
131
+ declare function NaeilHeader(props: NaeilHeaderProps): JSX.Element;
113
132
  /** The actual header theme control; reads only the consumer's theme provider. */
114
133
  declare function NaeilHeaderThemeControl({ locale }: {
115
134
  locale: NaeilHeaderLocale;
@@ -119,8 +138,6 @@ declare function NaeilHeaderLocaleControl({ locale, onLocaleChange }: {
119
138
  locale: NaeilHeaderLocale;
120
139
  onLocaleChange: (locale: NaeilHeaderLocale) => void;
121
140
  }): react_jsx_runtime.JSX.Element;
122
- /** One complete, opt-in branded composition shared by naeil.dev and applications. */
123
- 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;
124
141
 
125
142
  interface SiteFooterProps extends React.ComponentPropsWithoutRef<"footer"> {
126
143
  brand?: React.ReactNode;
@@ -147,4 +164,4 @@ interface NaeilFooterProps extends Omit<SiteFooterProps, "brand" | "description"
147
164
  /** Canonical content in full/compact density. Product legal/status copy belongs in meta. */
148
165
  declare function NaeilFooter({ locale, resolveHref, variant, Link, onNavigate, ...props }: NaeilFooterProps): react_jsx_runtime.JSX.Element;
149
166
 
150
- export { NaeilFooter, type NaeilFooterLocale, type NaeilFooterProps, NaeilHeader, type NaeilHeaderAccount, NaeilHeaderAccountControl, type NaeilHeaderAccountControlProps, type NaeilHeaderLocale, NaeilHeaderLocaleControl, type NaeilHeaderProps, NaeilHeaderThemeControl };
167
+ export { NaeilFooter, type NaeilFooterLocale, type NaeilFooterProps, NaeilHeader, type NaeilHeaderAccount, NaeilHeaderAccountControl, type NaeilHeaderAccountControlProps, type NaeilHeaderLocale, NaeilHeaderLocaleControl, type NaeilHeaderProps, NaeilHeaderThemeControl, type NaeilService, type NaeilServiceHeaderProps, naeilServices };
@@ -1230,16 +1230,16 @@ import { cva as cva2 } from "class-variance-authority";
1230
1230
  import { Slot as Slot2 } from "radix-ui";
1231
1231
  import { jsx as jsx4 } from "react/jsx-runtime";
1232
1232
  var buttonVariants = cva2(
1233
- "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",
1233
+ "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",
1234
1234
  {
1235
1235
  variants: {
1236
1236
  variant: {
1237
- default: "bg-primary text-primary-foreground hover:bg-primary-hover",
1238
- destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90 ",
1239
- outline: "border border-border-strong bg-background hover:bg-surface-subtle",
1240
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
1241
- ghost: "hover:bg-surface-subtle",
1242
- link: "text-primary underline-offset-4 hover:underline"
1237
+ 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)]",
1238
+ 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)]",
1239
+ 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",
1240
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-pressed:bg-surface-selected aria-pressed:text-foreground aria-pressed:inset-ring-foreground",
1241
+ ghost: "hover:bg-surface-subtle aria-pressed:bg-surface-selected aria-pressed:text-foreground aria-pressed:inset-ring-foreground",
1242
+ link: "text-primary underline-offset-4 hover:underline aria-pressed:bg-surface-selected aria-pressed:text-foreground aria-pressed:inset-ring-foreground"
1243
1243
  },
1244
1244
  size: {
1245
1245
  default: "px-4 has-[>svg]:px-3",
@@ -1615,6 +1615,7 @@ function Navigation(_a) {
1615
1615
  toolbar,
1616
1616
  mobileToolbar,
1617
1617
  mobileFooter,
1618
+ omitEmptyNavigation = false,
1618
1619
  mobileOpen: controlledOpen,
1619
1620
  onMobileOpenChange,
1620
1621
  containerClassName,
@@ -1629,6 +1630,7 @@ function Navigation(_a) {
1629
1630
  "toolbar",
1630
1631
  "mobileToolbar",
1631
1632
  "mobileFooter",
1633
+ "omitEmptyNavigation",
1632
1634
  "mobileOpen",
1633
1635
  "onMobileOpenChange",
1634
1636
  "containerClassName",
@@ -1699,7 +1701,8 @@ function Navigation(_a) {
1699
1701
  setOpenGroup(null);
1700
1702
  }, [currentHref, setMobileOpen]);
1701
1703
  React.useEffect(() => {
1702
- const element = desktop.current;
1704
+ var _a2, _b2;
1705
+ const element = omitEmptyNavigation && !items.length ? (_b2 = (_a2 = desktop.current) == null ? void 0 : _a2.parentElement) == null ? void 0 : _b2.querySelector(".ui-navigation-toolbar") : desktop.current;
1703
1706
  if (!element || typeof ResizeObserver === "undefined") return;
1704
1707
  const observer = new ResizeObserver(() => {
1705
1708
  if (element.getBoundingClientRect().width > 0) setMobileOpen(false);
@@ -1707,7 +1710,8 @@ function Navigation(_a) {
1707
1710
  });
1708
1711
  observer.observe(element);
1709
1712
  return () => observer.disconnect();
1710
- }, [setMobileOpen]);
1713
+ }, [setMobileOpen, omitEmptyNavigation, items.length]);
1714
+ const DesktopElement = omitEmptyNavigation && !items.length ? "div" : "nav";
1711
1715
  const active = (item) => {
1712
1716
  var _a2;
1713
1717
  return (_a2 = item.active) != null ? _a2 : !item.external && item.href === currentHref;
@@ -1756,10 +1760,12 @@ function Navigation(_a) {
1756
1760
  children: /* @__PURE__ */ jsx6("div", { className: cn("ui-navigation-frame", containerClassName), children: /* @__PURE__ */ jsxs4("div", { className: "ui-navigation-row", children: [
1757
1761
  /* @__PURE__ */ jsx6("div", { className: "ui-navigation-brand", children: brand }),
1758
1762
  /* @__PURE__ */ jsx6(
1759
- "nav",
1763
+ DesktopElement,
1760
1764
  {
1761
- ref: desktop,
1762
- "aria-label": labels.navigation,
1765
+ ref: (element) => {
1766
+ desktop.current = element;
1767
+ },
1768
+ "aria-label": omitEmptyNavigation && !items.length ? void 0 : labels.navigation,
1763
1769
  className: "ui-navigation-desktop",
1764
1770
  children: items.map(
1765
1771
  (item) => "items" in item ? /* @__PURE__ */ jsx6(
@@ -1811,7 +1817,7 @@ function Navigation(_a) {
1811
1817
  className: "ui-navigation-sheet ui-popup",
1812
1818
  "aria-describedby": void 0,
1813
1819
  onCloseAutoFocus: (event) => {
1814
- var _a2, _b2;
1820
+ var _a2, _b2, _c, _d;
1815
1821
  if (controlledOpen !== void 0 && (!mounted.current || state.current.open)) {
1816
1822
  event.preventDefault();
1817
1823
  return;
@@ -1825,7 +1831,10 @@ function Navigation(_a) {
1825
1831
  }
1826
1832
  if (trigger.current && trigger.current.getBoundingClientRect().width === 0) {
1827
1833
  event.preventDefault();
1828
- (_b2 = (_a2 = desktop.current) == null ? void 0 : _a2.querySelector("button, a")) == null ? void 0 : _b2.focus();
1834
+ const target = (_a2 = desktop.current) == null ? void 0 : _a2.querySelector("button, a");
1835
+ const toolbar2 = omitEmptyNavigation && !items.length ? (_c = (_b2 = desktop.current) == null ? void 0 : _b2.parentElement) == null ? void 0 : _c.querySelector(".ui-navigation-toolbar") : void 0;
1836
+ 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;
1837
+ (_d = target != null ? target : toolbarTarget) == null ? void 0 : _d.focus();
1829
1838
  }
1830
1839
  },
1831
1840
  children: [
@@ -1840,7 +1849,7 @@ function Navigation(_a) {
1840
1849
  }
1841
1850
  )
1842
1851
  ] }),
1843
- /* @__PURE__ */ jsx6(
1852
+ (!omitEmptyNavigation || items.length > 0) && /* @__PURE__ */ jsx6(
1844
1853
  "nav",
1845
1854
  {
1846
1855
  "aria-label": labels.navigation,
@@ -1886,7 +1895,7 @@ function Navigation(_a) {
1886
1895
  }
1887
1896
 
1888
1897
  // src/lib/ui-release.ts
1889
- var uiRelease = { version: "0.12.0", contractVersion: "1" };
1898
+ var uiRelease = { version: "0.13.1", contractVersion: "1" };
1890
1899
 
1891
1900
  // src/components/naeil-header-data.ts
1892
1901
  var headerLocales = ["en", "ko", "ja"];
@@ -2078,6 +2087,58 @@ function NaeilHeaderAccountControl({ locale, account, Link = NativeLink3 }) {
2078
2087
 
2079
2088
  // src/components/naeil-header-core.tsx
2080
2089
  import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
2090
+ var naeilServices = Object.freeze(["abstorm", "home", "estimate", "jev"]);
2091
+ var serviceCompatibilityProps = [
2092
+ "brandSuffix",
2093
+ "statusSlot",
2094
+ "brandSlot",
2095
+ "toolbarSlot",
2096
+ "subNav",
2097
+ "brandLinkComponent",
2098
+ "projects",
2099
+ "projectsExternal",
2100
+ "showProjects",
2101
+ "showBlog",
2102
+ "translations",
2103
+ "className",
2104
+ "containerClassName",
2105
+ "style",
2106
+ "brandLinkProps",
2107
+ "mobileFooter",
2108
+ "omitEmptyNavigation"
2109
+ ];
2110
+ var warnedServiceProps = /* @__PURE__ */ new Set();
2111
+ function isDevelopment() {
2112
+ try {
2113
+ return process.env.NODE_ENV === "development";
2114
+ } catch (e) {
2115
+ return false;
2116
+ }
2117
+ }
2118
+ function warnServiceProp(name, message) {
2119
+ if (!isDevelopment() || warnedServiceProps.has(name)) return;
2120
+ warnedServiceProps.add(name);
2121
+ console.warn(message);
2122
+ }
2123
+ function isNaeilService(value) {
2124
+ return naeilServices.includes(value);
2125
+ }
2126
+ function NaeilHeader(input) {
2127
+ if (input.service === void 0) return /* @__PURE__ */ jsx9(HeaderComposition, __spreadValues({}, input));
2128
+ if (!isNaeilService(input.service)) {
2129
+ warnServiceProp("service", "NaeilHeader: invalid service; rendering the legacy profile.");
2130
+ const legacy = __spreadProps(__spreadValues({}, input), { service: void 0 });
2131
+ delete legacy.service;
2132
+ return /* @__PURE__ */ jsx9(HeaderComposition, __spreadValues({}, legacy));
2133
+ }
2134
+ const props = __spreadValues({}, input);
2135
+ for (const name of Object.keys(props)) {
2136
+ if (!serviceCompatibilityProps.includes(name) && name !== "data-slot" && name !== "data-global-header" && !name.startsWith("data-naeil-")) continue;
2137
+ delete props[name];
2138
+ warnServiceProp(name, `NaeilHeader: service profile ignores ${name}; use the shared service composition.`);
2139
+ }
2140
+ return /* @__PURE__ */ jsx9(HeaderComposition, __spreadProps(__spreadValues({}, props), { service: input.service }));
2141
+ }
2081
2142
  var subscribe = () => () => {
2082
2143
  };
2083
2144
  function NaeilHeaderThemeControl({ locale }) {
@@ -2114,8 +2175,9 @@ function NaeilHeaderLocaleControl({ locale, onLocaleChange }) {
2114
2175
  ] });
2115
2176
  }
2116
2177
  var NativeLink4 = (props) => /* @__PURE__ */ jsx9("a", __spreadValues({}, props));
2117
- function NaeilHeader(_a) {
2178
+ function HeaderComposition(_a) {
2118
2179
  var _b = _a, {
2180
+ service,
2119
2181
  locale,
2120
2182
  resolveHref = (path) => `https://naeil.dev/${locale}${path === "/" ? "" : path}`,
2121
2183
  brandHref,
@@ -2143,6 +2205,7 @@ function NaeilHeader(_a) {
2143
2205
  className,
2144
2206
  containerClassName
2145
2207
  } = _b, props = __objRest(_b, [
2208
+ "service",
2146
2209
  "locale",
2147
2210
  "resolveHref",
2148
2211
  "brandHref",
@@ -2171,6 +2234,11 @@ function NaeilHeader(_a) {
2171
2234
  "containerClassName"
2172
2235
  ]);
2173
2236
  var _b2, _c;
2237
+ if (service) {
2238
+ brandSuffix = service;
2239
+ showProjects = false;
2240
+ showBlog = false;
2241
+ }
2174
2242
  const content = useNaeilMessages(locale).header;
2175
2243
  const projectLinks = !showProjects ? [] : projects != null ? projects : headerProjects.map((project) => {
2176
2244
  var _a3, _b3, _c2;
@@ -2189,7 +2257,7 @@ function NaeilHeader(_a) {
2189
2257
  badge: project.status === "coming" ? /* @__PURE__ */ jsx9(Badge, { variant: "outline", children: (_c2 = translations == null ? void 0 : translations.project("status.coming")) != null ? _c2 : content.projects.status.coming }) : void 0
2190
2258
  };
2191
2259
  });
2192
- const brandDestination = brandHref != null ? brandHref : resolveHref("/");
2260
+ const brandDestination = brandHref != null ? brandHref : service ? "/" : resolveHref("/");
2193
2261
  const BrandLink = brandLinkComponent != null ? brandLinkComponent : Link;
2194
2262
  const _a2 = brandLinkProps != null ? brandLinkProps : {}, { onClick: onBrandClick } = _a2, brandProps = __objRest(_a2, ["onClick"]);
2195
2263
  const toolbar = /* @__PURE__ */ jsx9(
@@ -2215,11 +2283,11 @@ function NaeilHeader(_a) {
2215
2283
  __spreadProps(__spreadValues({
2216
2284
  href: brandDestination,
2217
2285
  "data-app-brand": true,
2218
- "aria-label": brandSlot == null ? ".naeil" : void 0
2286
+ "aria-label": service ? `.naeil ${service}` : brandSlot == null ? ".naeil" : void 0
2219
2287
  }, brandProps), {
2220
2288
  onClick: (event) => {
2221
2289
  onBrandClick == null ? void 0 : onBrandClick(event);
2222
- onNavigate == null ? void 0 : onNavigate(event, { id: "brand", href: brandDestination, label: ".naeil" });
2290
+ onNavigate == null ? void 0 : onNavigate(event, { id: "brand", href: brandDestination, label: service ? `.naeil ${service}` : ".naeil" });
2223
2291
  },
2224
2292
  children: brandSlot != null ? brandSlot : brandSuffix ? /* @__PURE__ */ jsxs7(Fragment3, { children: [
2225
2293
  logo,
@@ -2229,7 +2297,7 @@ function NaeilHeader(_a) {
2229
2297
  );
2230
2298
  return /* @__PURE__ */ jsx9(
2231
2299
  Navigation,
2232
- __spreadProps(__spreadValues({}, props), {
2300
+ __spreadProps(__spreadValues(__spreadValues({}, props), service ? { "data-slot": "navigation", omitEmptyNavigation: true } : {}), {
2233
2301
  "data-global-header": true,
2234
2302
  "data-naeil-header": true,
2235
2303
  "data-naeil-chrome": "",
@@ -2381,5 +2449,6 @@ export {
2381
2449
  NaeilHeader,
2382
2450
  NaeilHeaderAccountControl,
2383
2451
  NaeilHeaderLocaleControl,
2384
- NaeilHeaderThemeControl
2452
+ NaeilHeaderThemeControl,
2453
+ naeilServices
2385
2454
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  assertLocale
3
- } from "./chunk-U65OQHZY.js";
3
+ } from "./chunk-WT6QXR7O.js";
4
4
  import {
5
5
  __spreadValues
6
6
  } from "./chunk-N3ETBM74.js";
@@ -6,6 +6,10 @@ import {
6
6
  // src/naeil-locale/routing.ts
7
7
  var naeilLocales = ["en", "ko", "ja"];
8
8
  var defaultLocale = "en";
9
+ function escapeRegExp(value) {
10
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11
+ }
12
+ var localePrefix = new RegExp(`^/(?:${naeilLocales.map(escapeRegExp).join("|")})(?:/|$)`);
9
13
  var naeilRouting = {
10
14
  locales: naeilLocales,
11
15
  defaultLocale,
@@ -28,7 +32,7 @@ var origins = {
28
32
  estimate: "https://abestimate.naeil.dev"
29
33
  };
30
34
  var protocolPrefixes = ["/api", "/auth", "/_next", "/_vercel", "/.well-known", "/health", "/invite", "/return", "/webhook", "/images", "/fonts"];
31
- var protocolFiles = ["/favicon.ico", "/icon.svg", "/apple-icon.png", "/robots.txt", "/sitemap.xml"];
35
+ var protocolFiles = ["/favicon.ico", "/favicon.svg", "/icon.svg", "/apple-icon.png", "/robots.txt", "/sitemap.xml"];
32
36
  function defineLocaleService(service) {
33
37
  var _a, _b, _c, _d;
34
38
  if (origins[service.id] !== service.origin) throw new Error("Service origin must match the exact owned origin");
@@ -36,7 +40,7 @@ function defineLocaleService(service) {
36
40
  if (!paths.length || new Set(paths).size !== paths.length) throw new Error("Route inventory is empty or duplicated");
37
41
  for (const path of paths) {
38
42
  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}`);
39
- 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}`);
43
+ 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}`);
40
44
  }
41
45
  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");
42
46
  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 : []]) }));
@@ -72,7 +76,7 @@ function invalid(reason, status = 404, locale) {
72
76
  return __spreadValues({ kind: "invalid", status, reason }, locale ? { locale } : {});
73
77
  }
74
78
  function splitInput(input, origin) {
75
- if (!input || /[\u0000-\u0020\u007f\\]/.test(input)) return;
79
+ if (!input || /[\u0000-\u0020\u007f]/.test(input)) return;
76
80
  let path = input;
77
81
  if (!path.startsWith("/")) {
78
82
  if (!path.startsWith(origin + "/") && path !== origin) return;
@@ -85,12 +89,14 @@ function splitInput(input, origin) {
85
89
  const queryAt = withoutHash.indexOf("?");
86
90
  return { pathname: queryAt < 0 ? withoutHash : withoutHash.slice(0, queryAt), search: queryAt < 0 ? "" : withoutHash.slice(queryAt), hash };
87
91
  }
88
- function safePath(path) {
92
+ function safePath(path, service) {
93
+ var _a;
89
94
  if (!path.startsWith("/") || path.includes("//")) return false;
95
+ const reserved = /* @__PURE__ */ new Set([...naeilLocales, ...[...protocolPrefixes, ...(_a = service.protocolPrefixes) != null ? _a : []].flatMap((prefix) => prefix.split("/").filter(Boolean))]);
90
96
  return path.split("/").every((segment) => {
91
97
  try {
92
98
  const decoded = decodeURIComponent(segment);
93
- return !/[\u0000-\u001f\u007f/\\%?#]/.test(decoded) && decoded !== "." && decoded !== ".." && (!/^(?:en|ko|ja|api|auth|_next|invite|return|webhook)$/.test(decoded) || decoded === segment);
99
+ return !/[\u0000-\u001f\u007f/\\%?#]/.test(decoded) && decoded !== "." && decoded !== ".." && (!reserved.has(decoded) || decoded === segment);
94
100
  } catch (e) {
95
101
  return false;
96
102
  }
@@ -113,7 +119,7 @@ function resolveLocaleUrl(inputService, input) {
113
119
  var _a, _b;
114
120
  const service = getLocaleService(inputService);
115
121
  const parts = splitInput(input, service.origin);
116
- if (!parts || !safePath(parts.pathname)) return invalid("Malformed or encoded path");
122
+ if (!parts || !safePath(parts.pathname, service)) return invalid("Malformed or encoded path");
117
123
  const rawPathname = parts.pathname;
118
124
  const pathname = rawPathname !== "/" && rawPathname.endsWith("/") ? rawPathname.slice(0, -1) : rawPathname;
119
125
  if (isProtocolPath(pathname, service) && !((_a = service.uiProtocolExceptions) == null ? void 0 : _a.includes(pathname))) return { kind: "protocol", pathname };
@@ -121,7 +127,7 @@ function resolveLocaleUrl(inputService, input) {
121
127
  const explicit = isNaeilLocale(segments[1]);
122
128
  const pathLocale = explicit ? segments[1] : void 0;
123
129
  const logicalPath = explicit ? "/" + segments.slice(2).join("/") : pathname;
124
- 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);
130
+ 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);
125
131
  const route = matchRoute(service, logicalPath);
126
132
  if (!route) return invalid("Unregistered UI route", 404, pathLocale);
127
133
  const legacy = service.id !== "main";
@@ -137,7 +143,7 @@ function localePath(inputService, unlocalizedRoute, locale) {
137
143
  assertLocale(locale);
138
144
  const service = getLocaleService(inputService);
139
145
  const parts = splitInput(unlocalizedRoute, service.origin);
140
- if (!parts || /^\/(?:en|ko|ja)(?:\/|$)/.test(parts.pathname)) throw new Error("localePath requires an unlocalized route");
146
+ if (!parts || localePrefix.test(parts.pathname)) throw new Error("localePath requires an unlocalized route");
141
147
  const result = resolveLocaleUrl(service, parts.pathname);
142
148
  if (result.kind !== "ui") throw new Error("Unregistered UI destination");
143
149
  return prefixPath(result.logicalPath, locale) + (service.id === "main" ? parts.search : stripLegacyLang(parts.search)) + parts.hash;