najm-kit 2.1.48 → 2.1.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.1.49 - 2026-08-04
4
+
5
+ ### ImageInput and AvatarInput
6
+
7
+ - Add `previewAlt`, `fallbackImage`, `fallbackAlt`, `unavailableContent`,
8
+ `imageClassName`, `onPreviewError`, `replaceAriaLabel`, and `clearAriaLabel`
9
+ to `ImageInputProps`. `AvatarInput` forwards every new prop unchanged.
10
+ - Preview sources resolve in this priority order: `value`, `fallbackImage`,
11
+ `defaultImage`. Candidate URLs are deduplicated so a failing primary URL is
12
+ never retried through multiple stages. When every candidate fails, the
13
+ broken `<img>` is unmounted and `unavailableContent` (or a neutral default)
14
+ is rendered in its place.
15
+ - `imageVersion` is appended safely to relative, absolute, queried, and
16
+ fragmented URLs. `data:`, `blob:`, `javascript:`, and `file:` URLs are
17
+ left unchanged.
18
+ - Expose `data-image-input-state="empty" | "preview" | "fallback" | "unavailable"`
19
+ on the preview container for styling, testing, and consumer diagnostics.
20
+ - File selection is race-safe: stale `FileReader` completions cannot replace
21
+ a newer value. Object URLs created by the component are tracked so
22
+ consumer-owned blob URLs are never revoked.
23
+ - Replace and clear controls are real `<button>` elements, are reachable
24
+ with the keyboard, and stay visible on touch and coarse-pointer devices.
25
+ Only on `(hover: hover) and (pointer: fine)` desktops do the controls fall
26
+ back to a hover/focus reveal. `focus-visible` always restores visibility.
27
+ - Use logical positioning (`end-*`) so the clear button works correctly in
28
+ RTL layouts.
29
+ - Re-export `ImageInputPreviewSource` and `ImageInputPreviewError` from
30
+ `najm-kit/components/inputs` and the package root.
31
+
3
32
  ## 2.1.48 - 2026-08-04
4
33
 
5
34
  - Keep responsive card row actions visible on phone, tablet, and touch input,
package/README.md CHANGED
@@ -141,12 +141,80 @@ import { Form, FormInput, useNForm } from 'najm-kit';
141
141
  | Category | Components |
142
142
  |----------|-----------|
143
143
  | Actions | NButton, IconButton, toggleVariants |
144
- | Forms | Input, Textarea, Label, Select, Checkbox, RadioGroup, Switch, DateInput, FileInput |
144
+ | Forms | Input, Textarea, Label, Select, Checkbox, RadioGroup, Switch, DateInput, FileInput, ImageInput, AvatarInput |
145
145
  | Feedback | Alert, Badge, Progress, Spinner, Toast |
146
146
  | Layout | Card, Sheet, Dialog, Popover, DropdownMenu, Tabs |
147
147
  | Data | Table (NTable), StatCard, DetailList |
148
148
  | Overlays | Command palette, Tooltip, Toast |
149
149
 
