najm-kit 2.8.2 → 2.10.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.
@@ -3,6 +3,76 @@ import * as React2 from 'react';
3
3
  import React2__default from 'react';
4
4
  import { jsx } from 'react/jsx-runtime';
5
5
 
6
+ // src/components/Badge/status.ts
7
+ var NAJM_STATUS_COLORS = {
8
+ active: "success",
9
+ approved: "success",
10
+ completed: "success",
11
+ confirmed: "success",
12
+ delivered: "success",
13
+ paid: "success",
14
+ published: "success",
15
+ succeeded: "success",
16
+ validated: "success",
17
+ verified: "success",
18
+ in_preparation: "warning",
19
+ in_progress: "warning",
20
+ out_for_delivery: "warning",
21
+ pending: "warning",
22
+ pending_email_verification: "warning",
23
+ pending_review: "warning",
24
+ processing: "warning",
25
+ submitted: "warning",
26
+ paused: "info",
27
+ purchased: "info",
28
+ scheduled: "info",
29
+ archived: "neutral",
30
+ cancelled: "neutral",
31
+ canceled: "neutral",
32
+ closed: "neutral",
33
+ draft: "neutral",
34
+ inactive: "neutral",
35
+ stopped: "neutral",
36
+ unknown: "neutral",
37
+ blocked: "destructive",
38
+ expired: "destructive",
39
+ failed: "destructive",
40
+ refunded: "destructive",
41
+ rejected: "destructive",
42
+ suspended: "destructive"
43
+ };
44
+ var NAJM_COLOR_TEXT_CLASSES = {
45
+ primary: "text-primary",
46
+ secondary: "text-secondary-foreground",
47
+ accent: "text-accent-foreground",
48
+ neutral: "text-muted-foreground",
49
+ info: "text-info",
50
+ success: "text-success",
51
+ warning: "text-warning",
52
+ destructive: "text-destructive"
53
+ };
54
+ function normalizeStatusToken(status) {
55
+ return status.trim().toLowerCase().replace(/[\s-]+/g, "_");
56
+ }
57
+ function lookup(map, status, normalized) {
58
+ if (!map) return void 0;
59
+ return map[status] ?? map[normalized];
60
+ }
61
+ function findStatusColor(status, statusMap) {
62
+ if (!status) return void 0;
63
+ const normalized = normalizeStatusToken(status);
64
+ return lookup(statusMap, status, normalized) ?? lookup(NAJM_STATUS_COLORS, status, normalized);
65
+ }
66
+ function resolveStatusColor(status, statusMap, fallback = "neutral") {
67
+ return findStatusColor(status, statusMap) ?? fallback;
68
+ }
69
+ function colorTextClass(color) {
70
+ return NAJM_COLOR_TEXT_CLASSES[color] ?? "text-foreground";
71
+ }
72
+ function statusTextClass(status, statusMap, fallback) {
73
+ const color = findStatusColor(status, statusMap) ?? fallback;
74
+ return color ? colorTextClass(color) : "";
75
+ }
6
76
  var NTableDefaultsContext = React2__default.createContext({});
