najm-kit 2.11.20 → 2.11.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,4 +1,19 @@
1
- # Changelog
1
+ # Changelog
2
+
3
+ ## 2.11.22 - 2026-09-11
4
+
5
+ - Allowed destructive dialogs to render a caller-supplied title, wrap and
6
+ center long item names, and use a transparent destructive-outline cancel
7
+ action.
8
+ - Made native calendar caption dropdowns follow the resolved light or dark
9
+ theme and clarified pointer feedback for enabled and disabled dates.
10
+
11
+ ## 2.11.21 - 2026-09-09
12
+
13
+ - Added first-visit browser-language negotiation to `defineNajmPreferences`.
14
+ Applications can pass the raw `Accept-Language` header to `resolve`; explicit
15
+ cookies and account fallbacks retain priority, while quality weights and
16
+ regional language tags are matched against the configured catalog.
2
17
 
3
18
  ## 2.11.19 - 2026-09-06
4
19
 
package/README.md CHANGED
@@ -1132,13 +1132,18 @@ field are the same `400`. Nothing from the request body reaches the response.
1132
1132
 
1133
1133
  ```tsx
1134
1134
  // src/app/layout.tsx
1135
- import { cookies } from "next/headers";
1135
+ import { cookies, headers } from "next/headers";
1136
1136
  import { preferences } from "@/preferences";
1137
1137
 
1138
1138
  export default async function RootLayout({ children }: { children: React.ReactNode }) {
1139
- const [cookieStore, session] = await Promise.all([cookies(), getSession()]);
1139
+ const [cookieStore, requestHeaders, session] = await Promise.all([
1140
+ cookies(),
1141
+ headers(),
1142
+ getSession(),
1143
+ ]);
1140
1144
  const { language, theme, timeZone } = preferences.resolve(cookieStore, {
1141
1145
  languageFallback: session?.user.language,
1146
+ acceptLanguage: requestHeaders.get("accept-language"),
1142
1147
  });
1143
1148
 
1144
1149
  return (
@@ -1156,9 +1161,10 @@ export default async function RootLayout({ children }: { children: React.ReactNo
1156
1161
  ```
1157
1162
 
1158
1163
  `resolve` takes anything with `get(name)` — Next's cookie store, or a plain
1159
- object in a test. Precedence is cookie, then `languageFallback`, then the
1160
- catalog default; an invalid or dropped cookie language falls through to the
1161
- fallback rather than pinning the UI.
1164
+ object in a test. Language precedence is cookie, then `languageFallback`, then
1165
+ the `acceptLanguage` request header, then the catalog default. Browser
1166
+ negotiation honors quality weights and regional tags; invalid stored values
1167
+ fall through rather than pinning the UI.
1162
1168
 
1163
1169
  ### Types
1164
1170
 
@@ -77,6 +77,12 @@ interface NajmPreferenceResolveOptions {
77
77
  * valid cookie: the cookie is what the user last chose in this browser.
78
78
  */
79
79
  languageFallback?: unknown;
80
+ /**
81
+ * The raw `Accept-Language` request header. Used only after the language
82
+ * cookie and `languageFallback`; quality weights and regional language tags
83
+ * are matched against the application's supported languages.
84
+ */
85
+ acceptLanguage?: string | null;
80
86
  }
81
87
  /** A route handler, ready to `export const POST = ...`. */
82
88
  type NajmPreferenceHandler = (request: Request) => Promise<Response>;
