@pagamio/frontend-commons-lib 0.8.351 → 0.8.352

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.
@@ -1,5 +1,19 @@
1
1
  import * as React from 'react';
2
2
  import type { UsePagamioComboboxReturn } from '../../shared/hooks/usePagamioCombobox';
3
+ /**
4
+ * Optional in-popover action — replaces the old "+ Create New X" sentinel
5
+ * option pattern that some `<Select>` callers used to inline a "create a
6
+ * new item" affordance. Rendered as a button row pinned to the top of the
7
+ * popover, above the options list. Clicking it closes the popover and
8
+ * fires `onClick` (typically opening a quick-create modal). The handler
9
+ * is responsible for the side effect; the combobox does NOT update its
10
+ * `value` automatically.
11
+ */
12
+ export interface SearchableComboboxActionButton {
13
+ /** Label shown on the action button (e.g. "+ New category"). */
14
+ label: React.ReactNode;
15
+ onClick: () => void;
16
+ }
3
17
  export interface PagamioSearchableComboboxProps<T extends {
4
18
  id: string;
5
19
  }> {
@@ -8,6 +22,12 @@ export interface PagamioSearchableComboboxProps<T extends {
8
22
  /** Controlled selected id, or null when nothing is selected. */
9
23
  value: string | null;
10
24
  onChange: (id: string | null) => void;
25
+ /**
26
+ * Optional action button rendered at the top of the popover. Used to
27
+ * surface a "+ Create new …" affordance inside the picker itself,
28
+ * matching where the old static-select sentinel option lived.
29
+ */
30
+ actionButton?: SearchableComboboxActionButton;
11
31
  /**
12
32
  * Optional pre-resolved selected option. When set, the trigger shows its
13
33
  * label without needing to fetch via `restoreIds`. If both this and the
@@ -39,4 +59,4 @@ export interface PagamioSearchableComboboxProps<T extends {
39
59
  }
40
60
  export declare function PagamioSearchableCombobox<T extends {
41
61
  id: string;
42
- }>({ combobox, value, onChange, selectedOption, placeholder, searchPlaceholder, emptyMessage, cappedMessage, loadingMessage, disabled, required, name, getLabel, renderOption, renderSelected, className, triggerClassName, contentClassName, }: PagamioSearchableComboboxProps<T>): import("react/jsx-runtime").JSX.Element;
62
+ }>({ combobox, value, onChange, actionButton, selectedOption, placeholder, searchPlaceholder, emptyMessage, cappedMessage, loadingMessage, disabled, required, name, getLabel, renderOption, renderSelected, className, triggerClassName, contentClassName, }: PagamioSearchableComboboxProps<T>): import("react/jsx-runtime").JSX.Element;
@@ -19,7 +19,7 @@ function defaultGetLabel(option) {
19
19
  const o = option;
20
20
  return o.name ?? option.id;
21
21
  }
22
- export function PagamioSearchableCombobox({ combobox, value, onChange, selectedOption, placeholder = 'Select…', searchPlaceholder = 'Search...', emptyMessage = 'No options found', cappedMessage = 'Showing top results — refine your search.', loadingMessage = 'Loading…', disabled, required, name, getLabel = defaultGetLabel, renderOption, renderSelected, className, triggerClassName, contentClassName, }) {
22
+ export function PagamioSearchableCombobox({ combobox, value, onChange, actionButton, selectedOption, placeholder = 'Select…', searchPlaceholder = 'Search...', emptyMessage = 'No options found', cappedMessage = 'Showing top results — refine your search.', loadingMessage = 'Loading…', disabled, required, name, getLabel = defaultGetLabel, renderOption, renderSelected, className, triggerClassName, contentClassName, }) {
23
23
  const [open, setOpen] = React.useState(false);
24
24
  const inputRef = React.useRef(null);
25
25
  // Focus the search input when the popover opens — matches the Select's
@@ -52,11 +52,18 @@ export function PagamioSearchableCombobox({ combobox, value, onChange, selectedO
52
52
  : placeholder }), _jsx(ChevronDownIcon, { className: "ml-2 h-4 w-4 opacity-50 shrink-0" })] }) }), _jsxs(PopoverContent, { align: "start", sideOffset: 4,
53
53
  // Mirrors SelectContent: same shadow + animation classes, popover
54
54
  // background, popover-foreground text. Width matches trigger.
55
- className: cn('p-0 w-[var(--radix-popover-trigger-width)] min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md', contentClassName), children: [_jsx("div", { className: "p-2 border-b border-border", children: _jsx("input", { ref: inputRef, type: "text", placeholder: searchPlaceholder, value: combobox.search, onChange: (e) => combobox.setSearch(e.target.value), onKeyDown: (e) => e.stopPropagation(), className: "w-full px-2 py-1 text-sm border border-input rounded bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring" }) }), _jsx("div", { className: "p-1 max-h-[200px] overflow-y-auto", children: combobox.isLoading && combobox.options.length === 0 ? (_jsx("div", { className: "px-2 py-1.5 text-sm text-muted-foreground", children: loadingMessage })) : combobox.error ? (_jsx("div", { className: "px-2 py-1.5 text-sm text-destructive", children: combobox.error.message || 'Failed to load options.' })) : combobox.options.length === 0 ? (_jsx("div", { className: "px-2 py-1.5 text-sm text-muted-foreground", children: emptyMessage })) : (_jsxs(_Fragment, { children: [combobox.options.map((option) => {
56
- const isSelected = option.id === value;
57
- return (_jsx("button", { type: "button", role: "option", "aria-selected": isSelected, onClick: () => handleSelect(option.id), className: cn(
58
- // Mirrors SelectItem styling: padding, hover via
59
- // focus:bg-muted, selected via bg-accent.
60
- 'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none text-left', 'hover:bg-muted focus:bg-muted focus:text-foreground', isSelected && 'bg-accent text-foreground'), children: renderOption ? (renderOption(option, isSelected)) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "line-clamp-1", children: getLabel(option) }), isSelected ? (_jsx("span", { className: "absolute right-2 flex h-3.5 w-3.5 items-center justify-center", children: _jsx(CheckIcon, { className: "h-4 w-4" }) })) : null] })) }, option.id));
61
- }), combobox.isCapped ? (_jsx("div", { className: "mt-1 border-t px-2 py-1.5 text-xs text-muted-foreground", children: cappedMessage })) : null] })) })] })] }), name ? _jsx("input", { type: "hidden", name: name, value: value ?? '' }) : null] }));
55
+ className: cn('p-0 w-[var(--radix-popover-trigger-width)] min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md', contentClassName), children: [_jsx("div", { className: "p-2 border-b border-border", children: _jsx("input", { ref: inputRef, type: "text", placeholder: searchPlaceholder, value: combobox.search, onChange: (e) => combobox.setSearch(e.target.value), onKeyDown: (e) => e.stopPropagation(), className: "w-full px-2 py-1 text-sm border border-input rounded bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring" }) }), _jsxs("div", { className: "p-1 max-h-[200px] overflow-y-auto", children: [actionButton ? (_jsx("button", { type: "button", onClick: () => {
56
+ setOpen(false);
57
+ actionButton.onClick();
58
+ }, className: cn(
59
+ // Same row geometry as an option row so the picker reads
60
+ // as one consistent list. Primary-coloured label keeps it
61
+ // visually distinct from real options.
62
+ 'relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none text-left font-medium text-primary', 'hover:bg-muted focus:bg-muted'), children: actionButton.label }, "__action__")) : null, combobox.isLoading && combobox.options.length === 0 ? (_jsx("div", { className: "px-2 py-1.5 text-sm text-muted-foreground", children: loadingMessage })) : combobox.error ? (_jsx("div", { className: "px-2 py-1.5 text-sm text-destructive", children: combobox.error.message || 'Failed to load options.' })) : combobox.options.length === 0 ? (_jsx("div", { className: "px-2 py-1.5 text-sm text-muted-foreground", children: emptyMessage })) : (_jsxs(_Fragment, { children: [combobox.options.map((option) => {
63
+ const isSelected = option.id === value;
64
+ return (_jsx("button", { type: "button", role: "option", "aria-selected": isSelected, onClick: () => handleSelect(option.id), className: cn(
65
+ // Mirrors SelectItem styling: padding, hover via
66
+ // focus:bg-muted, selected via bg-accent.
67
+ 'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none text-left', 'hover:bg-muted focus:bg-muted focus:text-foreground', isSelected && 'bg-accent text-foreground'), children: renderOption ? (renderOption(option, isSelected)) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "line-clamp-1", children: getLabel(option) }), isSelected ? (_jsx("span", { className: "absolute right-2 flex h-3.5 w-3.5 items-center justify-center", children: _jsx(CheckIcon, { className: "h-4 w-4" }) })) : null] })) }, option.id));
68
+ }), combobox.isCapped ? (_jsx("div", { className: "mt-1 border-t px-2 py-1.5 text-xs text-muted-foreground", children: cappedMessage })) : null] }))] })] })] }), name ? _jsx("input", { type: "hidden", name: name, value: value ?? '' }) : null] }));
62
69
  }
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /* eslint-disable @typescript-eslint/no-explicit-any */
3
3
  import { Dropdown, DropdownItem } from 'flowbite-react';
4
4
  import { jsPDF } from 'jspdf';
5
- import { autoTable } from 'jspdf-autotable';
5
+ import autoTable from 'jspdf-autotable';
6
6
  import { FiDownload } from 'react-icons/fi';
7
7
  import * as XLSX from 'xlsx';
8
8
  const TableDownload = ({ columns, data }) => {
@@ -34,7 +34,7 @@ const SearchableComboboxInput = forwardRef(({ field, value, onChange }, ref) =>
34
34
  debounceMs: source.debounceMs,
35
35
  restoreIds,
36
36
  });
37
- return (_jsxs("div", { ref: ref, children: [field.label && (_jsx("label", { htmlFor: field.name, className: "block text-sm font-medium text-foreground mb-1", children: field.label })), _jsx(PagamioSearchableCombobox, { combobox: combobox, value: value ?? null, onChange: (id) => onChange?.(id), placeholder: field.placeholder, disabled: field.disabled, required: field.validation?.required ? true : undefined, name: field.name, getLabel: source.getLabel })] }));
37
+ return (_jsxs("div", { ref: ref, children: [field.label && (_jsx("label", { htmlFor: field.name, className: "block text-sm font-medium text-foreground mb-1", children: field.label })), _jsx(PagamioSearchableCombobox, { combobox: combobox, value: value ?? null, onChange: (id) => onChange?.(id), actionButton: field.actionButton, placeholder: field.placeholder, disabled: field.disabled, required: field.validation?.required ? true : undefined, name: field.name, getLabel: source.getLabel })] }));
38
38
  });
