najm-kit 2.1.47 → 2.1.48

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 ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ ## 2.1.48 - 2026-08-04
4
+
5
+ - Keep responsive card row actions visible on phone, tablet, and touch input,
6
+ while retaining hover and keyboard-focus reveal on fine-pointer desktops.
7
+ - Size table and card loading skeletons from the measured body and active grid,
8
+ and keep loading borders, radius, color, and shadow aligned with loaded
9
+ surfaces.
10
+ - Add the exported `NTableCardPagination` and `NTableLoadMorePagination`
11
+ contracts for paged, complete supplied-data, and explicit server-backed Load
12
+ more card presentation, including guarded append/retry behavior and accessible
13
+ loading, result, error, and terminal feedback.
package/README.md CHANGED
@@ -211,8 +211,74 @@ Notes:
211
211
  - The columns the TanStack table receives are already filtered, so the
212
212
  settings menu will not list `visible: false` columns.
213
213
 
214
- If you need to inspect or build your own effective column list, the same
215
- pure helper is exported as `filterResponsiveColumns`. The literal class
216
- map is also exported as `hiddenBelowClasses`, and
217
- `resolveHiddenBelowClass(breakpoint)` returns the class for a single
218
- breakpoint or `undefined` when no breakpoint is set.
214
+ If you need to inspect or build your own effective column list, the same
215
+ pure helper is exported as `filterResponsiveColumns`. The literal class
216
+ map is also exported as `hiddenBelowClasses`, and
217
+ `resolveHiddenBelowClass(breakpoint)` returns the class for a single
218
+ breakpoint or `undefined` when no breakpoint is set.
219
+
220
+ ## NTable responsive cards, loading, and pagination
221
+
222
+ Responsive row actions are visible by default on phone, tablet, and coarse or
223
+ non-hover pointers. Fine-pointer desktop layouts may reveal them on hover, but
224
+ keyboard focus always reveals the action. Applications still decide which menu
225
+ items exist through `menu`, `onView`, `onEdit`, and `onDelete`; visibility does
226
+ not grant an action or replace server authorization.
227
+
228
+ When `dynamicHeight` is enabled, table and card loading skeletons measure the
229
+ available body. Table rows use the same header/row geometry as dynamic page
230
+ sizing, while cards measure the active grid columns, card height, and gap. The
231
+ loading surface also follows the loaded `bordered`, design recipe, radius,
232
+ border color, shadow, and `classNames.content`/`classNames.cards` contract.
233
+
234
+ Use `cardPagination` to choose pagination presentation whenever the effective
235
+ rendered mode is cards:
236
+
237
+ - `{ mode: "paged" }` (the default) preserves existing pagination.
238
+ - `{ mode: "all" }` renders every row already supplied and hides the footer.
239
+ - `{ mode: "load-more", ... }` renders every supplied row and provides a
240
+ guarded, keyboard-operable Load more/Retry control with polite loading,
241
+ appended-result, and end-of-list announcements.
242
+
243
+ `showPagination={false}` remains an absolute presentation override and hides
244
+ both numbered controls and Load more. In table mode, existing controlled and
245
+ manual server pagination remains unchanged.
246
+
247
+ ```tsx
248
+ import { NTable, type NTableCardPagination } from "najm-kit";
249
+
250
+ const cardPagination: NTableCardPagination = {
251
+ mode: "load-more",
252
+ hasNextPage: query.hasNextPage,
253
+ loadingMore: query.isFetchingNextPage,
254
+ loadMoreError: query.isFetchNextPageError
255
+ ? "The next page could not be loaded."
256
+ : undefined,
257
+ onLoadMore: () => query.fetchNextPage(),
258
+ loadMoreLabel: "Load more",
259
+ loadingMoreLabel: "Loading more...",
260
+ retryLabel: "Retry",
261
+ endLabel: "No more results.",
262
+ };
263
+
264
+ <NTable
265
+ data={query.data?.pages.flatMap((page) => page.rows) ?? []}
266
+ columns={columns}
267
+ getRowId={(row) => row.id}
268
+ renderCard={ResultCard}
269
+ cardPagination={cardPagination}
270
+ />
271
+ ```
272
+
273
+ The application owns the query, cursor/offset, accumulated pages, cache
274
+ invalidation, search/filter/sort semantics, authorization, and privacy
275
+ projection. Najm Kit never imports React Query, calls an endpoint, invents a
276
+ page size, or treats supplied rows as proof that every database row is loaded.
277
+ Client sorting and filtering cover the rows currently supplied unless the
278
+ application implements matching server-side behavior.
279
+
280
+ For a responsive screen that uses current-page data in desktop table mode and
281
+ accumulated pages in card mode, keep those two query shapes in the application
282
+ and pass the appropriate `data`. Crossing the `<640px` responsive-card
283
+ breakpoint does not overwrite the user's chosen view, pagination position,
284
+ sorting, filters, expansion, or row selection.
package/dist/index.d.ts CHANGED
@@ -2671,6 +2671,39 @@ type StepSubmitResult = {
2671
2671
  };
2672
2672
  declare function useFormSubmission({ steps, schema, defaultValues, onSubmit, currentStep, isLastStep, handleNext, markStepCompleted, reset, }: UseFormSubmissionOptions): FormSubmissionState;
2673
2673
 