150
+ ## ImageInput and AvatarInput
151
+
152
+ `ImageInput` and `AvatarInput` ship with a resilient preview contract so
153
+ consumers do not need to wrap them with application-specific preview
154
+ components.
155
+
156
+ Source precedence:
157
+
158
+ - When `value` is a non-empty string URL, candidates are tried in order:
159
+ 1. `value` is the primary preview source.
160
+ 2. If the primary source fails, `fallbackImage` is tried when supplied.
161
+ 3. `defaultImage` is the last-resort fallback.
162
+ - When `value` is `null` or empty, only `defaultImage` is tracked. The
163
+ `fallbackImage` is intentionally not used in the empty state — a null
164
+ `value` is the consumer's empty-state signal, and only the configured
165
+ default participates in the failed-default → unavailable transition.
166
+ If `defaultImage` itself fails, `onPreviewError({ source: "default" })`
167
+ fires and the unavailable state is rendered.
168
+
169
+ Candidate URLs are deduplicated so the same failing URL is never retried
170
+ through multiple stages. When every candidate fails, the broken `<img>` is
171
+ unmounted and `unavailableContent` (or a neutral default) is rendered in its
172
+ place. A `data-image-input-state="empty" | "preview" | "fallback" | "unavailable"`
173
+ marker is exposed for styling, testing, and consumer diagnostics.
174
+
175
+ Candidate URLs are deduplicated so the same failing URL is never retried
176
+ through multiple stages. When every candidate fails, the broken `<img>` is
177
+ unmounted and `unavailableContent` (or a neutral default) is rendered in its
178
+ place. A `data-image-input-state="empty" | "preview" | "fallback" | "unavailable"`
179
+ marker is exposed for styling, testing, and consumer diagnostics.
180
+
181
+ ```tsx
182
+ import { ImageInput } from "najm-kit";
183
+
184
+ <ImageInput
185
+ value="https://cdn.example.com/avatar.png"
186
+ onChange={setAvatar}
187
+ previewAlt="Workspace logo"
188
+ fallbackImage="/assets/logo-default.png"
189
+ fallbackAlt="Default workspace logo"
190
+ unavailableContent={<span>Logo unavailable</span>}
191
+ imageClassName="object-contain"
192
+ imageVersion={cacheBustVersion}
193
+ replaceAriaLabel="Replace workspace logo"
194
+ clearAriaLabel="Remove workspace logo"
195
+ onPreviewError={(err) => log(err)}
196
+ />
197
+ ```
198
+
199
+ Key behaviors:
200
+
201
+ - The replace and clear controls are real `<button>` elements, are reachable
202
+ with the keyboard (`Enter` and `Space` activate them once), and stay
203
+ visible on touch and coarse-pointer devices. Only on `(hover: hover) and
204
+ (pointer: fine)` desktops do the controls fall back to a hover/focus
205
+ reveal. `focus-visible` always restores visibility.
206
+ - Positioning uses logical properties (`end-*`) so the clear button works
207
+ correctly in RTL layouts.
208
+ - `imageVersion` is appended safely to relative, absolute, queried, and
209
+ fragmented URLs. `data:`, `blob:`, `javascript:`, and `file:` URLs are
210
+ left unchanged.
211
+ - File selection is race-safe: stale `FileReader` completions cannot replace
212
+ a newer value, and object URLs created by the component are tracked so
213
+ consumer-owned blob URLs are never revoked.
214
+
215
+ `AvatarInput` forwards every preview and accessibility prop unchanged while
216
+ preserving its circular, size, fill, and camera-icon defaults.
217
+
150
218
  ## Hooks
151
219
 
