najm-kit 2.11.19 → 2.11.21
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 +13 -6
- package/README.md +15 -9
- package/dist/index.d.ts +2 -1
- package/dist/index.mjs +5 -3
- package/dist/server/index.d.ts +6 -0
- package/dist/server/index.mjs +43 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 2.11.
|
|
3
|
+
## 2.11.21 - 2026-09-09
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
- Added first-visit browser-language negotiation to `defineNajmPreferences`.
|
|
6
|
+
Applications can pass the raw `Accept-Language` header to `resolve`; explicit
|
|
7
|
+
cookies and account fallbacks retain priority, while quality weights and
|
|
8
|
+
regional language tags are matched against the configured catalog.
|
|
9
9
|
|
|
10
|
-
## 2.11.
|
|
10
|
+
## 2.11.19 - 2026-09-06
|
|
11
|
+
|
|
12
|
+
- Made native calendar month and year dropdowns follow the resolved Najm light
|
|
13
|
+
or dark theme instead of opening with the browser's light color scheme.
|
|
14
|
+
- Added pointer cursors to interactive calendar days while retaining the
|
|
15
|
+
not-allowed cursor for disabled dates.
|
|
16
|
+
|
|
17
|
+
## 2.11.18 - 2026-09-06
|
|
11
18
|
|
|
12
19
|
- Added a resolved-state render-prop form of `NSidebar.footer`, matching the
|
|
13
20
|
existing logo contract. Custom footers now receive the actual desktop
|
package/README.md
CHANGED
|
@@ -1132,14 +1132,19 @@ 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([
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1139
|
+
const [cookieStore, requestHeaders, session] = await Promise.all([
|
|
1140
|
+
cookies(),
|
|
1141
|
+
headers(),
|
|
1142
|
+
getSession(),
|
|
1143
|
+
]);
|
|
1144
|
+
const { language, theme, timeZone } = preferences.resolve(cookieStore, {
|
|
1145
|
+
languageFallback: session?.user.language,
|
|
1146
|
+
acceptLanguage: requestHeaders.get("accept-language"),
|
|
1147
|
+
});
|
|
1143
1148
|
|
|
1144
1149
|
return (
|
|
1145
1150
|
<html
|
|
@@ -1155,10 +1160,11 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
|
|
1155
1160
|
}
|
|
1156
1161
|
```
|
|
1157
1162
|
|
|
1158
|
-
`resolve` takes anything with `get(name)` — Next's cookie store, or a plain
|
|
1159
|
-
object in a test.
|
|
1160
|
-
|
|
1161
|
-
|
|
1163
|
+
`resolve` takes anything with `get(name)` — Next's cookie store, or a plain
|
|
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
|
|
package/dist/index.d.ts
CHANGED
|
@@ -846,6 +846,7 @@ interface NConfirmDialogProps {
|
|
|
846
846
|
declare function NConfirmDialog({ open, onOpenChange, title, description, confirmLabel, cancelLabel, onConfirm, variant, loading, children, icon, }: NConfirmDialogProps): React__default.JSX.Element;
|
|
847
847
|
|
|
848
848
|
interface NDeleteDialogContentProps {
|
|
849
|
+
title?: ReactNode;
|
|
849
850
|
itemName: string;
|
|
850
851
|
itemType?: string;
|
|
851
852
|
icon?: NIconSource;
|
|
@@ -866,7 +867,7 @@ interface NDeleteDialogProps extends Omit<NDeleteDialogContentProps, "className"
|
|
|
866
867
|
onConfirm?: () => void | Promise<void>;
|
|
867
868
|
onCancel?: () => void | Promise<void>;
|
|
868
869
|
}
|
|
869
|
-
declare function NDeleteDialogContent({ itemName, itemType, icon: Icon, warningText, className, }: NDeleteDialogContentProps): React$1.JSX.Element;
|
|
870
|
+
declare function NDeleteDialogContent({ title, itemName, itemType, icon: Icon, warningText, className, }: NDeleteDialogContentProps): React$1.JSX.Element;
|
|
870
871
|
declare function NDeleteDialog({ open, title, description, itemName, itemType, icon, warningText, confirmText, cancelText, loading, zIndex, className, contentClassName, onOpenChange, onConfirm, onCancel, }: NDeleteDialogProps): React$1.JSX.Element;
|
|
871
872
|
|
|
872
873
|
declare function NPortalScopeProvider({ className, children }: {
|
package/dist/index.mjs
CHANGED
|
@@ -2233,6 +2233,7 @@ function NConfirmDialog({
|
|
|
2233
2233
|
] }) });
|
|
2234
2234
|
}
|
|
2235
2235
|
function NDeleteDialogContent({
|
|
2236
|
+
title = "Delete",
|
|
2236
2237
|
itemName,
|
|
2237
2238
|
itemType,
|
|
2238
2239
|
icon: Icon2 = "trash-2",
|
|
@@ -2242,10 +2243,10 @@ function NDeleteDialogContent({
|
|
|
2242
2243
|
return /* @__PURE__ */ jsx("form", { id: "najm-delete-form", onSubmit: (e) => e.preventDefault(), children: /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col items-center text-center", className), children: [
|
|
2243
2244
|
/* @__PURE__ */ jsx("div", { className: "mb-6 flex size-16 items-center justify-center rounded-[20px] bg-destructive/10 text-destructive dark:bg-[#5a1822] dark:text-white", children: /* @__PURE__ */ jsx(NIcon, { icon: Icon2, className: "size-7" }) }),
|
|
2244
2245
|
/* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
2245
|
-
/* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold leading-none text-card-foreground dark:text-white", children:
|
|
2246
|
+
/* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold leading-none text-card-foreground dark:text-white", children: title }),
|
|
2246
2247
|
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
2247
2248
|
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium leading-5 text-muted-foreground dark:text-[#aab4c2]", children: warningText }),
|
|
2248
|
-
/* @__PURE__ */ jsxs("p", { className: "max-w-[280px]
|
|
2249
|
+
/* @__PURE__ */ jsxs("p", { className: "mx-auto max-w-[280px] break-words text-center text-sm font-bold leading-5 text-card-foreground dark:text-white", children: [
|
|
2249
2250
|
'"',
|
|
2250
2251
|
itemName,
|
|
2251
2252
|
'"',
|
|
@@ -2296,6 +2297,7 @@ function NDeleteDialog({
|
|
|
2296
2297
|
/* @__PURE__ */ jsx(
|
|
2297
2298
|
NDeleteDialogContent,
|
|
2298
2299
|
{
|
|
2300
|
+
title,
|
|
2299
2301
|
itemName,
|
|
2300
2302
|
itemType,
|
|
2301
2303
|
icon,
|
|
@@ -2312,7 +2314,7 @@ function NDeleteDialog({
|
|
|
2312
2314
|
variant: "outline",
|
|
2313
2315
|
onClick: onCancel,
|
|
2314
2316
|
disabled: loading,
|
|
2315
|
-
className: "h-10 w-full rounded-[10px] border border-
|
|
2317
|
+
className: "h-10 w-full rounded-[10px] border border-destructive bg-transparent text-sm font-bold text-card-foreground shadow-none hover:bg-transparent hover:text-card-foreground dark:border-destructive dark:bg-transparent dark:text-white dark:hover:bg-transparent dark:hover:text-white",
|
|
2316
2318
|
children: cancelText
|
|
2317
2319
|
}
|
|
2318
2320
|
),
|
package/dist/server/index.d.ts
CHANGED
|
@@ -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>;
|
package/dist/server/index.mjs
CHANGED
|
@@ -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 :
|
|
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 {
|