@@ -44,6 +44,44 @@ function rejection(message) {
44
44
  function accepted(field, value, cookie) {
45
45
  return Response.json({ [field]: value }, { headers: { "Set-Cookie": cookie } });
46
46
  }
47
+ var LANGUAGE_RANGE_PATTERN = /^(?:\*|[a-z]{1,8}(?:-[a-z0-9]{1,8})*)$/i;
48
+ var LANGUAGE_QUALITY_PATTERN = /^q\s*=\s*(0(?:\.\d{0,3})?|1(?:\.0{0,3})?)$/i;
49
+ function parseAcceptLanguage(value) {
50
+ if (!value) return [];
51
+ return value.split(",").map((entry, order) => {
52
+ const [rawRange, ...parameters] = entry.split(";");
53
+ const range = rawRange?.trim().toLowerCase() ?? "";
54
+ if (!LANGUAGE_RANGE_PATTERN.test(range)) return null;
55
+ let quality = 1;
56
+ for (const rawParameter of parameters) {
57
+ const match = LANGUAGE_QUALITY_PATTERN.exec(rawParameter.trim());
58
+ if (!match) return null;
59
+ quality = Number(match[1]);
60
+ }
61
+ return { range, quality, order };
62
+ }).filter((entry) => entry !== null).sort((left, right) => right.quality - left.quality || left.order - right.order);
63
+ }
64
+ function lookupAcceptedLanguage(value, supportedLanguages, defaultLanguage) {
65
+ const supported = supportedLanguages.map((language) => ({
66
+ language,
67
+ normalized: language.toLowerCase()
68
+ }));
69
+ for (const preference of parseAcceptLanguage(value)) {
70
+ if (preference.quality === 0) continue;
71
+ if (preference.range === "*") return defaultLanguage;
72
+ let candidate = preference.range;
73
+ while (candidate) {
74
+ const match = supported.find(
75
+ (entry) => entry.normalized === candidate || entry.normalized.startsWith(`${candidate}-`)
76
+ );
77
+ if (match) return match.language;
78
+ const separator = candidate.lastIndexOf("-");
79
+ if (separator === -1) break;
80
+ candidate = candidate.slice(0, separator);
81
+ }
82
+ }
83
+ return void 0;
84
+ }
47
85
  function defineNajmPreferences(config) {
48
86
  const { i18n } = config;
49
87
  const timeZones = Object.freeze([
@@ -81,7 +119,11 @@ function defineNajmPreferences(config) {
81
119
  const isLanguage = (value) => typeof value === "string" && i18n.supportedLanguages.includes(value);
82
120
  function resolve(cookies, options = {}) {
83
121
  const languageCookie = cookies.get(cookieNames.language)?.value;
84
- const language = isLanguage(languageCookie) ? languageCookie : i18n.normalizeLanguage(options.languageFallback);
122
+ const language = isLanguage(languageCookie) ? languageCookie : isLanguage(options.languageFallback) ? options.languageFallback : lookupAcceptedLanguage(
123
+ options.acceptLanguage,
124
+ i18n.supportedLanguages,
125
+ i18n.defaultLanguage
126
+ ) ?? i18n.defaultLanguage;
85
127
  const themeCookie = cookies.get(cookieNames.theme)?.value;
86
128
  const timeZoneCookie = cookies.get(cookieNames.timeZone)?.value;
87
129
  return {
package/dist/theme.css CHANGED
@@ -317,10 +317,10 @@ input:autofill {
317
317
  overflow-x: auto;
318
318
  scrollbar-width: none;
319
319
  }
320
- .najm-overlay-scroll-x::-webkit-scrollbar {
321
- display: none;
322
- }
323
-
320
+ .najm-overlay-scroll-x::-webkit-scrollbar {
321
+ display: none;
322
+ }
323
+
324
324
  /* Responsive table actions stay discoverable on touch and tablet layouts.
325
325
  Fine-pointer desktops may keep the quieter hover/focus reveal treatment. */
326
326
  .ntable-card-action {
@@ -377,7 +377,7 @@ input:autofill {
377
377
  .nimage-input-compact-overlay:focus-visible {
378
378
  opacity: 1;
379
379
  }
380
- }
380
+ }
381
381
 
382
382
  /* OverlayScrollbars theme used by the <NajmScroll> component — a thin,
383
383
  translucent slate bar that floats over content with no reserved space.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "najm-kit",
3
- "version": "2.11.20",
3
+ "version": "2.11.22",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Reusable React UI component package for Najm framework",