39
39
  SearchableComboboxInput.displayName = 'SearchableComboboxInput';
40
40
  export default SearchableComboboxInput;
@@ -215,6 +215,17 @@ export interface Field {
215
215
  * Tells the form engine how to fetch lookup results.
216
216
  */
217
217
  source?: ComboboxSourceSpec;
218
+ /**
219
+ * Optional in-picker action for `searchable-combobox`. Renders as the
220
+ * first row of the popover (above the options) — the same place a
221
+ * "+ Create new …" sentinel option used to live in the old static
222
+ * `<Select>`. Clicking it closes the popover and fires `onClick`; the
223
+ * handler owns the side effect (e.g. opens a quick-create modal).
224
+ */
225
+ actionButton?: {
226
+ label: string;
227
+ onClick: () => void;
228
+ };
218
229
  }
219
230
  export interface DependentFieldUpdate {
220
231
  field: string;
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Dropdown, DropdownItem } from 'flowbite-react';
3
3
  import { jsPDF } from 'jspdf';
4
- import { autoTable } from 'jspdf-autotable';
4
+ import autoTable from 'jspdf-autotable';
5
5
  import { FiDownload } from 'react-icons/fi';
6
6
  import * as XLSX from 'xlsx';
7
7
  const TableDownload = ({ columns, data }) => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pagamio/frontend-commons-lib",
3
3
  "description": "Pagamio library for Frontend reusable components like the form engine and table container",
4
- "version": "0.8.351",
4
+ "version": "0.8.352",
5
5
  "publishConfig": {
6
6
  "access": "public",
7
7
  "provenance": false