152
220
  ```tsx
package/dist/index.d.ts CHANGED
@@ -2178,6 +2178,11 @@ interface TimeInputProps extends BaseProps {
2178
2178
  interface TimeZoneInputProps extends Omit<ComboboxInputProps, "items"> {
2179
2179
  items?: SelectItemType[];
2180
2180
  }
2181
+ type ImageInputPreviewSource = "value" | "fallback" | "default";
2182
+ interface ImageInputPreviewError {
2183
+ source: ImageInputPreviewSource;
2184
+ src: string;
2185
+ }
2181
2186
  interface ImageInputProps extends BaseProps {
2182
2187
  value: File | string | null;
2183
2188
  onChange: (file: File | null) => void;
@@ -2192,6 +2197,8 @@ interface ImageInputProps extends BaseProps {
2192
2197
  previewStyle?: CSSProperties;
2193
2198
  /** Class applied to the empty and replace-overlay content inside the preview. */
2194
2199
  contentClassName?: string;
2200
+ /** Class applied to the preview <img> element to control object-fit / sizing. */
2201
+ imageClassName?: string;
2195
2202
  showPreview?: boolean;
2196
2203
  previewPosition?: "top" | "bottom" | "left" | "right";
2197
2204
  allowClear?: boolean;
@@ -2208,6 +2215,26 @@ interface ImageInputProps extends BaseProps {
2208
2215
  trigger?: "icon" | "button" | "both";
2209
2216
  buttonLabel?: string;
2210
2217
  disabled?: boolean;
2218
+ /** Accessible name for the primary preview image. Falls back to `replaceTitle`. */
2219
+ previewAlt?: string;
2220
+ /** Accessible name for the fallback/default preview image. Falls back to `previewAlt`. */
2221
+ fallbackAlt?: string;
2222
+ /**
2223
+ * URL tried when the primary `value` fails to load.
2224
+ * The same URL is never attempted twice, so a failing primary cannot loop.
2225
+ */
2226
+ fallbackImage?: string | null;
2227
+ /**
2228
+ * Rendered inside the preview area when every candidate URL has failed.
2229
+ * When omitted, a small neutral unavailable state is rendered instead.
2230
+ */
2231
+ unavailableContent?: React.ReactNode;
2232
+ /** Fires once for every URL that failed to load, with its source. */
2233
+ onPreviewError?: (error: ImageInputPreviewError) => void;
2234
+ /** Accessible name for the replace (re-upload) control. */
2235
+ replaceAriaLabel?: string;
2236
+ /** Accessible name for the clear control. */
2237
+ clearAriaLabel?: string;
2211
2238
  }
2212
2239
  interface OtpInputProps extends BaseProps {
2213
2240
  value: string;
@@ -2334,7 +2361,7 @@ interface NUploaderProps {
2334
2361
  }
2335
2362
  declare function NUploader({ title, subtitle, accept, multiple, disabled, items, listTitle, className, dropzoneClassName, onFilesSelected, onCancel, onRemove, }: NUploaderProps): react_jsx_runtime.JSX.Element;
2336
2363
 
2337
- declare function ImageInput({ value, onChange, containerClassName, previewClassName, previewStyle, contentClassName, showPreview, previewPosition, allowClear, accept, defaultImage, imageSize, imageVersion, disabled, uploadIcon, title, subtitle, titleClassName, subtitleClassName, replaceTitle, replaceSubtitle, trigger, buttonLabel, }: ImageInputProps): react_jsx_runtime.JSX.Element;
2364
+ declare function ImageInput({ value, onChange, containerClassName, previewClassName, previewStyle, contentClassName, imageClassName, showPreview, previewPosition, allowClear, accept, defaultImage, fallbackImage, previewAlt, fallbackAlt, unavailableContent, onPreviewError, replaceAriaLabel, clearAriaLabel, imageSize, imageVersion, disabled, uploadIcon, title, subtitle, titleClassName, subtitleClassName, replaceTitle, replaceSubtitle, trigger, buttonLabel, }: ImageInputProps): react_jsx_runtime.JSX.Element;
2338
2365
 
2339
2366
  /** A circular image picker preset for profile photos and user avatars. */
2340
2367
  declare function AvatarInput({ imageSize, size, fill, radius, containerClassName, previewClassName, previewStyle, contentClassName, uploadIcon, title, subtitleClassName, trigger, ...props }: AvatarInputProps): react_jsx_runtime.JSX.Element;
@@ -3905,4 +3932,4 @@ interface NGridItemProps extends Omit<React$1.AllHTMLAttributes<HTMLDivElement>,
3905
3932
  declare function NGrid({ as: Comp, children, cols, smCols, mdCols, lgCols, xlCols, gap, className, style, ...props }: NGridProps): react_jsx_runtime.JSX.Element;
3906
3933
  declare function NGridItem({ children, span, smSpan, mdSpan, lgSpan, xlSpan, className, ...props }: NGridItemProps): react_jsx_runtime.JSX.Element;
3907
3934
 
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 };
3935
+ 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 ImageInputPreviewError, type ImageInputPreviewSource, 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
@@ -9850,12 +9850,74 @@ function UploaderRow({ item, onCancel, onRemove }) {
9850
9850
  }
9851
9851
  );
9852
9852
  }
9853
+
9854
+ // src/components/inputs/imagePreview.ts
9855
+ var NON_APPENDABLE_PREFIXES = ["data:", "blob:", "javascript:", "file:"];
9856
+ function isNonAppendable(src) {
9857
+ const lower = src.toLowerCase();
9858
+ return NON_APPENDABLE_PREFIXES.some((prefix) => lower.startsWith(prefix));
9859
+ }
9860
+ function isMeaningful(src) {
9861
+ return typeof src === "string" && src.length > 0;
9862
+ }
9863
+ function appendImageVersion(src, version) {
9864
+ if (!isMeaningful(src)) return src;
9865
+ if (version == null || version === "") return src;
9866
+ if (isNonAppendable(src)) return src;
9867
+ const fragmentIndex = src.indexOf("#");
9868
+ const beforeFragment = fragmentIndex === -1 ? src : src.slice(0, fragmentIndex);
9869
+ const fragment = fragmentIndex === -1 ? "" : src.slice(fragmentIndex);
9870
+ const queryIndex = beforeFragment.indexOf("?");
9871
+ const base = queryIndex === -1 ? beforeFragment : beforeFragment.slice(0, queryIndex);
9872
+ const existingQuery = queryIndex === -1 ? "" : beforeFragment.slice(queryIndex);
9873
+ const separator = existingQuery ? "&" : "?";
9874
+ const versionString = `${separator}v=${encodeURIComponent(String(version))}`;
9875
+ if (!existingQuery && !fragment) {
9876
+ return `${base}${versionString}`;
9877
+ }
9878
+ if (!existingQuery) {
9879
+ return `${base}${versionString}${fragment}`;
9880
+ }
9881
+ if (!fragment) {
9882
+ return `${base}${existingQuery}${versionString}`;
9883
+ }
9884
+ return `${base}${existingQuery}${versionString}${fragment}`;
9885
+ }
9886
+ function appendVersionToCandidate(src, version) {
9887
+ if (version == null || version === "") return src;
9888
+ if (isNonAppendable(src)) return src;
9889
+ return appendImageVersion(src, version);
9890
+ }
9891
+ function buildPreviewCandidates(options) {
9892
+ const seen = /* @__PURE__ */ new Set();
9893
+ const result = [];
9894
+ const push = (src, source) => {
9895
+ if (!isMeaningful(src)) return;
9896
+ if (seen.has(src)) return;
9897
+ seen.add(src);
9898
+ result.push({ src, source });
9899
+ };
9900
+ push(options.value ?? null, "value");
9901
+ push(options.fallback ?? null, "fallback");
9902
+ push(options.defaultImage ?? null, "default");
9903
+ const version = options.imageVersion;
9904
+ if (version == null || version === "") return result;
9905
+ return result.map((candidate) => ({
9906
+ src: appendVersionToCandidate(candidate.src, version),
9907
+ source: candidate.source
9908
+ }));
9909
+ }
9910
+ function candidatesKey(candidates) {
9911
+ return candidates.map((candidate) => `${candidate.source}:${candidate.src}`).join("|");
9912
+ }
9853
9913
  var IMAGE_SIZE_MAP = {
9854
9914
  sm: "w-16 h-16",
9855
9915
  md: "w-24 h-24",
9856
9916
  lg: "w-32 h-32",
9857
9917
  xl: "w-40 h-40"
9858
9918
  };
9919
+ var CONTROL_VISIBILITY = "nimage-input-control";
9920
+ var COMPACT_OVERLAY_VISIBILITY = "nimage-input-compact-overlay";
9859
9921
  function ImageInput({
9860
9922
  value,
9861
9923
  onChange,
@@ -9863,11 +9925,19 @@ function ImageInput({
9863
9925
  previewClassName,
9864
9926
  previewStyle,
9865
9927
  contentClassName,
9928
+ imageClassName,
9866
9929
  showPreview = true,
9867
9930
  previewPosition = "top",
9868
9931
  allowClear = true,
9869
9932
  accept = "image/*",
9870
9933
  defaultImage,
9934
+ fallbackImage,
9935
+ previewAlt,
9936
+ fallbackAlt,
9937
+ unavailableContent,
9938
+ onPreviewError,
9939
+ replaceAriaLabel,
9940
+ clearAriaLabel,
9871
9941
  imageSize = "md",
9872
9942
  imageVersion,
9873
9943
  disabled = false,
@@ -9882,117 +9952,242 @@ function ImageInput({
9882
9952
  buttonLabel = "Upload"
9883
9953
  }) {
9884
9954
  const fileInputRef = useRef(null);
9885
- const [preview, setPreview] = useState(null);
9955
+ const [localFilePreview, setLocalFilePreview] = useState(null);
9956
+ const [failedSources, setFailedSources] = useState(() => /* @__PURE__ */ new Set());
9957
+ const readerTokenRef = useRef(0);
9958
+ const previewCandidates = useMemo(() => {
9959
+ if (value instanceof File) return [];
9960
+ if (typeof value === "string" && value) {
9961
+ return buildPreviewCandidates({
9962
+ value,
9963
+ fallback: fallbackImage ?? null,
9964
+ defaultImage: defaultImage ?? null,
9965
+ imageVersion
9966
+ });
9967
+ }
9968
+ return buildPreviewCandidates({
9969
+ value: null,
9970
+ fallback: null,
9971
+ defaultImage: defaultImage ?? null,
9972
+ imageVersion
9973
+ });
9974
+ }, [value, fallbackImage, defaultImage, imageVersion]);
9975
+ const candidateKeyValue = useMemo(
9976
+ () => candidatesKey(previewCandidates),
9977
+ [previewCandidates]
9978
+ );
9979
+ const [trackedKey, setTrackedKey] = useState(candidateKeyValue);
9980
+ if (trackedKey !== candidateKeyValue) {
9981
+ setTrackedKey(candidateKeyValue);
9982
+ setFailedSources(/* @__PURE__ */ new Set());
9983
+ }
9886
9984
  useEffect(() => {
9887
- if (value instanceof File) {
9888
- const reader = new FileReader();
9889
- reader.onloadend = () => setPreview(reader.result);
9890
- reader.readAsDataURL(value);
9891
- } else if (typeof value === "string" && value) {
9892
- const url = imageVersion != null ? `${value}?v=${imageVersion}` : value;
9893
- setPreview(url);
9894
- } else {
9895
- setPreview(null);
9985
+ if (!(value instanceof File)) {
9986
+ setLocalFilePreview(null);
9987
+ return void 0;
9988
+ }
9989
+ const token = ++readerTokenRef.current;
9990
+ const reader = new FileReader();
9991
+ reader.onloadend = () => {
9992
+ if (token !== readerTokenRef.current) return;
9993
+ if (typeof reader.result === "string") {
9994
+ setLocalFilePreview(reader.result);
9995
+ }
9996
+ };
9997
+ reader.onerror = () => {
9998
+ if (token !== readerTokenRef.current) return;
9999
+ setLocalFilePreview(null);
10000
+ };
10001
+ reader.readAsDataURL(value);
10002
+ return () => {
10003
+ if (token === readerTokenRef.current) {
10004
+ readerTokenRef.current = token - 1;
10005
+ }
10006
+ };
10007
+ }, [value]);
10008
+ const activeCandidate = useMemo(() => {
10009
+ if (value instanceof File) return null;
10010
+ for (const candidate of previewCandidates) {
10011
+ if (!failedSources.has(candidate.src)) return candidate;
9896
10012
  }
9897
- }, [value, imageVersion]);
10013
+ return null;
10014
+ }, [previewCandidates, failedSources, value]);
10015
+ const handleCandidateError = (candidate) => {
10016
+ setFailedSources((prev) => {
10017
+ if (prev.has(candidate.src)) return prev;
10018
+ const next = new Set(prev);
10019
+ next.add(candidate.src);
10020
+ return next;
10021
+ });
10022
+ onPreviewError?.({ source: candidate.source, src: candidate.src });
10023
+ };
9898
10024
  const handleClick = () => {
9899
- if (!disabled) fileInputRef.current?.click();
10025
+ if (disabled) return;
10026
+ fileInputRef.current?.click();
9900
10027
  };
9901
10028
  const handleChange = (e) => {
9902
10029
  const file = e.target.files?.[0] || null;
9903
10030
  if (file) onChange(file);
10031
+ e.target.value = "";
9904
10032
  };
9905
10033
  const handleClear = (e) => {
9906
10034
  e.stopPropagation();
10035
+ if (disabled) return;
9907
10036
  onChange(null);
9908
- setPreview(null);
10037
+ setLocalFilePreview(null);
9909
10038
  if (fileInputRef.current) fileInputRef.current.value = "";
9910
10039
  };
10040
+ const handleImgError = () => {
10041
+ if (value instanceof File) return;
10042
+ if (activeCandidate) handleCandidateError(activeCandidate);
10043
+ };
9911
10044
  const effectiveSize = previewClassName || IMAGE_SIZE_MAP[imageSize];
9912
10045
  const isDropzone = !!previewClassName;
9913
10046
  const effectiveReplaceSubtitle = replaceSubtitle ?? subtitle;
10047
+ const primaryAlt = previewAlt ?? replaceTitle ?? "Preview";
10048
+ const secondaryAlt = fallbackAlt ?? previewAlt ?? replaceTitle ?? "Preview";
10049
+ const replaceAccessibleName = replaceAriaLabel ?? replaceTitle;
10050
+ const clearAccessibleName = clearAriaLabel ?? "Remove image";
10051
+ let dataState = "empty";
10052
+ let previewSrc = null;
10053
+ let previewAltText = primaryAlt;
10054
+ if (value instanceof File) {
10055
+ dataState = localFilePreview ? "preview" : "empty";
10056
+ previewSrc = localFilePreview;
10057
+ previewAltText = primaryAlt;
10058
+ } else if (activeCandidate) {
10059
+ dataState = activeCandidate.source === "value" ? "preview" : "fallback";
10060
+ previewSrc = activeCandidate.src;
10061
+ previewAltText = activeCandidate.source === "value" ? primaryAlt : secondaryAlt;
10062
+ } else if (previewCandidates.length > 0) {
10063
+ dataState = "unavailable";
10064
+ }
10065
+ const renderUnavailableContent = () => unavailableContent ?? /* @__PURE__ */ jsxs(Fragment, { children: [
10066
+ /* @__PURE__ */ jsx(Image, { className: "h-10 w-10 text-muted-foreground/50", "aria-hidden": true }),
10067
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Image unavailable" })
10068
+ ] });
10069
+ const renderClearButton = (compact) => {
10070
+ if (!allowClear) return null;
10071
+ return /* @__PURE__ */ jsx(
10072
+ "button",
10073
+ {
10074
+ type: "button",
10075
+ onClick: handleClear,
10076
+ disabled,
10077
+ "aria-label": clearAccessibleName,
10078
+ className: cn(
10079
+ "absolute top-2 end-2 z-10 flex items-center justify-center rounded-full bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
10080
+ compact ? "p-1.5" : "h-6 w-6",
10081
+ CONTROL_VISIBILITY
10082
+ ),
10083
+ children: /* @__PURE__ */ jsx(X, { className: compact ? "h-4 w-4" : "h-3.5 w-3.5" })
10084
+ }
10085
+ );
10086
+ };
9914
10087
  const renderPreview = () => /* @__PURE__ */ jsx(
9915
10088
  "div",
9916
10089
  {
9917
10090
  style: previewStyle,
10091
+ "data-image-input-state": dataState,
9918
10092
  className: cn(
9919
- "flex relative group rounded-lg overflow-hidden border-2 border-dashed border-muted-foreground/60 hover:border-primary transition-colors",
10093
+ "group/image flex relative rounded-lg overflow-hidden border-2 border-dashed border-muted-foreground/60 hover:border-primary transition-colors",
9920
10094
  effectiveSize
9921
10095
  ),
9922
- children: preview ? isDropzone ? /* @__PURE__ */ jsxs(Fragment, { children: [
9923
- /* @__PURE__ */ jsx("img", { src: preview, alt: "Preview", className: "absolute inset-0 w-full h-full object-cover" }),
9924
- /* @__PURE__ */ jsxs("div", { className: cn("relative w-full h-full flex flex-col items-center justify-center gap-1.5 bg-black/40 text-white px-6 py-8 text-center", contentClassName), children: [
9925
- /* @__PURE__ */ jsx("span", { className: cn("text-sm font-medium", titleClassName), children: replaceTitle }),
9926
- effectiveReplaceSubtitle ? /* @__PURE__ */ jsx("span", { className: cn("text-xs opacity-80", subtitleClassName), children: effectiveReplaceSubtitle }) : null,
9927
- allowClear && /* @__PURE__ */ jsx(
9928
- "button",
9929
- {
9930
- type: "button",
9931
- onClick: handleClear,
9932
- disabled,
9933
- className: "absolute top-2 right-2 flex h-6 w-6 items-center justify-center rounded-full bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
9934
- "aria-label": "Remove image",
9935
- children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
9936
- }
9937
- )
9938
- ] })
9939
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
9940
- /* @__PURE__ */ jsx("img", { src: preview, alt: "Preview", className: "w-full h-full object-cover" }),
10096
+ children: previewSrc ? isDropzone ? /* @__PURE__ */ jsxs(Fragment, { children: [
9941
10097
  /* @__PURE__ */ jsx(
9942
- "div",
10098
+ "img",
9943
10099
  {
9944
- onClick: handleClick,
9945
- className: "absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center cursor-pointer",
9946
- children: /* @__PURE__ */ jsx(Upload, { className: "h-8 w-8 text-white" })
10100
+ src: previewSrc,
10101
+ alt: previewAltText,
10102
+ onError: handleImgError,
10103
+ className: cn(
10104
+ "absolute inset-0 w-full h-full object-cover",
10105
+ imageClassName
10106
+ )
9947
10107
  }
9948
10108
  ),
9949
- allowClear && /* @__PURE__ */ jsx(
10109
+ /* @__PURE__ */ jsxs(
9950
10110
  "button",
9951
10111
  {
9952
10112
  type: "button",
9953
- onClick: handleClear,
10113
+ onClick: handleClick,
9954
10114
  disabled,
9955
- className: "absolute top-2 right-2 p-1.5 bg-destructive text-white rounded-full opacity-0 group-hover:opacity-100 transition-opacity shadow-md hover:bg-destructive/90 z-10",
9956
- children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
10115
+ "aria-label": replaceAccessibleName,
10116
+ className: cn(
10117
+ "relative w-full h-full flex flex-col items-center justify-center gap-1.5 bg-black/40 text-white px-6 py-8 text-center cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
10118
+ contentClassName
10119
+ ),
10120
+ children: [
10121
+ /* @__PURE__ */ jsx("span", { className: cn("text-sm font-medium", titleClassName), children: replaceTitle }),
10122
+ effectiveReplaceSubtitle ? /* @__PURE__ */ jsx("span", { className: cn("text-xs opacity-80", subtitleClassName), children: effectiveReplaceSubtitle }) : null
10123
+ ]
9957
10124
  }
9958
- )
9959
- ] }) : defaultImage ? /* @__PURE__ */ jsxs(Fragment, { children: [
9960
- /* @__PURE__ */ jsx("img", { src: defaultImage, alt: "Default", className: "w-full h-full object-cover" }),
10125
+ ),
10126
+ renderClearButton(false)
10127
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
9961
10128
  /* @__PURE__ */ jsx(
9962
- "div",
10129
+ "img",
9963
10130
  {
10131
+ src: previewSrc,
10132
+ alt: previewAltText,
10133
+ onError: handleImgError,
10134
+ className: cn("w-full h-full", imageClassName ?? "object-cover")
10135
+ }
10136
+ ),
10137
+ /* @__PURE__ */ jsx(
10138
+ "button",
10139
+ {
10140
+ type: "button",
9964
10141
  onClick: handleClick,
9965
- className: "absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center cursor-pointer",
9966
- children: /* @__PURE__ */ jsx(Upload, { className: "h-8 w-8 text-white" })
10142
+ disabled,
10143
+ "aria-label": replaceAccessibleName,
10144
+ className: cn(
10145
+ "absolute inset-0 bg-black/50 flex items-center justify-center cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
10146
+ COMPACT_OVERLAY_VISIBILITY
10147
+ ),
10148
+ children: /* @__PURE__ */ jsx(Upload, { className: "h-8 w-8 text-white", "aria-hidden": true })
9967
10149
  }
9968
- )
9969
- ] }) : isDropzone ? /* @__PURE__ */ jsxs(
10150
+ ),
10151
+ renderClearButton(true)
10152
+ ] }) : dataState === "unavailable" ? /* @__PURE__ */ jsx(
9970
10153
  "div",
9971
10154
  {
10155
+ "data-image-input-unavailable": true,
10156
+ className: cn(
10157
+ "w-full h-full flex flex-col items-center justify-center gap-2 bg-muted/30 text-muted-foreground px-6 py-8 text-center",
10158
+ contentClassName
10159
+ ),
10160
+ children: renderUnavailableContent()
10161
+ }
10162
+ ) : isDropzone ? /* @__PURE__ */ jsxs(
10163
+ "button",
10164
+ {
10165
+ type: "button",
9972
10166
  onClick: handleClick,
9973
- className: cn("w-full h-full flex flex-col items-center justify-center gap-2 cursor-pointer bg-muted/30 hover:bg-muted/50 transition-colors px-6 py-8 text-center", contentClassName),
10167
+ disabled,
10168
+ "aria-label": replaceAriaLabel ?? title,
10169
+ className: cn(
10170
+ "w-full h-full flex flex-col items-center justify-center gap-2 cursor-pointer bg-muted/30 hover:bg-muted/50 transition-colors px-6 py-8 text-center focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
10171
+ contentClassName
10172
+ ),
9974
10173
  children: [
9975
10174
  (trigger === "icon" || trigger === "both") && /* @__PURE__ */ jsx("div", { className: "text-primary", children: uploadIcon ?? /* @__PURE__ */ jsx(Plus, { className: "h-8 w-8" }) }),
9976
- (trigger === "button" || trigger === "both") && /* @__PURE__ */ jsx(
9977
- "span",
9978
- {
9979
- role: "button",
9980
- className: "inline-flex items-center justify-center rounded-md border border-input bg-background px-3 py-1.5 text-xs font-medium text-foreground shadow-sm hover:bg-accent hover:text-accent-foreground",
9981
- children: buttonLabel
9982
- }
9983
- ),
10175
+ (trigger === "button" || trigger === "both") && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center justify-center rounded-md border border-input bg-background px-3 py-1.5 text-xs font-medium text-foreground shadow-sm hover:bg-accent hover:text-accent-foreground", children: buttonLabel }),
9984
10176
  /* @__PURE__ */ jsx("span", { className: cn("text-sm font-medium text-foreground", titleClassName), children: title }),
9985
10177
  subtitle ? /* @__PURE__ */ jsx("span", { className: cn("text-xs text-muted-foreground", subtitleClassName), children: subtitle }) : null
9986
10178
  ]
9987
10179
  }
9988
10180
  ) : /* @__PURE__ */ jsxs(
9989
- "div",
10181
+ "button",
9990
10182
  {
10183
+ type: "button",
9991
10184
  onClick: handleClick,
9992
- className: "w-full h-full flex flex-col items-center justify-center cursor-pointer bg-muted/30 hover:bg-muted/50 transition-colors",
10185
+ disabled,
10186
+ "aria-label": replaceAriaLabel ?? title,
10187
+ className: "w-full h-full flex flex-col items-center justify-center cursor-pointer bg-muted/30 hover:bg-muted/50 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
9993
10188
  children: [
9994
- /* @__PURE__ */ jsx(Image, { className: "h-12 w-12 text-muted-foreground/50 mb-2" }),
9995
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground text-center px-2", children: "Click to upload" })
10189
+ /* @__PURE__ */ jsx(Image, { className: "h-12 w-12 text-muted-foreground/50 mb-2", "aria-hidden": true }),
10190
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground text-center px-2", children: title })
9996
10191
  ]
9997
10192
  }
9998
10193
  )
@@ -10006,20 +10201,42 @@ function ImageInput({
10006
10201
  onChange: handleChange,
10007
10202
  className: "hidden",
10008
10203
  accept,
10009
- disabled
10204
+ disabled,
10205
+ "aria-hidden": true,
10206
+ tabIndex: -1
10010
10207
  }
10011
10208
  );
10012
10209
  if (!showPreview) return renderFileInput();
10013
10210
  if (previewPosition === "left" || previewPosition === "right") {
10014
- return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-4", previewPosition === "right" && "flex-row-reverse", containerClassName), children: [
10015
- renderPreview(),
10016
- /* @__PURE__ */ jsx("div", { className: "flex-1", children: renderFileInput() })
10017
- ] });
10211
+ return /* @__PURE__ */ jsxs(
10212
+ "div",
10213
+ {
10214
+ className: cn(
10215
+ "flex items-center gap-4",
10216
+ previewPosition === "right" && "flex-row-reverse",
10217
+ containerClassName
10218
+ ),
10219
+ children: [
10220
+ renderPreview(),
10221
+ /* @__PURE__ */ jsx("div", { className: "flex-1", children: renderFileInput() })
10222
+ ]
10223
+ }
10224
+ );
10018
10225
  }
10019
- return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-3", previewPosition === "bottom" && "flex-col-reverse", containerClassName), children: [
10020
- renderPreview(),
10021
- renderFileInput()
10022
- ] });
10226
+ return /* @__PURE__ */ jsxs(
10227
+ "div",
10228
+ {
10229
+ className: cn(
10230
+ "flex flex-col gap-3",
10231
+ previewPosition === "bottom" && "flex-col-reverse",
10232
+ containerClassName
10233
+ ),
10234
+ children: [
10235
+ renderPreview(),
10236
+ renderFileInput()
10237
+ ]
10238
+ }
10239
+ );
10023
10240
  }
10024
10241
  var AVATAR_SIZE_MAP = {
10025
10242
  sm: "size-16",
package/dist/theme.css CHANGED
@@ -311,23 +311,62 @@ input:autofill {
311
311
  display: none;
312
312
  }
313
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
- }
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
+ }
332
+
333
+ /* ImageInput controls (clear button and replace overlay) stay visible on touch
334
+ and coarse-pointer devices. Only on fine-pointer desktops do they fall back to
335
+ a hover/focus reveal. Keyboard focus always restores visibility. The rule
336
+ below ships in dist/theme.css via scripts/build-css.mjs so the JSX only needs
337
+ to reference the static class names. */
338
+ .nimage-input-control {
339
+ opacity: 1;
340
+ }
341
+
342
+ @media (hover: hover) and (pointer: fine) {
343
+ .nimage-input-control {
344
+ opacity: 0;
345
+ }
346
+
347
+ .group\/image:hover .nimage-input-control,
348
+ .group\/image:focus-within .nimage-input-control,
349
+ .nimage-input-control:focus,
350
+ .nimage-input-control:focus-visible {
351
+ opacity: 1;
352
+ }
353
+ }
354
+
355
+ .nimage-input-compact-overlay {
356
+ opacity: 1;
357
+ }
358
+
359
+ @media (hover: hover) and (pointer: fine) {
360
+ .nimage-input-compact-overlay {
361
+ opacity: 0;
362
+ }
363
+
364
+ .group\/image:hover .nimage-input-compact-overlay,
365
+ .group\/image:focus-within .nimage-input-compact-overlay,
366
+ .nimage-input-compact-overlay:focus,
367
+ .nimage-input-compact-overlay:focus-visible {
368
+ opacity: 1;
369
+ }
331
370
  }
332
371
 
333
372
  /* OverlayScrollbars theme used by the <NajmScroll> component — a thin,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "najm-kit",
3
- "version": "2.1.48",
3
+ "version": "2.1.49",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Reusable React UI component package for Najm framework",