7
77
  function NTableDefaultsProvider({
8
78
  children,
@@ -190,12 +260,41 @@ function NajmDesignEditorProvider({
190
260
  }
191
261
  function noop() {
192
262
  }
263
+ var NBadgeDefaultsContext = React2.createContext(null);
264
+ function NBadgeDefaultsProvider({
265
+ defaults,
266
+ t,
267
+ children
268
+ }) {
269
+ const value = React2.useMemo(
270
+ () => ({ defaults, t }),
271
+ [defaults, t]
272
+ );
273
+ return /* @__PURE__ */ jsx(NBadgeDefaultsContext.Provider, { value, children });
274
+ }
275
+ function useNBadgeDefaults() {
276
+ return React2.useContext(NBadgeDefaultsContext);
277
+ }
278
+ function resolveBadgeStatusLabel(status, defaults, t) {
279
+ if (!defaults) return void 0;
280
+ const normalized = normalizeStatusToken(status);
281
+ const literal = defaults.statusLabels?.[status] ?? defaults.statusLabels?.[normalized];
282
+ if (literal !== void 0) return literal;
283
+ const key = defaults.statusLabelKeys?.[status] ?? defaults.statusLabelKeys?.[normalized];
284
+ return key !== void 0 && t ? t(key) : void 0;
285
+ }
286
+ function mergeBadgeMaps(base, overrides) {
287
+ if (!base) return overrides;
288
+ if (!overrides) return base;
289
+ return { ...base, ...overrides };
290
+ }
193
291
  function NajmUICore({
194
292
  children,
195
293
  className,
196
294
  t,
197
295
  paginationKeyPrefix = DEFAULT_PAGINATION_KEY_PREFIX,
198
- tableDefaults
296
+ tableDefaults,
297
+ badgeDefaults
199
298
  }) {
200
299
  const { theme } = useNajmTheme();
201
300
  const design = useNajmDesignEditor()?.design ?? EMPTY_DESIGN;
@@ -211,7 +310,7 @@ function NajmUICore({
211
310
  config: design,
212
311
  mode: theme,
213
312
  className: cn("min-h-full", className),
214
- children: /* @__PURE__ */ jsx(NTableDefaultsProvider, { value: defaults, children })
313
+ children: /* @__PURE__ */ jsx(NTableDefaultsProvider, { value: defaults, children: /* @__PURE__ */ jsx(NBadgeDefaultsProvider, { defaults: badgeDefaults, t, children }) })
215
314
  }
216
315
  );
217
316
  }
@@ -223,6 +322,7 @@ function NajmUIProvider({
223
322
  t,
224
323
  paginationKeyPrefix,
225
324
  tableDefaults,
325
+ badgeDefaults,
226
326
  initialTheme,
227
327
  initialTimeZone,
228
328
  onThemeChange,
@@ -237,6 +337,7 @@ function NajmUIProvider({
237
337
  t,
238
338
  paginationKeyPrefix,
239
339
  tableDefaults,
340
+ badgeDefaults,
240
341
  children
241
342
  }
242
343
  ) });
@@ -254,4 +355,63 @@ function NajmUIProvider({
254
355
  );
255
356
  }
256
357
 
257
- export { DEFAULT_PAGINATION_KEY_PREFIX, DEFAULT_TIME_ZONE, EMPTY_DESIGN, NTableDefaultsProvider, NajmDesignEditorProvider, NajmPreferencesProvider, NajmUIProvider, buildPaginationLabels, useNTableDefaults, useNajmDesignEditor, useNajmPreferencesContext, useNajmTheme, useNajmTimeZone, useResolvedPaginationLabels };
358
+ // src/lib/imageSource.ts
359
+ var INLINE_SOURCE = /^(?:data|blob):/i;
360
+ function withSrcVersion(src, version) {
361
+ if (version === null || version === void 0 || version === "") return src;
362
+ if (INLINE_SOURCE.test(src)) return src;
363
+ const separator = src.includes("?") ? "&" : "?";
364
+ return `${src}${separator}v=${encodeURIComponent(String(version))}`;
365
+ }
366
+ function normalizeImageSources(candidates, version) {
367
+ const sources = [];
368
+ for (const candidate of candidates) {
369
+ const trimmed = candidate?.trim();
370
+ if (!trimmed) continue;
371
+ const stamped = withSrcVersion(trimmed, version);
372
+ if (!sources.includes(stamped)) sources.push(stamped);
373
+ }
374
+ return sources;
375
+ }
376
+ function selectImageSource(sources, failed) {
377
+ return sources.find((source) => !failed.includes(source));
378
+ }
379
+ var NO_FAILURES = [];
380
+ function useImageChain(sources) {
381
+ const key = JSON.stringify(sources);
382
+ const [state, setState] = React2.useState(() => ({
383
+ key,
384
+ failed: NO_FAILURES,
385
+ loaded: null
386
+ }));
387
+ const fresh = { key, failed: NO_FAILURES, loaded: null };
388
+ const current = state.key === key ? state : fresh;
389
+ if (state.key !== key) setState(fresh);
390
+ const active = selectImageSource(sources, current.failed);
391
+ const markLoaded = React2.useCallback(() => {
392
+ if (active === void 0) return;
393
+ setState(
394
+ (prev) => prev.key !== key || prev.loaded === active ? prev : { ...prev, loaded: active }
395
+ );
396
+ }, [key, active]);
397
+ const markFailed = React2.useCallback(() => {
398
+ if (active === void 0) return;
399
+ setState((prev) => {
400
+ if (prev.key !== key || prev.failed.includes(active)) return prev;
401
+ return {
402
+ ...prev,
403
+ failed: [...prev.failed, active],
404
+ loaded: prev.loaded === active ? null : prev.loaded
405
+ };
406
+ });
407
+ }, [key, active]);
408
+ return {
409
+ src: active ?? sources[sources.length - 1],
410
+ exhausted: sources.length > 0 && active === void 0,
411
+ loaded: active !== void 0 && current.loaded === active,
412
+ markLoaded,
413
+ markFailed
414
+ };
415
+ }
416
+
417
+ export { DEFAULT_PAGINATION_KEY_PREFIX, DEFAULT_TIME_ZONE, EMPTY_DESIGN, NAJM_COLOR_TEXT_CLASSES, NAJM_STATUS_COLORS, NTableDefaultsProvider, NajmDesignEditorProvider, NajmPreferencesProvider, NajmUIProvider, buildPaginationLabels, colorTextClass, findStatusColor, mergeBadgeMaps, normalizeImageSources, normalizeStatusToken, resolveBadgeStatusLabel, resolveStatusColor, statusTextClass, useImageChain, useNBadgeDefaults, useNTableDefaults, useNajmDesignEditor, useNajmPreferencesContext, useNajmTheme, useNajmTimeZone, useResolvedPaginationLabels };
@@ -1,4 +1,4 @@
1
- import { useNajmPreferencesContext } from './chunk-USZUOJMK.mjs';
1
+ import { useNajmPreferencesContext } from './chunk-FDONJASN.mjs';
2
2
  import { humanizeToken, DEFAULT_PLACEHOLDER, formatRelativeTime, formatTime, formatDateTime, formatDate, formatPercent, formatNumber, formatCurrency } from './chunk-JABLSOQN.mjs';
3
3
  import * as React2 from 'react';
4
4
  import { createContext, useContext, useMemo, useState, useCallback } from 'react';
@@ -0,0 +1,404 @@
1
+ import { useNajmComponentStyle, cn } from './chunk-KVZACF4G.mjs';
2
+ import { resolveVariantAlias, resolveRadiusValue } from './chunk-TFHWLE7N.mjs';
3
+ import * as React2 from 'react';
4
+ import React2__default, { createContext, useContext } from 'react';
5
+ import * as LucideIcons from 'lucide-react';
6
+ import { LoaderCircleIcon } from 'lucide-react';
7
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
+ import { Slot } from '@radix-ui/react-slot';
9
+ import { cva } from 'class-variance-authority';
10
+ import { OverlayScrollbars } from 'overlayscrollbars';
11
+ import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
12
+
13
+ function toPascalCase(value) {
14
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[\s_-]+/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
15
+ }
16
+ function isImageSource(value) {
17
+ return value.startsWith("/") || value.startsWith("./") || value.startsWith("../") || value.startsWith("http://") || value.startsWith("https://") || value.startsWith("data:image/") || /\.(avif|gif|jpe?g|png|svg|webp)(\?.*)?$/i.test(value);
18
+ }
19
+ function isComponentSource(value) {
20
+ return typeof value === "function" || Boolean(value && typeof value === "object" && "$$typeof" in value);
21
+ }
22
+ var NIcon = ({
23
+ icon = "circle",
24
+ size = 24,
25
+ alt = "",
26
+ className,
27
+ style,
28
+ onClick,
29
+ ...rest
30
+ }) => {
31
+ const dimensionStyle = {
32
+ width: size,
33
+ height: size,
34
+ ...style
35
+ };
36
+ if (React2__default.isValidElement(icon)) {
37
+ return React2__default.cloneElement(icon, {
38
+ className: cn(className, icon.props.className),
39
+ onClick,
40
+ ...rest
41
+ });
42
+ }
43
+ if (icon && typeof icon === "object" && "src" in icon) {
44
+ return /* @__PURE__ */ jsx(
45
+ "img",
46
+ {
47
+ alt: icon.alt ?? alt,
48
+ className: cn("inline-block shrink-0 object-contain", className),
49
+ src: icon.src,
50
+ style: dimensionStyle,
51
+ onClick,
52
+ ...rest
53
+ }
54
+ );
55
+ }
56
+ if (isComponentSource(icon)) {
57
+ const IconComponent = icon;
58
+ return /* @__PURE__ */ jsx(IconComponent, { size, className, onClick, style, ...rest });
59
+ }
60
+ if (typeof icon === "string") {
61
+ if (isImageSource(icon)) {
62
+ return /* @__PURE__ */ jsx(
63
+ "img",
64
+ {
65
+ alt,
66
+ className: cn("inline-block shrink-0 object-contain", className),
67
+ src: icon,
68
+ style: dimensionStyle,
69
+ onClick,
70
+ ...rest
71
+ }
72
+ );
73
+ }
74
+ const pascalCaseIcon = toPascalCase(icon);
75
+ if (LucideIcons[pascalCaseIcon]) {
76
+ const LucideIcon = LucideIcons[pascalCaseIcon];
77
+ return /* @__PURE__ */ jsx(LucideIcon, { size, className, onClick, style, ...rest });
78
+ }
79
+ return null;
80
+ }
81
+ return null;
82
+ };
83
+ NIcon.displayName = "NIcon";
84
+
85
+ // src/theme/borders.ts
86
+ var SURFACE_BORDER_CLASSES = {
87
+ all: "najm-border border-border",
88
+ top: "najm-border-t border-border",
89
+ right: "najm-border-r border-border",
90
+ bottom: "najm-border-b border-border",
91
+ left: "najm-border-l border-border"
92
+ };
93
+ var SIDEBAR_BORDER_CLASSES = {
94
+ all: "najm-border border-sidebar-border",
95
+ top: "najm-border-t border-sidebar-border",
96
+ right: "najm-border-r border-sidebar-border",
97
+ bottom: "najm-border-b border-sidebar-border",
98
+ left: "najm-border-l border-sidebar-border"
99
+ };
100
+ var BORDER_RESET_CLASSES = {
101
+ all: "border-0",
102
+ top: "border-t-0",
103
+ right: "border-r-0",
104
+ bottom: "border-b-0",
105
+ left: "border-l-0"
106
+ };
107
+ function surfaceBorderClasses(bordered = true, side = "all") {
108
+ return bordered === false ? BORDER_RESET_CLASSES[side] : SURFACE_BORDER_CLASSES[side];
109
+ }
110
+ function sidebarBorderClasses(bordered = true, side = "all") {
111
+ return bordered === false ? BORDER_RESET_CLASSES[side] : SIDEBAR_BORDER_CLASSES[side];
112
+ }
113
+ function inputBorderClasses(bordered = true) {
114
+ return bordered === false ? "border-0" : "najm-border border-input";
115
+ }
116
+ var buttonVariants = cva(
117
+ [
118
+ "relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap text-sm font-medium outline-none transition-all",
119
+ "disabled:pointer-events-none disabled:opacity-50 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50",
120
+ "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
121
+ "aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
122
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
123
+ ].join(" "),
124
+ {
125
+ variants: {
126
+ variant: {
127
+ default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
128
+ destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
129
+ outline: "border border-input bg-transparent text-foreground shadow-xs hover:bg-accent hover:text-accent-foreground",
130
+ secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
131
+ tertiary: "bg-tertiary text-tertiary-foreground shadow-xs hover:bg-tertiary/80",
132
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
133
+ link: "h-auto p-0 text-primary underline-offset-4 hover:underline",
134
+ success: "bg-emerald-600 text-white shadow-xs hover:bg-emerald-700",
135
+ warning: "bg-amber-400 text-slate-950 shadow-xs hover:bg-amber-500",
136
+ info: "bg-sky-600 text-white shadow-xs hover:bg-sky-700",
137
+ soft: "bg-primary/10 text-primary shadow-none hover:bg-primary/15",
138
+ subtle: "bg-muted text-foreground shadow-none hover:bg-muted/80",
139
+ plain: "bg-transparent shadow-none hover:bg-transparent"
140
+ },
141
+ size: {
142
+ "2xs": "h-5 gap-1 px-1.5 text-[11px] [&_svg:not([class*='size-'])]:size-3",
143
+ xs: "h-6 gap-1 px-2 text-xs [&_svg:not([class*='size-'])]:size-3",
144
+ sm: "h-8 gap-1.5 px-3 has-[>svg]:px-2.5",
145
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
146
+ md: "h-9 px-4 py-2 has-[>svg]:px-3",
147
+ lg: "h-10 px-6 has-[>svg]:px-4",
148
+ xl: "h-12 px-8 text-base [&_svg:not([class*='size-'])]:size-5",
149
+ "2xl": "h-14 px-10 text-base [&_svg:not([class*='size-'])]:size-5",
150
+ icon: "size-9",
151
+ "icon-xs": "size-6 [&_svg:not([class*='size-'])]:size-3",
152
+ "icon-sm": "size-8",
153
+ "icon-lg": "size-10 [&_svg:not([class*='size-'])]:size-5",
154
+ "icon-xl": "size-12 [&_svg:not([class*='size-'])]:size-5"
155
+ },
156
+ rounded: {
157
+ none: "rounded-none",
158
+ sm: "rounded-sm",
159
+ default: "rounded-md",
160
+ md: "rounded-md",
161
+ lg: "rounded-lg",
162
+ xl: "rounded-xl",
163
+ "2xl": "rounded-2xl",
164
+ full: "rounded-full"
165
+ },
166
+ fullWidth: {
167
+ true: "w-full",
168
+ false: ""
169
+ }
170
+ },
171
+ defaultVariants: {
172
+ variant: "default",
173
+ size: "default",
174
+ rounded: "default",
175
+ fullWidth: false
176
+ }
177
+ }
178
+ );
179
+ function isPromiseLike(value) {
180
+ return Boolean(value && typeof value.then === "function");
181
+ }
182
+ var defaultLoader = /* @__PURE__ */ jsx(LoaderCircleIcon, { "aria-hidden": "true", className: "animate-spin" });
183
+ function renderIcon(icon) {
184
+ if (!icon) return null;
185
+ return /* @__PURE__ */ jsx(NIcon, { "aria-hidden": "true", icon, className: "shrink-0" });
186
+ }
187
+ var Button = React2.forwardRef(
188
+ ({
189
+ className,
190
+ variant,
191
+ size,
192
+ rounded,
193
+ fullWidth,
194
+ asChild = false,
195
+ loading = false,
196
+ autoLoading = true,
197
+ disabledWhileLoading = true,
198
+ loadingText,
199
+ loader = defaultLoader,
200
+ loaderPosition = "left",
201
+ leftIcon,
202
+ rightIcon,
203
+ bordered,
204
+ disabled,
205
+ children,
206
+ onClick,
207
+ style,
208
+ ...props
209
+ }, ref) => {
210
+ const recipe = useNajmComponentStyle("button");
211
+ const aliased = resolveVariantAlias(
212
+ recipe?.variants,
213
+ variant ?? recipe?.defaultVariant ?? "default"
214
+ );
215
+ const effVariant = variant ?? aliased.variant ?? recipe?.defaultVariant;
216
+ const effSize = size ?? recipe?.defaultSize;
217
+ const recipeRadius = rounded === void 0 ? resolveRadiusValue(recipe?.radius) : void 0;
218
+ const recipeStyle = recipeRadius ? { borderRadius: recipeRadius } : void 0;
219
+ const [pending, setPending] = React2.useState(false);
220
+ const isLoading = loading || pending;
221
+ const isDisabled = disabled || disabledWhileLoading && isLoading;
222
+ const Comp = asChild ? Slot : "button";
223
+ const handleClick = React2.useCallback(
224
+ (event) => {
225
+ if (isDisabled) {
226
+ event.preventDefault();
227
+ return;
228
+ }
229
+ const result = onClick?.(event);
230
+ if (autoLoading && isPromiseLike(result)) {
231
+ setPending(true);
232
+ void result.then(
233
+ () => setPending(false),
234
+ () => setPending(false)
235
+ );
236
+ }
237
+ },
238
+ [autoLoading, isDisabled, onClick]
239
+ );
240
+ const child = asChild ? React2.Children.only(children) : null;
241
+ const childContent = asChild && React2.isValidElement(child) ? child.props.children : children;
242
+ const content = loadingText && isLoading ? loadingText : childContent;
243
+ const loaderNode = isLoading ? loader : null;
244
+ const leftIconNode = renderIcon(leftIcon);
245
+ const rightIconNode = renderIcon(rightIcon);
246
+ const showCenteredLoader = Boolean(loaderNode && loaderPosition === "center");
247
+ const renderedContent = showCenteredLoader ? /* @__PURE__ */ jsxs(Fragment, { children: [
248
+ /* @__PURE__ */ jsxs("span", { className: "invisible inline-flex items-center gap-2", children: [
249
+ leftIconNode,
250
+ content,
251
+ rightIconNode
252
+ ] }),
253
+ /* @__PURE__ */ jsx("span", { className: "absolute inset-0 inline-flex items-center justify-center", children: loaderNode })
254
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
255
+ loaderNode && loaderPosition === "left" ? loaderNode : leftIconNode,
256
+ content,
257
+ loaderNode && loaderPosition === "right" ? loaderNode : rightIconNode
258
+ ] });
259
+ const renderedChild = asChild && React2.isValidElement(child) ? React2.cloneElement(child, void 0, renderedContent) : renderedContent;
260
+ return /* @__PURE__ */ jsx(
261
+ Comp,
262
+ {
263
+ ref,
264
+ "data-slot": "button",
265
+ "data-loading": isLoading || void 0,
266
+ "data-disabled": isDisabled || void 0,
267
+ "data-bordered": effVariant === "outline" ? "true" : bordered === false ? "false" : bordered ? "true" : void 0,
268
+ "aria-busy": isLoading || void 0,
269
+ "aria-disabled": asChild && isDisabled ? true : void 0,
270
+ disabled: !asChild ? isDisabled : void 0,
271
+ style: recipeStyle ? { ...recipeStyle, ...style } : style,
272
+ className: cn(
273
+ buttonVariants({ variant: effVariant, size: effSize, rounded, fullWidth }),
274
+ aliased.style?.className,
275
+ // Outline buttons always carry an input-style border.
276
+ effVariant === "outline" && inputBorderClasses(true),
277
+ // Filled/ghost/plain/link/success/warning/info/soft/subtle buttons only get a
278
+ // border when the consumer explicitly opts in via `bordered`. Global theming
279
+ // should not outline every filled button.
280
+ effVariant !== "outline" && bordered === true && cn(
281
+ "border",
282
+ "border-muted-foreground"
283
+ ),
284
+ className
285
+ ),
286
+ onClick: handleClick,
287
+ ...props,
288
+ children: renderedChild
289
+ }
290
+ );
291
+ }
292
+ );
293
+ Button.displayName = "Button";
294
+ var NButton = Button;
295
+ function assignRef(ref, node) {
296
+ if (!ref) return;
297
+ if (typeof ref === "function") ref(node);
298
+ else ref.current = node;
299
+ }
300
+ function applyViewportLayout(node, axis) {
301
+ node.style.width = "100%";
302
+ node.style.height = "100%";
303
+ node.style.minWidth = "0";
304
+ node.style.minHeight = "0";
305
+ node.style.overflowX = axis === "x" || axis === "both" ? "auto" : "hidden";
306
+ node.style.overflowY = axis === "y" || axis === "both" ? "auto" : "hidden";
307
+ }
308
+ function najmScrollOptions(axis, autoHide, options) {
309
+ return {
310
+ scrollbars: { theme: "os-theme-najm", autoHide, autoHideDelay: 500, clickScroll: true },
311
+ overflow: {
312
+ x: axis === "x" || axis === "both" ? "scroll" : "hidden",
313
+ y: axis === "y" || axis === "both" ? "scroll" : "hidden"
314
+ },
315
+ ...options
316
+ };
317
+ }
318
+ function useNajmScrollViewport({
319
+ axis = "y",
320
+ autoHide = "never",
321
+ options
322
+ } = {}) {
323
+ const hostRef = React2.useRef(null);
324
+ const viewportRef = React2.useRef(null);
325
+ React2.useEffect(() => {
326
+ const target = hostRef.current;
327
+ const viewport = viewportRef.current;
328
+ if (!target || !viewport) return;
329
+ const instance = OverlayScrollbars(
330
+ { target, elements: { viewport } },
331
+ najmScrollOptions(axis, autoHide, options)
332
+ );
333
+ const containWheel = (event) => event.stopPropagation();
334
+ viewport.addEventListener("wheel", containWheel, { passive: true });
335
+ return () => {
336
+ viewport.removeEventListener("wheel", containWheel);
337
+ instance.destroy();
338
+ };
339
+ }, [axis, autoHide, options]);
340
+ return { hostRef, viewportRef };
341
+ }
342
+ function NajmScroll({ className, axis = "y", autoHide = "never", viewportRef, events, options, element, children, style, ...props }) {
343
+ return /* @__PURE__ */ jsx(
344
+ OverlayScrollbarsComponent,
345
+ {
346
+ className: cn(className),
347
+ style: {
348
+ ...style,
349
+ overflow: "hidden",
350
+ minHeight: 0,
351
+ minWidth: 0
352
+ },
353
+ element,
354
+ defer: true,
355
+ options: najmScrollOptions(axis, autoHide, options || void 0),
356
+ events: {
357
+ ...events,
358
+ initialized: (instance, ...rest) => {
359
+ const viewport = instance.elements().viewport;
360
+ applyViewportLayout(viewport, axis);
361
+ assignRef(viewportRef, viewport);
362
+ events?.initialized?.(instance, ...rest);
363
+ },
364
+ updated: (instance, ...rest) => {
365
+ applyViewportLayout(instance.elements().viewport, axis);
366
+ events?.updated?.(instance, ...rest);
367
+ },
368
+ destroyed: (instance, ...rest) => {
369
+ assignRef(viewportRef, null);
370
+ events?.destroyed?.(instance, ...rest);
371
+ }
372
+ },
373
+ ...props,
374
+ children
375
+ }
376
+ );
377
+ }
378
+ var TableStoreContext = createContext(null);
379
+ var useTableStore = { use: {} };
380
+ var handler = {
381
+ get: (_, prop) => () => {
382
+ const store = useContext(TableStoreContext);
383
+ if (!store) throw new Error("useTableStore must be used within NTable");
384
+ return store.use[prop]();
385
+ }
386
+ };
387
+ useTableStore.use = new Proxy({}, handler);
388
+ function formatJsonValue(value) {
389
+ if (typeof value === "string") return value;
390
+ try {
391
+ return JSON.stringify(value ?? null, null, 2) ?? String(value);
392
+ } catch {
393
+ return String(value);
394
+ }
395
+ }
396
+ function NTableJson() {
397
+ const viewMode = useTableStore.use.viewMode();
398
+ const renderJson = useTableStore.use.renderJson();
399
+ const jsonValue = useTableStore.use.jsonValue();
400
+ if (viewMode !== "json") return null;
401
+ return /* @__PURE__ */ jsx("div", { className: "flex-1 flex flex-col min-h-0 overflow-hidden", children: renderJson?.() ?? /* @__PURE__ */ jsx(NajmScroll, { axis: "both", className: "h-full min-h-0 rounded-md border border-border bg-muted/40", children: /* @__PURE__ */ jsx("pre", { className: "p-4 font-mono text-xs leading-relaxed text-foreground", children: formatJsonValue(jsonValue) }) }) });
402
+ }
403
+
404
+ export { Button, NButton, NIcon, NTableJson, NajmScroll, TableStoreContext, buttonVariants, inputBorderClasses, sidebarBorderClasses, surfaceBorderClasses, useNajmScrollViewport, useTableStore };