2674
+ interface NTableLoadMorePagination {
2675
+ /** Render the supplied rows as one card list with an explicit continuation control. */
2676
+ mode: "load-more";
2677
+ /** Whether the owning application has another server page available. */
2678
+ hasNextPage: boolean;
2679
+ /** True while the owning application is appending the next page. */
2680
+ loadingMore?: boolean;
2681
+ /** A controlled append error. Existing rows remain rendered and the control becomes Retry. */
2682
+ loadMoreError?: ReactNode;
2683
+ /** Fetch exactly one additional page. Najm Kit never constructs or owns the request. */
2684
+ onLoadMore: () => unknown | Promise<unknown>;
2685
+ loadMoreLabel?: string;
2686
+ loadingMoreLabel?: string;
2687
+ retryLabel?: string;
2688
+ endLabel?: string;
2689
+ loadMoreErrorLabel?: string;
2690
+ /** Localize the polite announcement made after appended rows arrive. */
2691
+ itemsLoadedLabel?: (count: number) => string;
2692
+ }
2693
+ /**
2694
+ * Presentation policy used while NTable is actually rendering cards.
2695
+ *
2696
+ * `paged` preserves the existing page controls. `all` renders every supplied
2697
+ * row without a footer. `load-more` also renders every supplied row and adds a
2698
+ * guarded, accessible continuation control. Applications remain responsible
2699
+ * for fetching, accumulating, filtering, sorting, authorization, and privacy.
2700
+ */
2701
+ type NTableCardPagination = {
2702
+ mode?: "paged";
2703
+ } | {
2704
+ mode: "all";
2705
+ } | NTableLoadMorePagination;
2706
+
2674
2707
  interface NTableClassNames {
2675
2708
  root?: string;
2676
2709
  header?: string;
@@ -2742,7 +2775,14 @@ interface TableState {
2742
2775
  addButtonText: string;
2743
2776
  pageSizeOptions: number[];
2744
2777
  calculatedPageSize: number;
2778
+ skeletonRowCount: number;
2745
2779
  maxHeight: number | null;
2780
+ bodyWidth: number;
2781
+ bodyHeight: number;
2782
+ tableHeaderHeight: number;
2783
+ cardColumnCount: number;
2784
+ cardRowHeight: number;
2785
+ cardGap: number;
2746
2786
  syncWithProps: (updates: Partial<TableState>) => void;
2747
2787
  jsonValue: unknown;
2748
2788
  jsonColors: any;
@@ -2782,6 +2822,8 @@ interface TableState {
2782
2822
  hasSyncedSortingFromProps: boolean;
2783
2823
  responsiveCards: boolean;
2784
2824
  isMobile: boolean;
2825
+ effectiveViewMode: ViewMode;
2826
+ cardPagination: NTableCardPagination;
2785
2827
  isEmpty: boolean | undefined;
2786
2828
  isFilteredEmpty: boolean;
2787
2829
  renderFilteredEmpty: (() => React.ReactNode) | null;
@@ -2858,7 +2900,14 @@ declare const createTableStore: () => {
2858
2900
  addButtonText: () => string;
2859
2901
  pageSizeOptions: () => number[];
2860
2902
  calculatedPageSize: () => number;
2903
+ skeletonRowCount: () => number;
2861
2904
  maxHeight: () => number;
2905
+ bodyWidth: () => number;
2906
+ bodyHeight: () => number;
2907
+ tableHeaderHeight: () => number;
2908
+ cardColumnCount: () => number;
2909
+ cardRowHeight: () => number;
2910
+ cardGap: () => number;
2862
2911
  syncWithProps: () => (updates: Partial<TableState>) => void;
2863
2912
  jsonValue: () => unknown;
2864
2913
  jsonColors: () => any;
@@ -2898,6 +2947,8 @@ declare const createTableStore: () => {
2898
2947
  hasSyncedSortingFromProps: () => boolean;
2899
2948
  responsiveCards: () => boolean;
2900
2949
  isMobile: () => boolean;
2950
+ effectiveViewMode: () => ViewMode;
2951
+ cardPagination: () => NTableCardPagination;
2901
2952
  isEmpty: () => boolean;
2902
2953
  isFilteredEmpty: () => boolean;
2903
2954
  renderFilteredEmpty: () => () => React.ReactNode;
@@ -3030,6 +3081,8 @@ interface NTableProps<T = any, M extends ViewMode = ViewMode> {
3030
3081
  pageIndex: number;
3031
3082
  pageSize: number;
3032
3083
  }) => void;
3084
+ /** Pagination presentation used whenever NTable is actually rendering cards. */
3085
+ cardPagination?: NTableCardPagination;
3033
3086
  rowSelection?: RowSelectionState;
3034
3087
  defaultRowSelection?: RowSelectionState;
3035
3088
  onRowSelectionChange?: (state: RowSelectionState) => void;
@@ -3125,8 +3178,9 @@ interface NDataCardShellProps {
3125
3178
  openRowMenu?: ((e: React__default.MouseEvent, row: any) => void) | null;
3126
3179
  menuButton?: boolean;
3127
3180
  bordered?: boolean;
3181
+ borderColor?: string;
3128
3182
  }
3129
- declare function NDataCardShell({ row, onClick, onContextMenu, actions, children, className, showCheckbox, selectedRowId, openRowMenu, menuButton, bordered }: NDataCardShellProps): react_jsx_runtime.JSX.Element;
3183
+ declare function NDataCardShell({ row, onClick, onContextMenu, actions, children, className, showCheckbox, selectedRowId, openRowMenu, menuButton, bordered, borderColor }: NDataCardShellProps): react_jsx_runtime.JSX.Element;
3130
3184
 
3131
3185
  interface NTableCardRootProps extends Omit<React__default.HTMLAttributes<HTMLDivElement>, "onClick" | "onContextMenu"> {
3132
3186
  card: {
@@ -3264,7 +3318,14 @@ declare const TableStoreContext: React$1.Context<{
3264
3318
  addButtonText: () => string;
3265
3319
  pageSizeOptions: () => number[];
3266
3320
  calculatedPageSize: () => number;
3321
+ skeletonRowCount: () => number;
3267
3322
  maxHeight: () => number;
3323
+ bodyWidth: () => number;
3324
+ bodyHeight: () => number;
3325
+ tableHeaderHeight: () => number;
3326
+ cardColumnCount: () => number;
3327
+ cardRowHeight: () => number;
3328
+ cardGap: () => number;
3268
3329
  syncWithProps: () => (updates: Partial<TableState>) => void;
3269
3330
  jsonValue: () => unknown;
3270
3331
  jsonColors: () => any;
@@ -3304,6 +3365,8 @@ declare const TableStoreContext: React$1.Context<{
3304
3365
  hasSyncedSortingFromProps: () => boolean;
3305
3366
  responsiveCards: () => boolean;
3306
3367
  isMobile: () => boolean;
3368
+ effectiveViewMode: () => ViewMode;
3369
+ cardPagination: () => NTableCardPagination;
3307
3370
  isEmpty: () => boolean;
3308
3371
  isFilteredEmpty: () => boolean;
3309
3372
  renderFilteredEmpty: () => () => React.ReactNode;
@@ -3384,7 +3447,14 @@ declare function useStoreSync(props: any): {
3384
3447
  addButtonText: () => string;
3385
3448
  pageSizeOptions: () => number[];
3386
3449
  calculatedPageSize: () => number;
3450
+ skeletonRowCount: () => number;
3387
3451
  maxHeight: () => number;
3452
+ bodyWidth: () => number;
3453
+ bodyHeight: () => number;
3454
+ tableHeaderHeight: () => number;
3455
+ cardColumnCount: () => number;
3456
+ cardRowHeight: () => number;
3457
+ cardGap: () => number;
3388
3458
  syncWithProps: () => (updates: Partial<TableState>) => void;
3389
3459
  jsonValue: () => unknown;
3390
3460
  jsonColors: () => any;
@@ -3424,6 +3494,8 @@ declare function useStoreSync(props: any): {
3424
3494
  hasSyncedSortingFromProps: () => boolean;
3425
3495
  responsiveCards: () => boolean;
3426
3496
  isMobile: () => boolean;
3497
+ effectiveViewMode: () => ViewMode;
3498
+ cardPagination: () => NTableCardPagination;
3427
3499
  isEmpty: () => boolean;
3428
3500
  isFilteredEmpty: () => boolean;
3429
3501
  renderFilteredEmpty: () => () => React__default.ReactNode;
@@ -3436,8 +3508,8 @@ declare function useStoreSync(props: any): {
3436
3508
  hasSyncedExpandedFromProps: () => boolean;
3437
3509
  };
3438
3510
  };
3439
- declare function useDynamicPageSize(containerRef: React__default.RefObject<HTMLDivElement | null>): void;
3440
- declare function useTable(): {
3511
+ declare function useDynamicPageSize(containerRef: React__default.RefObject<HTMLDivElement | null>, effectiveViewMode?: TableState["viewMode"]): void;
3512
+ declare function useTable(effectiveViewModeOverride?: TableState["viewMode"]): {
3441
3513
  table: _tanstack_table_core.Table<unknown>;
3442
3514
  finalColumns: _tanstack_table_core.ColumnDef<unknown, unknown>[];
3443
3515
  sorting: SortingState;
@@ -3833,4 +3905,4 @@ interface NGridItemProps extends Omit<React$1.AllHTMLAttributes<HTMLDivElement>,
3833
3905
  declare function NGrid({ as: Comp, children, cols, smCols, mdCols, lgCols, xlCols, gap, className, style, ...props }: NGridProps): react_jsx_runtime.JSX.Element;
3834
3906
  declare function NGridItem({ children, span, smSpan, mdSpan, lgSpan, xlSpan, className, ...props }: NGridItemProps): react_jsx_runtime.JSX.Element;
3835
3907
 
3836
- export { Alert, type AlertLook, type AlertOrientation, type AlertProps, type AlertSize, type AlertTone, type AlertVariant, NCard as AsyncCard, Avatar, AvatarFallback, AvatarFormInput, type AvatarFormInputProps, AvatarGroup, type AvatarGroupProps, AvatarImage, AvatarInput, type AvatarInputProps, type AvatarInputRadius, type AvatarProps$1 as AvatarProps, type AvatarShape$1 as AvatarShape, type AvatarSize, AvatarStatus, type AvatarStatusType, Badge, type BadgeColor, type BadgeIcon, type BadgeLook, type BadgeProps, type BadgeShape, type BadgeSize, type BadgeVariant, BaseInput, type BuildDefaultFileColumnsOptions, Button, type ButtonConfig, type ButtonIcon, type ButtonLoaderPosition, type ButtonProps, type ButtonRounded, type ButtonSize, type ButtonVariant, Calendar, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, CheckboxGroupInput, type CheckboxGroupInputProps, CheckboxInput, type CheckboxInputProps, Collapsible, CollapsibleContent, CollapsibleTrigger, ColorArrayInput, type ColorArrayInputProps, type ColorFormat, ColorPickerInput, type ColorPickerInputProps, Combobox, ComboboxInput, type ComboboxInputProps, type ComboboxOption, type ComboboxProps, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, type ContextMenuItem, DEFAULT_THEME_FILE_NAME, DateInput, type DateInputProps, type DeleteDialogOptions, Dialog, type DialogActionMode, type DialogApi, DialogClose, type DialogConfig, DialogContent, type DialogContentProps, DialogDescription, DialogFooter, DialogHeader, type DialogHeight, DialogOverlay, type DialogPadding, DialogPortal, type DialogRenderContext, type DialogRenderer, type DialogSize, type DialogStore, DialogTitle, DialogTrigger, type DialogVariant, type DialogWidth, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, DynamicArray, type DynamicArrayProps, EmojiInput, type EmojiInputProps, type FileBrowserMode, FileImportButton, FileInput, type FileInputProps, type FileNode, Form, FormControl, FormDescription, FormField, FormInput, type FormInputBackground, type FormInputProps, FormItem, FormLabel, FormMessage, type FormProps, type FormSlotClassNames, type FormVariant, IconButton, type IconButtonProps, type IconButtonSize, type IconButtonVariant, ImageInput, type ImageInputProps, Indicator, type IndicatorHorizontal, type IndicatorOverlay, type IndicatorPosition, type IndicatorProps, type IndicatorResponsivePosition, type IndicatorSize, type IndicatorVertical, Input, type InputIcon, Label, LangInput, type LangInputProps, type LinkComponentType, MultiSelectInput, type MultiSelectInputProps, NAJM_COMPONENT_NAMES, NAlert, type NAppCommandItem, NAppShell, type NAppShellAction, type NAppShellClassNames, type NAppShellProps, type NAppShellUser, NCard as NAsyncCard, type CardClassNames as NAsyncCardClassNames, type CardProps as NAsyncCardProps, NAvatar, type NAvatarClassNames, type NAvatarProps, type AvatarShape as NAvatarShape, NBadge, type NBadgeLook, type NBadgeProps, type NBulkAction, type NBulkActionButton, type NBulkActionSelect, NBulkActionsBar, type NBulkActionsBarProps, NButton, type NButtonProps, NCard, NCardAction, type CardClassNames as NCardClassNames, type NCardDensity, NCardFooter, NCardInfo, type NCardInfoProps, NCardMedia, type NCardMediaAspect, type NCardMediaPlacement, type NCardMediaProps, type NCardMediaSize, type NCardMediaVariant, type CardProps as NCardProps, NCardSection, type NCardSectionProps, type NCardSectionSurface, NCommandPalette, type NCommandPaletteProps, NConfirmDialog, type NConfirmDialogProps, NContextMenu, type NContextMenuItem, type NContextMenuProps, NDataCardShell, type NDataCardShellActions, type NDataCardShellProps, NDeleteDialog, NDeleteDialogContent, type NDeleteDialogContentProps, type NDeleteDialogProps, NDetailCard, type NDetailCardClassNames, type NDetailCardProps, NDetailItem, type NDetailItemProps, NDetailList, type NDetailListItem, type NDetailListProps, NDialog, type NDialogActionProps, NDialogDescription, type NDialogDescriptionProps, type NDialogDirectProps, NDialogHeader, type NDialogHeaderProps, NDialogPrimaryButton, type NDialogProps, NDialogSecondaryButton, NDonutCard, type NDonutCardClassNames, type NDonutCardItem, type NDonutCardLayout, type NDonutCardLegendMarker, type NDonutCardProps, type NDonutCardVariant, type NEditorTab, NEditorTabs, type NEditorTabsProps, NEmptyState, type NEmptyStateProps, NErrorBoundary, NErrorState, type NErrorStateProps, NFileBrowser, type NFileBrowserCardProps, type NFileBrowserProps, type NFileBrowserRenderThumbProps, NFileTypeIcon, type NFileTypeIconProps, NFilterBar, NFolderIcon, type NFolderIconProps, NForm, NFormSectionHeader, type NFormSectionHeaderProps, NGrid, type NGridCols, NGridItem, type NGridItemProps, type NGridProps, type NGridSpan, NIcon, type NIconProps, type NIconSource, NIndicator, NInspectorSheet, NLoadingState, type NLoadingStateProps, NMultiDialog, type NMultiDialogProps, NNavbar, NPageHeader, NPageHeaderActions, type NPageHeaderBreakpoint, NPageHeaderCompactActions, NPageHeaderFilters, type NPageHeaderProps, NPageHeaderTop, NPageLayout, type NPageLayoutProps, NPortalScopeProvider, NProgress, type NProgressProps, NRowActions, NSection, NSectionHeader, type NSectionHeaderActionsProps, type NSectionHeaderContentProps, type NSectionHeaderProps, type NSectionHeaderSubtitleProps, type NSectionHeaderTitleProps, NSectionInfo, type NSectionInfoProps, type NSectionProps, NSectionWithInfo, type NSectionWithInfoItem, type NSectionWithInfoProps, NSheet, type NSheetClassNames, type NSheetProps, NSidebar, NSidebarContent, type NSidebarContentProps, NSidebarFooter, type NSidebarFooterProps, NSidebarHeader, type NSidebarHeaderProps, NSidebarItem, NSidebarLogo, type NSidebarLogoProps, NSidebarMobile, type NSidebarMobileProps, NSidebarSection, type NSidebarSectionProps, NSkeleton, NSkeletonCalendar, NSkeletonChart, NSkeletonDonut, NSkeletonEventList, NSkeletonWidget, NSkeletonWidgets, NSlider, type NSliderProps, NSmartPasteDialog, type NSmartPasteDialogProps, NSpinner, type NSpinnerProps, NStatCard, type NStatCardClassNames, type NStatCardProps, NStatCardSkeleton, type NStatCardVariant, Swap as NSwap, type NSwapProps, NTable, NTableCardRoot, type NTableCardRootProps, NTableCards, type NTableClassNames, type NTableColumnBreakpoint, type NTableColumnDef, type NTableColumnMeta, NTableContent, NTableHeader, NTableLoadingSkeleton, type NTableMenu, type NTableMenuProp, NTablePagination, type NTableProps, NTableRowSkeleton, NTableSkeleton, type NTableState, NTabs, type NTabsClassNames, type NTabsColor, type NTabsItem, type NTabsProps, type NTabsStyles, NThemeCustomizer, type NThemeCustomizerFontOption, type NThemeCustomizerLabels, type NThemeCustomizerProps, type NThemeCustomizerTab, NUploader, type NUploaderItem, type NUploaderItemStatus, type NUploaderProps, NViewBody, NViewToggle, type NajmAccent, type NajmAppearance, type NajmBorderSide, type NajmComponentName, type NajmComponentRadius, type NajmComponentStyleConfig, type NajmComponentThemeConfig, type NajmDensity, type NajmDesignConfig, NajmDesignProvider, type NajmDesignProviderProps, type NajmLayoutConfig, type NajmMode, type NajmPreset, type NajmResponsiveBreakpoint, type NajmResponsiveValue, NajmScroll, type NajmScrollProps, type NajmSlotStyle, type NajmThemeConfig, NajmThemeProvider, type NajmThemeProviderProps, type NajmThemeTokens, type NajmTypographyConfig, type NajmVariantStyle, NativeSelect, type NativeSelectOption, type NativeSelectProps, type NavItem, type NavItemGroup, NumberInput, type NumberInputProps, OtpInput, type OtpInputProps, PasswordInput, type PasswordInputProps, PhoneInput, type PhoneInputProps, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, PrefixProvider, Progress, type ProgressColor, type ProgressLabelPosition, type ProgressProps, type ProgressSize, type PushDialogOptions, RADIUS_VALUE_MAP, RadioGroup, RadioGroupInput, type RadioGroupInputProps, RadioGroupItem, type RenderSlot, RepeatingFields, type RepeatingFieldsProps, ScrollArea, type ScrollAreaProps, SearchField, SearchField as SearchInput, SegmentedControl, type SegmentedControlOption, type SegmentedControlProps, Select, SelectContent, SelectGroup, SelectInput, type SelectInputProps, SelectItem, type SelectItemType$1 as SelectItemDataType, type SelectItemType, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, type SidebarItemProps, type SidebarProps, type SidebarWidth, type SidebarWidths, SimpleTooltip, type SimpleTooltipProps, NSkeleton as Skeleton, Slider, SliderInput, type SliderInputProps, type SliderOrientation, type SliderProps, type SliderSize, type SliderVariant, type SmartPastePreview, type SpinnerVariant, StarRatingInput, type StarRatingInputProps, StatusPill, type StatusPillProps, type StatusPillTone, type StepConfig, StepIndicator, type StepMeta, StepsHeader, StepsProgress, type StorageMenuAction, type StorageSortOption, type StorageTarget, Swap, type SwapEffect, SwapIndeterminate, SwapOff, SwapOn, type SwapProps, type SwapSize, type SwapState, Switch, type SwitchColor, SwitchInput, type SwitchInputProps, type SwitchSize, TAB_COLORS, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, type TableHeaderColor, TableRow, type TableState, type TableStore, TableStoreContext, Tabs, TabsContent, TabsList, type TabsListProps, type TabsOrientation, type TabsProps, TabsTrigger, type TabsTriggerProps, type TabsVariant, TextAreaInput, type TextAreaInputProps, TextInput, type TextInputProps, Textarea, TimeInput, type TimeInputProps, TimeZoneInput, type TimeZoneInputProps, Toaster, Toggle, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, type UseContextMenuResult, type UseNFormOptions, type UseStorageContextMenuOptions, type UseStorageContextMenuResult, type UserMenuAction, VariantProvider, type WizardClassNames, WizardForm, type WizardFormProps, alertVariants, avatarVariants, badgeColorVariants, badgeVariants, buildDefaultFileColumns, buttonVariants, cn, composePreset, createDialogStore, createTableStore, defineNajmDesignConfig, defineNajmThemeConfig, detectFormat, dialogVariants, filterResponsiveColumns, formatColor, formatFileBytes, formatFileRelative, getIconColorProps, getNextEditorTabValue, hiddenBelowClasses, indicatorVariants, inputBorderClasses, normalizeThemeFileName, parseColor, parseNajmDesignConfig, parseNajmThemeConfig, parseThemeFile, resolveHiddenBelowClass, resolvePreset, resolveRadiusValue, resolveSlot, resolveVariantAlias, sidebarBorderClasses, sliderVariants, stringifyNajmDesignConfig, stringifyNajmThemeConfig, stringifyThemeFile, surfaceBorderClasses, swapVariants, toPickerHex, toggleVariants, tooltipContentVariants, truncateByCharacters, useClickOutside, useContextMenu, useDebouncedValue, useDelayedLoading, useDialog, useDialogStore, useDynamicPageSize, useFormField, useFormSubmission, useInfiniteScroll, useKeyboard, useLocalStorageState, useNForm, useNPortalScope, useNajmAppearance, useNajmComponentStyle, useNajmDesign, useNajmThemeMode, usePrefix, useSelection, useStepNavigation, useStorageContextMenu, useStoreSync, useTable, useTableKeyboard, useTableStore, useVariant, useVariantPreset };
3908
+ export { Alert, type AlertLook, type AlertOrientation, type AlertProps, type AlertSize, type AlertTone, type AlertVariant, NCard as AsyncCard, Avatar, AvatarFallback, AvatarFormInput, type AvatarFormInputProps, AvatarGroup, type AvatarGroupProps, AvatarImage, AvatarInput, type AvatarInputProps, type AvatarInputRadius, type AvatarProps$1 as AvatarProps, type AvatarShape$1 as AvatarShape, type AvatarSize, AvatarStatus, type AvatarStatusType, Badge, type BadgeColor, type BadgeIcon, type BadgeLook, type BadgeProps, type BadgeShape, type BadgeSize, type BadgeVariant, BaseInput, type BuildDefaultFileColumnsOptions, Button, type ButtonConfig, type ButtonIcon, type ButtonLoaderPosition, type ButtonProps, type ButtonRounded, type ButtonSize, type ButtonVariant, Calendar, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, CheckboxGroupInput, type CheckboxGroupInputProps, CheckboxInput, type CheckboxInputProps, Collapsible, CollapsibleContent, CollapsibleTrigger, ColorArrayInput, type ColorArrayInputProps, type ColorFormat, ColorPickerInput, type ColorPickerInputProps, Combobox, ComboboxInput, type ComboboxInputProps, type ComboboxOption, type ComboboxProps, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, type ContextMenuItem, DEFAULT_THEME_FILE_NAME, DateInput, type DateInputProps, type DeleteDialogOptions, Dialog, type DialogActionMode, type DialogApi, DialogClose, type DialogConfig, DialogContent, type DialogContentProps, DialogDescription, DialogFooter, DialogHeader, type DialogHeight, DialogOverlay, type DialogPadding, DialogPortal, type DialogRenderContext, type DialogRenderer, type DialogSize, type DialogStore, DialogTitle, DialogTrigger, type DialogVariant, type DialogWidth, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, DynamicArray, type DynamicArrayProps, EmojiInput, type EmojiInputProps, type FileBrowserMode, FileImportButton, FileInput, type FileInputProps, type FileNode, Form, FormControl, FormDescription, FormField, FormInput, type FormInputBackground, type FormInputProps, FormItem, FormLabel, FormMessage, type FormProps, type FormSlotClassNames, type FormVariant, IconButton, type IconButtonProps, type IconButtonSize, type IconButtonVariant, ImageInput, type ImageInputProps, Indicator, type IndicatorHorizontal, type IndicatorOverlay, type IndicatorPosition, type IndicatorProps, type IndicatorResponsivePosition, type IndicatorSize, type IndicatorVertical, Input, type InputIcon, Label, LangInput, type LangInputProps, type LinkComponentType, MultiSelectInput, type MultiSelectInputProps, NAJM_COMPONENT_NAMES, NAlert, type NAppCommandItem, NAppShell, type NAppShellAction, type NAppShellClassNames, type NAppShellProps, type NAppShellUser, NCard as NAsyncCard, type CardClassNames as NAsyncCardClassNames, type CardProps as NAsyncCardProps, NAvatar, type NAvatarClassNames, type NAvatarProps, type AvatarShape as NAvatarShape, NBadge, type NBadgeLook, type NBadgeProps, type NBulkAction, type NBulkActionButton, type NBulkActionSelect, NBulkActionsBar, type NBulkActionsBarProps, NButton, type NButtonProps, NCard, NCardAction, type CardClassNames as NCardClassNames, type NCardDensity, NCardFooter, NCardInfo, type NCardInfoProps, NCardMedia, type NCardMediaAspect, type NCardMediaPlacement, type NCardMediaProps, type NCardMediaSize, type NCardMediaVariant, type CardProps as NCardProps, NCardSection, type NCardSectionProps, type NCardSectionSurface, NCommandPalette, type NCommandPaletteProps, NConfirmDialog, type NConfirmDialogProps, NContextMenu, type NContextMenuItem, type NContextMenuProps, NDataCardShell, type NDataCardShellActions, type NDataCardShellProps, NDeleteDialog, NDeleteDialogContent, type NDeleteDialogContentProps, type NDeleteDialogProps, NDetailCard, type NDetailCardClassNames, type NDetailCardProps, NDetailItem, type NDetailItemProps, NDetailList, type NDetailListItem, type NDetailListProps, NDialog, type NDialogActionProps, NDialogDescription, type NDialogDescriptionProps, type NDialogDirectProps, NDialogHeader, type NDialogHeaderProps, NDialogPrimaryButton, type NDialogProps, NDialogSecondaryButton, NDonutCard, type NDonutCardClassNames, type NDonutCardItem, type NDonutCardLayout, type NDonutCardLegendMarker, type NDonutCardProps, type NDonutCardVariant, type NEditorTab, NEditorTabs, type NEditorTabsProps, NEmptyState, type NEmptyStateProps, NErrorBoundary, NErrorState, type NErrorStateProps, NFileBrowser, type NFileBrowserCardProps, type NFileBrowserProps, type NFileBrowserRenderThumbProps, NFileTypeIcon, type NFileTypeIconProps, NFilterBar, NFolderIcon, type NFolderIconProps, NForm, NFormSectionHeader, type NFormSectionHeaderProps, NGrid, type NGridCols, NGridItem, type NGridItemProps, type NGridProps, type NGridSpan, NIcon, type NIconProps, type NIconSource, NIndicator, NInspectorSheet, NLoadingState, type NLoadingStateProps, NMultiDialog, type NMultiDialogProps, NNavbar, NPageHeader, NPageHeaderActions, type NPageHeaderBreakpoint, NPageHeaderCompactActions, NPageHeaderFilters, type NPageHeaderProps, NPageHeaderTop, NPageLayout, type NPageLayoutProps, NPortalScopeProvider, NProgress, type NProgressProps, NRowActions, NSection, NSectionHeader, type NSectionHeaderActionsProps, type NSectionHeaderContentProps, type NSectionHeaderProps, type NSectionHeaderSubtitleProps, type NSectionHeaderTitleProps, NSectionInfo, type NSectionInfoProps, type NSectionProps, NSectionWithInfo, type NSectionWithInfoItem, type NSectionWithInfoProps, NSheet, type NSheetClassNames, type NSheetProps, NSidebar, NSidebarContent, type NSidebarContentProps, NSidebarFooter, type NSidebarFooterProps, NSidebarHeader, type NSidebarHeaderProps, NSidebarItem, NSidebarLogo, type NSidebarLogoProps, NSidebarMobile, type NSidebarMobileProps, NSidebarSection, type NSidebarSectionProps, NSkeleton, NSkeletonCalendar, NSkeletonChart, NSkeletonDonut, NSkeletonEventList, NSkeletonWidget, NSkeletonWidgets, NSlider, type NSliderProps, NSmartPasteDialog, type NSmartPasteDialogProps, NSpinner, type NSpinnerProps, NStatCard, type NStatCardClassNames, type NStatCardProps, NStatCardSkeleton, type NStatCardVariant, Swap as NSwap, type NSwapProps, NTable, type NTableCardPagination, NTableCardRoot, type NTableCardRootProps, NTableCards, type NTableClassNames, type NTableColumnBreakpoint, type NTableColumnDef, type NTableColumnMeta, NTableContent, NTableHeader, type NTableLoadMorePagination, NTableLoadingSkeleton, type NTableMenu, type NTableMenuProp, NTablePagination, type NTableProps, NTableRowSkeleton, NTableSkeleton, type NTableState, NTabs, type NTabsClassNames, type NTabsColor, type NTabsItem, type NTabsProps, type NTabsStyles, NThemeCustomizer, type NThemeCustomizerFontOption, type NThemeCustomizerLabels, type NThemeCustomizerProps, type NThemeCustomizerTab, NUploader, type NUploaderItem, type NUploaderItemStatus, type NUploaderProps, NViewBody, NViewToggle, type NajmAccent, type NajmAppearance, type NajmBorderSide, type NajmComponentName, type NajmComponentRadius, type NajmComponentStyleConfig, type NajmComponentThemeConfig, type NajmDensity, type NajmDesignConfig, NajmDesignProvider, type NajmDesignProviderProps, type NajmLayoutConfig, type NajmMode, type NajmPreset, type NajmResponsiveBreakpoint, type NajmResponsiveValue, NajmScroll, type NajmScrollProps, type NajmSlotStyle, type NajmThemeConfig, NajmThemeProvider, type NajmThemeProviderProps, type NajmThemeTokens, type NajmTypographyConfig, type NajmVariantStyle, NativeSelect, type NativeSelectOption, type NativeSelectProps, type NavItem, type NavItemGroup, NumberInput, type NumberInputProps, OtpInput, type OtpInputProps, PasswordInput, type PasswordInputProps, PhoneInput, type PhoneInputProps, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, PrefixProvider, Progress, type ProgressColor, type ProgressLabelPosition, type ProgressProps, type ProgressSize, type PushDialogOptions, RADIUS_VALUE_MAP, RadioGroup, RadioGroupInput, type RadioGroupInputProps, RadioGroupItem, type RenderSlot, RepeatingFields, type RepeatingFieldsProps, ScrollArea, type ScrollAreaProps, SearchField, SearchField as SearchInput, SegmentedControl, type SegmentedControlOption, type SegmentedControlProps, Select, SelectContent, SelectGroup, SelectInput, type SelectInputProps, SelectItem, type SelectItemType$1 as SelectItemDataType, type SelectItemType, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, type SidebarItemProps, type SidebarProps, type SidebarWidth, type SidebarWidths, SimpleTooltip, type SimpleTooltipProps, NSkeleton as Skeleton, Slider, SliderInput, type SliderInputProps, type SliderOrientation, type SliderProps, type SliderSize, type SliderVariant, type SmartPastePreview, type SpinnerVariant, StarRatingInput, type StarRatingInputProps, StatusPill, type StatusPillProps, type StatusPillTone, type StepConfig, StepIndicator, type StepMeta, StepsHeader, StepsProgress, type StorageMenuAction, type StorageSortOption, type StorageTarget, Swap, type SwapEffect, SwapIndeterminate, SwapOff, SwapOn, type SwapProps, type SwapSize, type SwapState, Switch, type SwitchColor, SwitchInput, type SwitchInputProps, type SwitchSize, TAB_COLORS, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, type TableHeaderColor, TableRow, type TableState, type TableStore, TableStoreContext, Tabs, TabsContent, TabsList, type TabsListProps, type TabsOrientation, type TabsProps, TabsTrigger, type TabsTriggerProps, type TabsVariant, TextAreaInput, type TextAreaInputProps, TextInput, type TextInputProps, Textarea, TimeInput, type TimeInputProps, TimeZoneInput, type TimeZoneInputProps, Toaster, Toggle, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, type UseContextMenuResult, type UseNFormOptions, type UseStorageContextMenuOptions, type UseStorageContextMenuResult, type UserMenuAction, VariantProvider, type WizardClassNames, WizardForm, type WizardFormProps, alertVariants, avatarVariants, badgeColorVariants, badgeVariants, buildDefaultFileColumns, buttonVariants, cn, composePreset, createDialogStore, createTableStore, defineNajmDesignConfig, defineNajmThemeConfig, detectFormat, dialogVariants, filterResponsiveColumns, formatColor, formatFileBytes, formatFileRelative, getIconColorProps, getNextEditorTabValue, hiddenBelowClasses, indicatorVariants, inputBorderClasses, normalizeThemeFileName, parseColor, parseNajmDesignConfig, parseNajmThemeConfig, parseThemeFile, resolveHiddenBelowClass, resolvePreset, resolveRadiusValue, resolveSlot, resolveVariantAlias, sidebarBorderClasses, sliderVariants, stringifyNajmDesignConfig, stringifyNajmThemeConfig, stringifyThemeFile, surfaceBorderClasses, swapVariants, toPickerHex, toggleVariants, tooltipContentVariants, truncateByCharacters, useClickOutside, useContextMenu, useDebouncedValue, useDelayedLoading, useDialog, useDialogStore, useDynamicPageSize, useFormField, useFormSubmission, useInfiniteScroll, useKeyboard, useLocalStorageState, useNForm, useNPortalScope, useNajmAppearance, useNajmComponentStyle, useNajmDesign, useNajmThemeMode, usePrefix, useSelection, useStepNavigation, useStorageContextMenu, useStoreSync, useTable, useTableKeyboard, useTableStore, useVariant, useVariantPreset };
package/dist/index.mjs CHANGED
@@ -7274,7 +7274,7 @@ function NSkeleton({ className, ...props }) {
7274
7274
  return /* @__PURE__ */ jsx(
7275
7275
  "div",
7276
7276
  {
7277
- className: cn("animate-pulse rounded-md bg-accent", className),
7277
+ className: cn("animate-pulse rounded-md bg-accent motion-reduce:animate-none", className),
7278
7278
  "aria-hidden": "true",
7279
7279
  ...props
7280
7280
  }
@@ -11629,7 +11629,14 @@ var createTableStore = () => {
11629
11629
  addButtonText: "",
11630
11630
  pageSizeOptions: [10, 20, 30, 40, 50],
11631
11631
  calculatedPageSize: 10,
11632
+ skeletonRowCount: 6,
11632
11633
  maxHeight: null,
11634
+ bodyWidth: 0,
11635
+ bodyHeight: 0,
11636
+ tableHeaderHeight: 48,
11637
+ cardColumnCount: 1,
11638
+ cardRowHeight: 0,
11639
+ cardGap: 12,
11633
11640
  // JSON mode
11634
11641
  jsonValue: void 0,
11635
11642
  jsonColors: null,
@@ -11699,6 +11706,8 @@ var createTableStore = () => {
11699
11706
  // Responsive cards
11700
11707
  responsiveCards: true,
11701
11708
  isMobile: false,
11709
+ effectiveViewMode: "table",
11710
+ cardPagination: { mode: "paged" },
11702
11711
  // Empty states
11703
11712
  isEmpty: void 0,
11704
11713
  isFilteredEmpty: false,
@@ -11774,6 +11783,8 @@ function filterResponsiveColumns(columns) {
11774
11783
  // src/components/table/hooks.ts
11775
11784
  var ROW_HEIGHT = 56;
11776
11785
  var DEFAULT_TABLE_HEADER_HEIGHT = 48;
11786
+ var DEFAULT_CARD_HEIGHT = 176;
11787
+ var DEFAULT_CARD_GAP = 12;
11777
11788
  var ROOT_SECTION_GAP_COUNT = 2;
11778
11789
  function useStoreSync(props) {
11779
11790
  const storeRef = useRef(null);
@@ -11854,23 +11865,43 @@ function calculateDynamicPageSize(input) {
11854
11865
  if (availableRowsHeight <= 0) return 1;
11855
11866
  return Math.max(1, Math.floor(availableRowsHeight / rowHeight));
11856
11867
  }
11857
- function useDynamicPageSize(containerRef) {
11868
+ function calculateCardSkeletonCount(input) {
11869
+ const columns = Math.max(1, Math.floor(input.columnCount));
11870
+ const cardHeight = Math.max(1, input.cardHeight ?? DEFAULT_CARD_HEIGHT);
11871
+ const gap = Math.max(0, input.gap ?? DEFAULT_CARD_GAP);
11872
+ if (input.bodyHeight <= 0) return columns;
11873
+ const rows = Math.max(1, Math.ceil((input.bodyHeight + gap) / (cardHeight + gap)));
11874
+ return rows * columns;
11875
+ }
11876
+ function fallbackCardColumns(width) {
11877
+ if (width >= 1280) return 4;
11878
+ if (width >= 1024) return 3;
11879
+ if (width >= 640) return 2;
11880
+ return 1;
11881
+ }
11882
+ function useDynamicPageSize(containerRef, effectiveViewMode) {
11858
11883
  const dynamicHeight = useTableStore.use.dynamicHeight();
11859
- const viewMode = useTableStore.use.viewMode();
11884
+ const viewMode = useTableStore.use.effectiveViewMode();
11860
11885
  const manualPagination = useTableStore.use.manualPagination();
11861
11886
  const isLoading = useTableStore.use.isLoading();
11862
11887
  const error = useTableStore.use.error();
11863
11888
  const hasNoData = useTableStore.use.hasNoData();
11864
11889
  const isFilteredEmpty = useTableStore.use.isFilteredEmpty();
11865
11890
  const syncWithProps = useTableStore.use.syncWithProps();
11891
+ const lastMeasurementRef = useRef("");
11866
11892
  useLayoutEffect(() => {
11867
- if (!dynamicHeight || !containerRef.current || viewMode !== "table" || manualPagination) return;
11893
+ if (!dynamicHeight || !containerRef.current) return;
11868
11894
  const calculatePageSize = () => {
11869
11895
  const container2 = containerRef.current;
11870
11896
  if (!container2) return;
11871
11897
  const bodyEl = container2.querySelector("[data-ntable-body]");
11872
11898
  const tableHeaderEl = container2.querySelector("[data-ntable-table-header]");
11899
+ const loadingHeaderEl = container2.querySelector("[data-ntable-loading-header]");
11900
+ const cardsGridEl = container2.querySelector(
11901
+ "[data-ntable-loading-cards-grid], [data-ntable-cards-grid]"
11902
+ );
11873
11903
  let bodyHeight = bodyEl?.clientHeight ?? 0;
11904
+ const bodyWidth = bodyEl?.clientWidth ?? container2.clientWidth ?? 0;
11874
11905
  if (!bodyHeight) {
11875
11906
  const rootHeight = container2.clientHeight;
11876
11907
  const headerHeight = container2.querySelector("[data-ntable-header]")?.offsetHeight ?? 0;
@@ -11879,10 +11910,36 @@ function useDynamicPageSize(containerRef) {
11879
11910
  const gap = Number.parseFloat(rootStyles.rowGap || rootStyles.gap || "0") || 0;
11880
11911
  bodyHeight = rootHeight - headerHeight - paginationHeight - gap * ROOT_SECTION_GAP_COUNT;
11881
11912
  }
11913
+ if (loadingHeaderEl && bodyEl) {
11914
+ const bodyStyles = window.getComputedStyle(bodyEl);
11915
+ const bodyGap = Number.parseFloat(bodyStyles.rowGap || bodyStyles.gap || "0") || 0;
11916
+ bodyHeight = Math.max(0, bodyHeight - loadingHeaderEl.offsetHeight - bodyGap);
11917
+ }
11882
11918
  const tableHeaderHeight = tableHeaderEl?.offsetHeight ?? DEFAULT_TABLE_HEADER_HEIGHT;
11883
11919
  const newPageSize = calculateDynamicPageSize({ bodyHeight, tableHeaderHeight });
11884
11920
  const calculatedMaxHeight = tableHeaderHeight + newPageSize * ROW_HEIGHT;
11885
- syncWithProps({ calculatedPageSize: newPageSize, maxHeight: calculatedMaxHeight });
11921
+ const gridStyles = cardsGridEl ? window.getComputedStyle(cardsGridEl) : null;
11922
+ const gridTemplateColumns = gridStyles?.gridTemplateColumns;
11923
+ const gridColumns = gridTemplateColumns && gridTemplateColumns !== "none" ? gridTemplateColumns.split(" ").filter(Boolean).length : 0;
11924
+ const cardColumnCount = gridColumns || fallbackCardColumns(bodyWidth);
11925
+ const cardGap = Number.parseFloat(gridStyles?.rowGap || gridStyles?.gap || "") || DEFAULT_CARD_GAP;
11926
+ const firstCard = cardsGridEl?.querySelector("[data-ntable-loading-card], [data-row]");
11927
+ const cardRowHeight = firstCard?.offsetHeight || firstCard?.getBoundingClientRect().height || DEFAULT_CARD_HEIGHT;
11928
+ const updates = {
11929
+ ...!manualPagination ? { calculatedPageSize: newPageSize, maxHeight: calculatedMaxHeight } : {},
11930
+ skeletonRowCount: newPageSize,
11931
+ bodyWidth,
11932
+ bodyHeight,
11933
+ tableHeaderHeight,
11934
+ cardColumnCount,
11935
+ cardRowHeight,
11936
+ cardGap
11937
+ };
11938
+ const fingerprint = JSON.stringify(updates);
11939
+ if (fingerprint !== lastMeasurementRef.current) {
11940
+ lastMeasurementRef.current = fingerprint;
11941
+ syncWithProps(updates);
11942
+ }
11886
11943
  };
11887
11944
  calculatePageSize();
11888
11945
  const resizeObserver = new ResizeObserver(calculatePageSize);
@@ -11891,11 +11948,14 @@ function useDynamicPageSize(containerRef) {
11891
11948
  container.querySelectorAll(
11892
11949
  "[data-ntable-header], [data-ntable-body], [data-ntable-pagination], [data-ntable-table-header]"
11893
11950
  ).forEach((el) => resizeObserver.observe(el));
11951
+ container.querySelectorAll(
11952
+ "[data-ntable-loading-header], [data-ntable-loading-cards-grid], [data-ntable-loading-card], [data-ntable-cards-grid]"
11953
+ ).forEach((el) => resizeObserver.observe(el));
11894
11954
  if (container.parentElement) resizeObserver.observe(container.parentElement);
11895
11955
  return () => resizeObserver.disconnect();
11896
- }, [dynamicHeight, viewMode, containerRef, syncWithProps, manualPagination, isLoading, error, hasNoData, isFilteredEmpty]);
11956
+ }, [dynamicHeight, effectiveViewMode, viewMode, containerRef, syncWithProps, manualPagination, isLoading, error, hasNoData, isFilteredEmpty]);
11897
11957
  }
11898
- function useTable() {
11958
+ function useTable(effectiveViewModeOverride) {
11899
11959
  const [sorting, setSorting] = useState([]);
11900
11960
  const [columnFilters, setColumnFilters] = useState([]);
11901
11961
  const [columnVisibility, setColumnVisibility] = useState({});
@@ -11912,6 +11972,8 @@ function useTable() {
11912
11972
  const CardComponent = useTableStore.use.CardComponent();
11913
11973
  const dynamicHeight = useTableStore.use.dynamicHeight();
11914
11974
  const viewMode = useTableStore.use.viewMode();
11975
+ const effectiveViewMode = useTableStore.use.effectiveViewMode();
11976
+ const cardPagination = useTableStore.use.cardPagination();
11915
11977
  const calculatedPageSize = useTableStore.use.calculatedPageSize();
11916
11978
  const syncWithProps = useTableStore.use.syncWithProps();
11917
11979
  const onStateChange = useTableStore.use.onStateChange();
@@ -12002,6 +12064,8 @@ function useTable() {
12002
12064
  notifyStateChange({ sorting, columnFilters, columnVisibility, rowSelection: storeRowSelection, globalFilter });
12003
12065
  }, [storePagination, storeRowSelection, setPagination, sorting, columnFilters, columnVisibility, globalFilter, notifyStateChange]);
12004
12066
  const hasExpansion = Boolean(renderSubRow || userGetRowCanExpand);
12067
+ const renderedMode = effectiveViewModeOverride ?? effectiveViewMode ?? viewMode;
12068
+ const renderAllSuppliedRows = renderedMode === "cards" && cardPagination.mode !== "paged";
12005
12069
  const tableConfig = {
12006
12070
  data,
12007
12071
  columns: finalColumns,
@@ -12018,7 +12082,7 @@ function useTable() {
12018
12082
  getPaginationRowModel: getPaginationRowModel(),
12019
12083
  getSortedRowModel: getSortedRowModel(),
12020
12084
  getExpandedRowModel: getExpandedRowModel(),
12021
- manualPagination,
12085
+ manualPagination: manualPagination || renderAllSuppliedRows,
12022
12086
  pageCount,
12023
12087
  rowCount
12024
12088
  };
@@ -12032,9 +12096,11 @@ function useTable() {
12032
12096
  }, [table]);
12033
12097
  useLayoutEffect(() => {
12034
12098
  if (manualPagination) return;
12035
- if (dynamicHeight && viewMode === "table") table.setPageSize(calculatedPageSize);
12036
- if (viewMode === "cards") table.setPageSize(data.length || 9999);
12037
- }, [calculatedPageSize, dynamicHeight, viewMode, table, data.length, manualPagination]);
12099
+ if (dynamicHeight && renderedMode === "table") table.setPageSize(calculatedPageSize);
12100
+ if (viewMode === "cards" && cardPagination.mode === "paged") {
12101
+ table.setPageSize(data.length || 9999);
12102
+ }
12103
+ }, [calculatedPageSize, dynamicHeight, renderedMode, viewMode, cardPagination.mode, table, data.length, manualPagination]);
12038
12104
  return { table, finalColumns, sorting, setSorting, columnFilters, setColumnFilters, columnVisibility, setColumnVisibility, globalFilter, setGlobalFilter };
12039
12105
  }
12040
12106
  function useTableKeyboard(options = {}) {
@@ -12128,6 +12194,26 @@ function resolveTableColor(value, fallback) {
12128
12194
  }
12129
12195
  return color;
12130
12196
  }
12197
+
12198
+ // src/components/table/tableSurface.ts
12199
+ function useTableSurfaceAppearance(bordered, borderColor) {
12200
+ const recipe = useNajmComponentStyle("table");
12201
+ const recipeRadius = resolveRadiusValue(recipe?.radius);
12202
+ const resolvedBorderColor = resolveTableColor(borderColor, DEFAULT_TABLE_BORDER_COLOR);
12203
+ const style = recipeRadius || bordered !== false && (recipe?.borderWidth || borderColor) ? {
12204
+ ...recipeRadius ? { borderRadius: recipeRadius } : {},
12205
+ ...bordered !== false && recipe?.borderWidth ? { borderWidth: recipe.borderWidth } : {},
12206
+ ...bordered !== false && borderColor ? { borderColor: resolvedBorderColor } : {}
12207
+ } : void 0;
12208
+ return {
12209
+ bordered,
12210
+ style,
12211
+ className: cn(
12212
+ "bg-card",
12213
+ bordered === true ? surfaceBorderClasses(true) : "border-0 shadow-sm"
12214
+ )
12215
+ };
12216
+ }
12131
12217
  var ROW_CONTEXT_HANDLED = "__ntableRowContextHandled";
12132
12218
  function EditableCell({ cell, onCellEdit }) {
12133
12219
  const columnDef = cell.column.columnDef;
@@ -12182,12 +12268,6 @@ function EditableCell({ cell, onCellEdit }) {
12182
12268
  ] });
12183
12269
  }
12184
12270
  function NTableContent({ effectiveMode }) {
12185
- const recipe = useNajmComponentStyle("table");
12186
- const recipeRadius = resolveRadiusValue(recipe?.radius);
12187
- const recipeStyle = recipeRadius || recipe?.borderWidth ? {
12188
- ...recipeRadius ? { borderRadius: recipeRadius } : {},
12189
- ...recipe?.borderWidth ? { borderWidth: recipe.borderWidth } : {}
12190
- } : void 0;
12191
12271
  const table = useTableStore.use.table();
12192
12272
  const storeIsTableView = useTableStore.use.isTableView();
12193
12273
  const columns = useTableStore.use.columns();
@@ -12204,10 +12284,6 @@ function NTableContent({ effectiveMode }) {
12204
12284
  backgroundColor: resolvedHeaderColor,
12205
12285
  color: resolvedHeaderTextColor
12206
12286
  };
12207
- const contentStyle = recipeStyle || tableBorderColor ? {
12208
- ...recipeStyle ?? {},
12209
- ...tableBorderColor ? { borderColor: resolvedBorderColor } : {}
12210
- } : void 0;
12211
12287
  const rowBorderStyle = tableBorderColor ? { borderColor: resolvedBorderColor } : void 0;
12212
12288
  const onRowClick = useTableStore.use.onRowClick();
12213
12289
  const onRowContextMenu = useTableStore.use.onRowContextMenu();
@@ -12220,6 +12296,7 @@ function NTableContent({ effectiveMode }) {
12220
12296
  const showContent = useTableStore.use.showContent();
12221
12297
  const classNames = useTableStore.use.classNames();
12222
12298
  const bordered = useTableStore.use.bordered();
12299
+ const surface = useTableSurfaceAppearance(bordered, tableBorderColor);
12223
12300
  const showCheckbox = useTableStore.use.showCheckbox();
12224
12301
  const selectedRowId = useTableStore.use.selectedRowId();
12225
12302
  const renderSubRow = useTableStore.use.renderSubRow();
@@ -12248,11 +12325,11 @@ function NTableContent({ effectiveMode }) {
12248
12325
  axis: "both",
12249
12326
  "data-bordered": bordered === false ? "false" : bordered ? "true" : void 0,
12250
12327
  className: cn(
12251
- "min-h-0 flex-1 overflow-hidden rounded-md bg-card",
12252
- bordered === true ? surfaceBorderClasses(true) : "shadow-sm",
12328
+ "min-h-0 flex-1 overflow-hidden rounded-md",
12329
+ surface.className,
12253
12330
  classNames?.content
12254
12331
  ),
12255
- style: contentStyle,
12332
+ style: surface.style,
12256
12333
  onContextMenu: handleBackgroundContextMenu,
12257
12334
  children: /* @__PURE__ */ jsxs(Table, { children: [
12258
12335
  /* @__PURE__ */ jsx(TableHeader, { "data-ntable-table-header": true, className: cn("bg-card sticky top-0 z-10", headerClassName, bordered === true && "[&_tr]:border-border", classNames?.tableHeader), children: table.getHeaderGroups().map((hg) => /* @__PURE__ */ jsxs(TableRow, { style: rowBorderStyle, className: cn("hover:bg-transparent", bordered === true && "border-border"), children: [
@@ -12361,13 +12438,8 @@ function NTableContent({ effectiveMode }) {
12361
12438
  }
12362
12439
  );
12363
12440
  }
12364
- function NDataCardShell({ row, onClick, onContextMenu, actions, children, className, showCheckbox = true, selectedRowId, openRowMenu, menuButton, bordered }) {
12365
- const recipe = useNajmComponentStyle("table");
12366
- const recipeRadius = resolveRadiusValue(recipe?.radius);
12367
- const recipeStyle = recipeRadius || recipe?.borderWidth ? {
12368
- ...recipeRadius ? { borderRadius: recipeRadius } : {},
12369
- ...recipe?.borderWidth ? { borderWidth: recipe.borderWidth } : {}
12370
- } : void 0;
12441
+ function NDataCardShell({ row, onClick, onContextMenu, actions, children, className, showCheckbox = true, selectedRowId, openRowMenu, menuButton, bordered, borderColor }) {
12442
+ const surface = useTableSurfaceAppearance(bordered, borderColor);
12371
12443
  const canExpand = row.getCanExpand();
12372
12444
  const isExpanded = canExpand && row.getIsExpanded();
12373
12445
  const isSelected = row.getIsSelected();
@@ -12382,10 +12454,10 @@ function NDataCardShell({ row, onClick, onContextMenu, actions, children, classN
12382
12454
  "data-bordered": bordered === false ? "false" : bordered ? "true" : void 0,
12383
12455
  onClick,
12384
12456
  onContextMenu,
12385
- style: recipeStyle,
12457
+ style: surface.style,
12386
12458
  className: cn(
12387
12459
  "relative group w-full rounded-lg bg-card text-card-foreground overflow-hidden",
12388
- surfaceBorderClasses(bordered),
12460
+ surface.className,
12389
12461
  isActive && (bordered ? "border-primary" : "ring-2 ring-primary ring-offset-1 ring-offset-background"),
12390
12462
  onClick && "cursor-pointer",
12391
12463
  className
@@ -12402,7 +12474,7 @@ function NDataCardShell({ row, onClick, onContextMenu, actions, children, classN
12402
12474
  className: "h-4 w-4"
12403
12475
  }
12404
12476
  ) }),
12405
- useMenuButton ? /* @__PURE__ */ jsx("div", { className: "absolute top-2 right-2 h-auto z-10 opacity-0 transition-opacity duration-200 group-hover:opacity-100 focus-within:opacity-100", children: /* @__PURE__ */ jsx(
12477
+ useMenuButton ? /* @__PURE__ */ jsx("div", { "data-ntable-card-action": true, className: "ntable-card-action absolute end-2 top-2 z-10 h-auto transition-opacity duration-200", children: /* @__PURE__ */ jsx(
12406
12478
  "button",
12407
12479
  {
12408
12480
  type: "button",
@@ -12411,10 +12483,10 @@ function NDataCardShell({ row, onClick, onContextMenu, actions, children, classN
12411
12483
  e.stopPropagation();
12412
12484
  openRowMenu(e, row.original);
12413
12485
  },
12414
- className: "flex h-7 w-7 p-0 rounded-md cursor-pointer justify-center items-center text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground",
12486
+ className: "flex h-7 w-7 cursor-pointer items-center justify-center rounded-md p-0 text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
12415
12487
  children: /* @__PURE__ */ jsx(MoreVertical, { className: "h-4 w-4" })
12416
12488
  }
12417
- ) }) : actions && (actions.onView || actions.onEdit || actions.onDelete) ? /* @__PURE__ */ jsx("div", { className: "absolute top-2 right-2 h-auto z-10 opacity-0 transition-opacity duration-200 group-hover:opacity-100 focus-within:opacity-100", children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [
12489
+ ) }) : actions && (actions.onView || actions.onEdit || actions.onDelete) ? /* @__PURE__ */ jsx("div", { "data-ntable-card-action": true, className: "ntable-card-action absolute end-2 top-2 z-10 h-auto transition-opacity duration-200", children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [
12418
12490
  /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
12419
12491
  "div",
12420
12492
  {
@@ -12519,6 +12591,7 @@ function NTableCards({ effectiveMode }) {
12519
12591
  const showContent = useTableStore.use.showContent();
12520
12592
  const classNames = useTableStore.use.classNames();
12521
12593
  const bordered = useTableStore.use.bordered();
12594
+ const borderColor = useTableStore.use.borderColor();
12522
12595
  const renderSubRow = useTableStore.use.renderSubRow();
12523
12596
  const userGetRowCanExpand = useTableStore.use.getRowCanExpand();
12524
12597
  const handleContainerContextMenu = useCallback((e) => {
@@ -12556,7 +12629,7 @@ function NTableCards({ effectiveMode }) {
12556
12629
  const defaultContainerClass = "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3";
12557
12630
  const containerClass = classNames?.cards ?? defaultContainerClass;
12558
12631
  const actions = !menuButton && (onView || onEdit || onDelete) ? { onView, onEdit, onDelete } : void 0;
12559
- return /* @__PURE__ */ jsx(NajmScroll, { axis: "y", className: "min-h-0 flex-1 overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: cn(containerClass), onContextMenu: handleContainerContextMenu, children: rows.map((row) => {
12632
+ return /* @__PURE__ */ jsx(NajmScroll, { axis: "y", className: "min-h-0 flex-1 overflow-hidden", children: /* @__PURE__ */ jsx("div", { "data-ntable-cards-grid": true, className: cn(containerClass), onContextMenu: handleContainerContextMenu, children: rows.map((row) => {
12560
12633
  const noShell = Boolean(row.original?.__smsNoShell);
12561
12634
  const hasExpansion = Boolean(renderSubRow || userGetRowCanExpand);
12562
12635
  const canExpand = hasExpansion && row.getCanExpand();
@@ -12586,7 +12659,8 @@ function NTableCards({ effectiveMode }) {
12586
12659
  e.stopPropagation();
12587
12660
  openRowMenu(e, row.original);
12588
12661
  },
12589
- className: "absolute top-2 right-2 z-10 flex h-7 w-7 cursor-pointer items-center justify-center rounded-md p-0 text-muted-foreground opacity-0 transition-all duration-200 hover:bg-muted/50 hover:text-foreground group-hover:opacity-100 focus:opacity-100 focus-visible:opacity-100",
12662
+ "data-ntable-card-action": true,
12663
+ className: "ntable-card-action absolute end-2 top-2 z-10 flex h-7 w-7 cursor-pointer items-center justify-center rounded-md p-0 text-muted-foreground transition-all duration-200 hover:bg-muted/50 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
12590
12664
  children: /* @__PURE__ */ jsx(MoreVertical, { className: "h-4 w-4" })
12591
12665
  }
12592
12666
  ),
@@ -12622,6 +12696,7 @@ function NTableCards({ effectiveMode }) {
12622
12696
  openRowMenu,
12623
12697
  menuButton,
12624
12698
  bordered,
12699
+ borderColor,
12625
12700
  className: rowClassName || void 0,
12626
12701
  children: /* @__PURE__ */ jsx(
12627
12702
  CardComponent,
@@ -12639,13 +12714,108 @@ function NTableCards({ effectiveMode }) {
12639
12714
  );
12640
12715
  }) }) });
12641
12716
  }
12717
+ function CardLoadMorePagination({
12718
+ config,
12719
+ rowCount,
12720
+ bordered,
12721
+ className
12722
+ }) {
12723
+ const [internalPending, setInternalPending] = React__default.useState(false);
12724
+ const [internalError, setInternalError] = React__default.useState(null);
12725
+ const [announcement, setAnnouncement] = React__default.useState("");
12726
+ const buttonRef = React__default.useRef(null);
12727
+ const pendingRef = React__default.useRef(false);
12728
+ const restoreFocusRef = React__default.useRef(false);
12729
+ const previousRowCountRef = React__default.useRef(rowCount);
12730
+ const errorId = React__default.useId();
12731
+ const pending = Boolean(config.loadingMore || internalPending);
12732
+ const error = config.loadMoreError ?? internalError;
12733
+ React__default.useEffect(() => {
12734
+ const previous = previousRowCountRef.current;
12735
+ if (rowCount > previous) {
12736
+ const appended = rowCount - previous;
12737
+ setAnnouncement(
12738
+ config.itemsLoadedLabel?.(appended) ?? `${appended} more ${appended === 1 ? "item" : "items"} loaded.`
12739
+ );
12740
+ }
12741
+ previousRowCountRef.current = rowCount;
12742
+ }, [config.itemsLoadedLabel, rowCount]);
12743
+ React__default.useEffect(() => {
12744
+ if (pending || !restoreFocusRef.current) return;
12745
+ restoreFocusRef.current = false;
12746
+ const frame = requestAnimationFrame(() => buttonRef.current?.focus());
12747
+ return () => cancelAnimationFrame(frame);
12748
+ }, [pending]);
12749
+ const loadMore = async () => {
12750
+ if (pendingRef.current || pending || !config.hasNextPage && !error) return;
12751
+ pendingRef.current = true;
12752
+ restoreFocusRef.current = document.activeElement === buttonRef.current;
12753
+ setInternalPending(true);
12754
+ setInternalError(null);
12755
+ const loadingAnnouncement = config.loadingMoreLabel ?? "Loading more items...";
12756
+ setAnnouncement(loadingAnnouncement);
12757
+ try {
12758
+ await config.onLoadMore();
12759
+ } catch {
12760
+ setInternalError(config.loadMoreErrorLabel ?? "Couldn't load more items.");
12761
+ setAnnouncement("");
12762
+ } finally {
12763
+ pendingRef.current = false;
12764
+ setInternalPending(false);
12765
+ setAnnouncement((current) => current === loadingAnnouncement ? "" : current);
12766
+ }
12767
+ };
12768
+ if (!config.hasNextPage && !pending && !error) {
12769
+ return /* @__PURE__ */ jsx(
12770
+ "div",
12771
+ {
12772
+ "data-ntable-load-more-end": true,
12773
+ role: "status",
12774
+ "aria-live": "polite",
12775
+ className: cn("py-2 text-center text-sm text-muted-foreground", className),
12776
+ children: config.endLabel ?? "No more items."
12777
+ }
12778
+ );
12779
+ }
12780
+ return /* @__PURE__ */ jsxs(
12781
+ "div",
12782
+ {
12783
+ "data-ntable-load-more": true,
12784
+ className: cn("flex min-w-0 flex-col items-center gap-2 py-2", className),
12785
+ children: [
12786
+ error ? /* @__PURE__ */ jsx("div", { id: errorId, role: "alert", className: "text-center text-sm text-destructive", children: error === true ? config.loadMoreErrorLabel ?? "Couldn't load more items." : error }) : null,
12787
+ /* @__PURE__ */ jsxs(
12788
+ Button,
12789
+ {
12790
+ ref: buttonRef,
12791
+ type: "button",
12792
+ bordered,
12793
+ variant: "outline",
12794
+ autoLoading: false,
12795
+ disabled: pending,
12796
+ "aria-describedby": error ? errorId : void 0,
12797
+ "aria-busy": pending ? "true" : void 0,
12798
+ onClick: loadMore,
12799
+ children: [
12800
+ pending ? /* @__PURE__ */ jsx(Loader2, { "aria-hidden": "true", className: "h-4 w-4 animate-spin motion-reduce:animate-none" }) : null,
12801
+ pending ? config.loadingMoreLabel ?? "Loading more..." : error ? config.retryLabel ?? "Retry" : config.loadMoreLabel ?? "Load more"
12802
+ ]
12803
+ }
12804
+ ),
12805
+ /* @__PURE__ */ jsx("span", { className: "sr-only", role: "status", "aria-live": "polite", "aria-atomic": "true", children: announcement })
12806
+ ]
12807
+ }
12808
+ );
12809
+ }
12642
12810
  function NTablePagination() {
12643
12811
  const table = useTableStore.use.table();
12644
12812
  const showPagination = useTableStore.use.showPagination();
12645
12813
  const showContent = useTableStore.use.showContent();
12646
12814
  const pageSizeOptions = useTableStore.use.pageSizeOptions();
12647
12815
  const classNames = useTableStore.use.classNames();
12648
- const viewMode = useTableStore.use.viewMode();
12816
+ const effectiveViewMode = useTableStore.use.effectiveViewMode();
12817
+ const cardPagination = useTableStore.use.cardPagination();
12818
+ const data = useTableStore.use.data();
12649
12819
  const pagination = useTableStore.use.pagination();
12650
12820
  const manualPagination = useTableStore.use.manualPagination();
12651
12821
  const pageCount = useTableStore.use.pageCount();
@@ -12653,7 +12823,19 @@ function NTablePagination() {
12653
12823
  const setPagination = useTableStore.use.setPagination();
12654
12824
  const isPaginationControlled = useTableStore.use.isPaginationControlled();
12655
12825
  const bordered = useTableStore.use.bordered();
12656
- if (!table || !showContent || !showPagination || viewMode === "json" || viewMode === "files") return null;
12826
+ if (!table || !showContent || !showPagination || effectiveViewMode === "json" || effectiveViewMode === "files") return null;
12827
+ if (effectiveViewMode === "cards" && cardPagination.mode === "all") return null;
12828
+ if (effectiveViewMode === "cards" && cardPagination.mode === "load-more") {
12829
+ return /* @__PURE__ */ jsx(
12830
+ CardLoadMorePagination,
12831
+ {
12832
+ config: cardPagination,
12833
+ rowCount: data.length,
12834
+ bordered,
12835
+ className: classNames?.pagination
12836
+ }
12837
+ );
12838
+ }
12657
12839
  const filteredRows = table.getFilteredRowModel().rows;
12658
12840
  const selectedRows = table.getFilteredSelectedRowModel().rows;
12659
12841
  const { pageIndex, pageSize } = table.getState().pagination;
@@ -13091,7 +13273,7 @@ function NTableHeaderSkeleton() {
13091
13273
  }
13092
13274
  );
13093
13275
  }
13094
- function NTableLoadingSkeleton({ rows = DEFAULT_ROWS2 }) {
13276
+ function NTableLoadingSkeleton({ rows }) {
13095
13277
  const rawColumns = useTableStore.use.columns();
13096
13278
  const responsiveColumns = React__default.useMemo(() => filterResponsiveColumns(rawColumns), [rawColumns]);
13097
13279
  const columns = responsiveColumns;
@@ -13099,24 +13281,34 @@ function NTableLoadingSkeleton({ rows = DEFAULT_ROWS2 }) {
13099
13281
  const headerClassName = useTableStore.use.headerClassName();
13100
13282
  const classNames = useTableStore.use.classNames();
13101
13283
  const dynamicHeight = useTableStore.use.dynamicHeight();
13284
+ const bordered = useTableStore.use.bordered();
13285
+ const borderColor = useTableStore.use.borderColor();
13286
+ const surface = useTableSurfaceAppearance(bordered, borderColor);
13287
+ const bodyHeight = useTableStore.use.bodyHeight();
13288
+ const skeletonRowCount = useTableStore.use.skeletonRowCount();
13102
13289
  const renderSubRow = useTableStore.use.renderSubRow();
13103
13290
  const userGetRowCanExpand = useTableStore.use.getRowCanExpand();
13104
13291
  const hasExpansion = Boolean(renderSubRow || userGetRowCanExpand);
13105
13292
  const loadingText = useTableStore.use.loadingText();
13293
+ const rowCount = rows ?? (dynamicHeight && bodyHeight > 0 ? skeletonRowCount : DEFAULT_ROWS2);
13106
13294
  const renderHeaderLabel = (header) => typeof header === "string" ? header : null;
13107
13295
  return /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-2", children: [
13108
13296
  /* @__PURE__ */ jsx(NTableHeaderSkeleton, {}),
13109
13297
  /* @__PURE__ */ jsxs(
13110
- Card,
13298
+ "div",
13111
13299
  {
13112
13300
  "data-testid": "ntable-loading-skeleton",
13301
+ "data-ntable-loading-row-count": rowCount,
13302
+ "data-bordered": surface.bordered === false ? "false" : surface.bordered ? "true" : void 0,
13113
13303
  "aria-busy": "true",
13114
13304
  "aria-label": loadingText,
13115
- className: cn("rounded-md p-0 border", dynamicHeight ? "overflow-hidden" : "najm-overlay-scroll", classNames?.content),
13305
+ role: "status",
13306
+ style: surface.style,
13307
+ className: cn("min-h-0 flex-1 rounded-md p-0", surface.className, dynamicHeight ? "overflow-hidden" : "najm-overlay-scroll", classNames?.content),
13116
13308
  children: [
13117
13309
  /* @__PURE__ */ jsx("span", { className: "sr-only", children: loadingText }),
13118
- /* @__PURE__ */ jsx("div", { className: dynamicHeight ? "najm-overlay-scroll" : void 0, children: /* @__PURE__ */ jsxs(Table, { children: [
13119
- /* @__PURE__ */ jsx(TableHeader, { className: cn(headerClassName, dynamicHeight && "sticky top-0 z-10", classNames?.tableHeader), children: /* @__PURE__ */ jsxs(TableRow, { className: "hover:bg-muted/30", children: [
13310
+ /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: dynamicHeight ? "najm-overlay-scroll h-full" : void 0, children: /* @__PURE__ */ jsxs(Table, { children: [
13311
+ /* @__PURE__ */ jsx(TableHeader, { "data-ntable-table-header": true, className: cn(headerClassName, "sticky top-0 z-10", classNames?.tableHeader), children: /* @__PURE__ */ jsxs(TableRow, { className: "hover:bg-muted/30", children: [
13120
13312
  showCheckbox && /* @__PURE__ */ jsx(TableHead, { "aria-label": "Select column", className: "w-10 text-foreground h-12" }),
13121
13313
  hasExpansion && /* @__PURE__ */ jsx(TableHead, { "aria-label": "Expand column", className: "w-10 text-foreground h-12" }),
13122
13314
  columns.map((col, i) => /* @__PURE__ */ jsx(
@@ -13129,7 +13321,7 @@ function NTableLoadingSkeleton({ rows = DEFAULT_ROWS2 }) {
13129
13321
  col?.id ?? col?.accessorKey ?? i
13130
13322
  ))
13131
13323
  ] }) }),
13132
- /* @__PURE__ */ jsx(TableBody, { children: Array.from({ length: rows }).map((_, r) => /* @__PURE__ */ jsxs(TableRow, { children: [
13324
+ /* @__PURE__ */ jsx(TableBody, { children: Array.from({ length: rowCount }).map((_, r) => /* @__PURE__ */ jsxs(TableRow, { children: [
13133
13325
  showCheckbox && /* @__PURE__ */ jsx(TableCell, { className: "h-14 w-10", children: /* @__PURE__ */ jsx(NSkeleton, { className: "h-4 w-4" }) }),
13134
13326
  hasExpansion && /* @__PURE__ */ jsx(TableCell, { className: "h-14 w-10", children: /* @__PURE__ */ jsx(NSkeleton, { className: "h-4 w-4" }) }),
13135
13327
  columns.map((col, c) => /* @__PURE__ */ jsx(
@@ -13160,74 +13352,105 @@ function NTableCardsLoadingSkeleton({ rows }) {
13160
13352
  );
13161
13353
  const classNames = useTableStore.use.classNames();
13162
13354
  const bordered = useTableStore.use.bordered();
13163
- const calculatedPageSize = useTableStore.use.calculatedPageSize();
13164
- const pagination = useTableStore.use.pagination();
13165
- const cardCount = rows ?? Math.max(1, calculatedPageSize || pagination?.pageSize || DEFAULT_CARD_COUNT);
13355
+ const borderColor = useTableStore.use.borderColor();
13356
+ const surface = useTableSurfaceAppearance(bordered, borderColor);
13357
+ const dynamicHeight = useTableStore.use.dynamicHeight();
13358
+ const bodyHeight = useTableStore.use.bodyHeight();
13359
+ const cardColumnCount = useTableStore.use.cardColumnCount();
13360
+ const cardRowHeight = useTableStore.use.cardRowHeight();
13361
+ const cardGap = useTableStore.use.cardGap();
13362
+ const loadingText = useTableStore.use.loadingText();
13363
+ const cardCount = rows ?? (dynamicHeight && bodyHeight > 0 ? calculateCardSkeletonCount({
13364
+ bodyHeight,
13365
+ columnCount: cardColumnCount,
13366
+ cardHeight: cardRowHeight,
13367
+ gap: cardGap
13368
+ }) : DEFAULT_CARD_COUNT);
13369
+ const defaultContainerClass = "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4";
13370
+ const containerClass = classNames?.cards ?? defaultContainerClass;
13166
13371
  return /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-2", children: [
13167
13372
  hasHeaderSkeleton && /* @__PURE__ */ jsx(NTableHeaderSkeleton, {}),
13168
- /* @__PURE__ */ jsx(NajmScroll, { axis: "y", className: "min-h-0 flex-1 overflow-hidden", children: /* @__PURE__ */ jsx(
13169
- "div",
13373
+ /* @__PURE__ */ jsxs(
13374
+ NajmScroll,
13170
13375
  {
13171
- "data-testid": "ntable-cards-loading-skeleton",
13376
+ axis: "y",
13172
13377
  "aria-busy": "true",
13173
- className: cn("grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4", classNames?.cards),
13174
- children: Array.from({ length: cardCount }).map((_, index) => /* @__PURE__ */ jsx(
13175
- Card,
13176
- {
13177
- className: cn("rounded-lg bg-card p-3 shadow-none sm:p-4", surfaceBorderClasses(bordered)),
13178
- children: /* @__PURE__ */ jsxs(
13179
- "div",
13180
- {
13181
- "data-ntable-loading-card-layout": "responsive-avatar",
13182
- className: "grid grid-cols-[80px_minmax(0,1fr)] gap-3 sm:grid-cols-[72px_minmax(0,1fr)]",
13183
- children: [
13184
- /* @__PURE__ */ jsx("div", { className: "col-start-1 row-start-1 flex items-start justify-center sm:justify-start", children: /* @__PURE__ */ jsx(
13185
- NSkeleton,
13186
- {
13187
- "data-ntable-loading-card-avatar": true,
13188
- className: "size-20 shrink-0 rounded-full sm:size-16"
13189
- }
13190
- ) }),
13191
- /* @__PURE__ */ jsxs("div", { className: "col-start-2 row-start-1 flex min-w-0 items-start justify-between gap-3", children: [
13192
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-2", children: [
13193
- /* @__PURE__ */ jsx(NSkeleton, { className: "h-5 w-36 max-w-full" }),
13194
- /* @__PURE__ */ jsx(NSkeleton, { className: "hidden h-3 w-16 sm:block" })
13195
- ] }),
13196
- /* @__PURE__ */ jsx(
13197
- NSkeleton,
13198
- {
13199
- "data-ntable-loading-card-status": true,
13200
- className: "hidden h-6 w-14 shrink-0 rounded-full sm:block"
13201
- }
13202
- )
13203
- ] }),
13204
- /* @__PURE__ */ jsx(
13378
+ "aria-label": loadingText,
13379
+ role: "status",
13380
+ className: "min-h-0 flex-1 overflow-hidden",
13381
+ children: [
13382
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: loadingText }),
13383
+ /* @__PURE__ */ jsx(
13384
+ "div",
13385
+ {
13386
+ "data-testid": "ntable-cards-loading-skeleton",
13387
+ "data-ntable-loading-cards-grid": true,
13388
+ "data-ntable-loading-card-count": cardCount,
13389
+ "aria-hidden": "true",
13390
+ className: cn(containerClass),
13391
+ children: Array.from({ length: cardCount }).map((_, index) => /* @__PURE__ */ jsx(
13392
+ "div",
13393
+ {
13394
+ "data-ntable-loading-card": true,
13395
+ "data-bordered": surface.bordered === false ? "false" : surface.bordered ? "true" : void 0,
13396
+ style: surface.style,
13397
+ className: cn("rounded-lg p-3 sm:p-4", surface.className),
13398
+ children: /* @__PURE__ */ jsxs(
13205
13399
  "div",
13206
13400
  {
13207
- "data-ntable-loading-card-details": true,
13208
- className: "col-start-2 row-start-2 space-y-1 sm:col-span-full sm:col-start-1 sm:space-y-2 sm:rounded-lg sm:bg-muted/50 sm:p-3",
13209
- children: Array.from({ length: 3 }).map((_2, detailIndex) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 sm:gap-2", children: [
13210
- /* @__PURE__ */ jsx(NSkeleton, { className: "size-3.5 shrink-0 rounded-sm sm:size-4" }),
13211
- /* @__PURE__ */ jsx(
13401
+ "data-ntable-loading-card-layout": "responsive-avatar",
13402
+ className: "grid grid-cols-[80px_minmax(0,1fr)] gap-3 sm:grid-cols-[72px_minmax(0,1fr)]",
13403
+ children: [
13404
+ /* @__PURE__ */ jsx("div", { className: "col-start-1 row-start-1 flex items-start justify-center sm:justify-start", children: /* @__PURE__ */ jsx(
13212
13405
  NSkeleton,
13213
13406
  {
13214
- className: cn(
13215
- "h-3 max-w-full sm:h-4",
13216
- detailIndex === 0 ? "w-full" : detailIndex === 1 ? "w-4/5" : "w-3/4"
13217
- )
13407
+ "data-ntable-loading-card-avatar": true,
13408
+ className: "size-20 shrink-0 rounded-full sm:size-16"
13409
+ }
13410
+ ) }),
13411
+ /* @__PURE__ */ jsxs("div", { className: "col-start-2 row-start-1 flex min-w-0 items-start justify-between gap-3", children: [
13412
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-2", children: [
13413
+ /* @__PURE__ */ jsx(NSkeleton, { className: "h-5 w-36 max-w-full" }),
13414
+ /* @__PURE__ */ jsx(NSkeleton, { className: "hidden h-3 w-16 sm:block" })
13415
+ ] }),
13416
+ /* @__PURE__ */ jsx(
13417
+ NSkeleton,
13418
+ {
13419
+ "data-ntable-loading-card-status": true,
13420
+ className: "hidden h-6 w-14 shrink-0 rounded-full sm:block"
13421
+ }
13422
+ )
13423
+ ] }),
13424
+ /* @__PURE__ */ jsx(
13425
+ "div",
13426
+ {
13427
+ "data-ntable-loading-card-details": true,
13428
+ className: "col-start-2 row-start-2 space-y-1 sm:col-span-full sm:col-start-1 sm:space-y-2 sm:rounded-lg sm:bg-muted/50 sm:p-3",
13429
+ children: Array.from({ length: 3 }).map((_2, detailIndex) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 sm:gap-2", children: [
13430
+ /* @__PURE__ */ jsx(NSkeleton, { className: "size-3.5 shrink-0 rounded-sm sm:size-4" }),
13431
+ /* @__PURE__ */ jsx(
13432
+ NSkeleton,
13433
+ {
13434
+ className: cn(
13435
+ "h-3 max-w-full sm:h-4",
13436
+ detailIndex === 0 ? "w-full" : detailIndex === 1 ? "w-4/5" : "w-3/4"
13437
+ )
13438
+ }
13439
+ )
13440
+ ] }, detailIndex))
13218
13441
  }
13219
13442
  )
13220
- ] }, detailIndex))
13443
+ ]
13221
13444
  }
13222
13445
  )
13223
- ]
13224
- }
13225
- )
13226
- },
13227
- index
13228
- ))
13446
+ },
13447
+ index
13448
+ ))
13449
+ }
13450
+ )
13451
+ ]
13229
13452
  }
13230
- ) })
13453
+ )
13231
13454
  ] });
13232
13455
  }
13233
13456
  function TableStateSlot({ children }) {
@@ -13278,7 +13501,7 @@ function TableLayout(props) {
13278
13501
  const responsiveCards = useTableStore.use.responsiveCards();
13279
13502
  const isCustomMode = useTableStore.use.isCustomMode();
13280
13503
  const renderCustomMode = useTableStore.use.renderCustomMode();
13281
- const [isMobile, setIsMobile] = useState(false);
13504
+ const [isMobile, setIsMobile] = useState(() => typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(max-width: 639px)").matches : false);
13282
13505
  useEffect(() => {
13283
13506
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
13284
13507
  const mql = window.matchMedia("(max-width: 639px)");
@@ -13287,18 +13510,22 @@ function TableLayout(props) {
13287
13510
  mql.addEventListener("change", handler2);
13288
13511
  return () => mql.removeEventListener("change", handler2);
13289
13512
  }, []);
13290
- useDynamicPageSize(containerRef);
13291
- useTable();
13292
- useTableKeyboard({
13293
- scopeRef: containerRef,
13294
- contextMenuClose: props.contextMenuClose,
13295
- contextMenuOpen: props.contextMenuOpen
13296
- });
13297
13513
  const effectiveMode = (() => {
13298
13514
  if (viewMode === "json") return "json";
13299
13515
  if (isMobile && responsiveCards && CardComponent) return "cards";
13300
13516
  return viewMode;
13301
13517
  })();
13518
+ const syncWithProps = useTableStore.use.syncWithProps();
13519
+ useLayoutEffect(() => {
13520
+ syncWithProps({ isMobile, effectiveViewMode: effectiveMode });
13521
+ }, [effectiveMode, isMobile, syncWithProps]);
13522
+ useDynamicPageSize(containerRef, effectiveMode);
13523
+ useTable(effectiveMode);
13524
+ useTableKeyboard({
13525
+ scopeRef: containerRef,
13526
+ contextMenuClose: props.contextMenuClose,
13527
+ contextMenuOpen: props.contextMenuOpen
13528
+ });
13302
13529
  const showFilteredEmpty = isFilteredEmpty && !isLoading && !error;
13303
13530
  const showEmpty = hasNoData && !isLoading && !error && !showFilteredEmpty;
13304
13531
  const customRenderer = isCustomMode ? renderCustomMode?.[viewMode] : void 0;
@@ -13458,6 +13685,7 @@ function NTable(props) {
13458
13685
  pagination: props.pagination,
13459
13686
  defaultPagination: props.defaultPagination,
13460
13687
  onPaginationChange: props.onPaginationChange ?? null,
13688
+ cardPagination: props.cardPagination ?? { mode: "paged" },
13461
13689
  // Row selection
13462
13690
  rowSelection: props.rowSelection,
13463
13691
  defaultRowSelection: props.defaultRowSelection,
package/dist/theme.css CHANGED
@@ -307,9 +307,28 @@ input:autofill {
307
307
  overflow-x: auto;
308
308
  scrollbar-width: none;
309
309
  }
310
- .najm-overlay-scroll-x::-webkit-scrollbar {
311
- display: none;
312
- }
310
+ .najm-overlay-scroll-x::-webkit-scrollbar {
311
+ display: none;
312
+ }
313
+
314
+ /* Responsive table actions stay discoverable on touch and tablet layouts.
315
+ Fine-pointer desktops may keep the quieter hover/focus reveal treatment. */
316
+ .ntable-card-action {
317
+ opacity: 1;
318
+ }
319
+
320
+ @media (min-width: 64rem) and (hover: hover) and (pointer: fine) {
321
+ .ntable-card-action {
322
+ opacity: 0;
323
+ }
324
+
325
+ .group:hover > .ntable-card-action,
326
+ .group:focus-within > .ntable-card-action,
327
+ .ntable-card-action:focus,
328
+ .ntable-card-action:focus-within {
329
+ opacity: 1;
330
+ }
331
+ }
313
332
 
314
333
  /* OverlayScrollbars theme used by the <NajmScroll> component — a thin,
315
334
  translucent slate bar that floats over content with no reserved space.
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "najm-kit",
3
- "version": "2.1.47",
3
+ "version": "2.1.48",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Reusable React UI component package for Najm framework",
7
7
  "main": "./dist/index.mjs",
8
8
  "types": "./dist/index.d.ts",
9
9
  "files": [
10
- "dist"
10
+ "dist",
11
+ "README.md",
12
+ "CHANGELOG.md"
11
13
  ],
12
14
  "exports": {
13
15
  ".": {