@pagamio/frontend-commons-lib 0.8.331 → 0.8.333
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/lib/components/ecommerce/ProductCard.js +5 -2
- package/lib/components/ecommerce/types.d.ts +7 -0
- package/lib/components/ui/PagamioSearchableCombobox.d.ts +42 -0
- package/lib/components/ui/PagamioSearchableCombobox.js +48 -0
- package/lib/components/ui/PagamioSearchableMultiCombobox.d.ts +31 -0
- package/lib/components/ui/PagamioSearchableMultiCombobox.js +59 -0
- package/lib/components/ui/index.d.ts +2 -0
- package/lib/components/ui/index.js +2 -0
- package/lib/form-engine/components/inputs/searchable-combobox/SearchableComboboxInput.d.ts +13 -0
- package/lib/form-engine/components/inputs/searchable-combobox/SearchableComboboxInput.js +40 -0
- package/lib/form-engine/components/inputs/searchable-multi-combobox/SearchableMultiComboboxInput.d.ts +13 -0
- package/lib/form-engine/components/inputs/searchable-multi-combobox/SearchableMultiComboboxInput.js +38 -0
- package/lib/form-engine/registry/RegisterComponents.js +5 -1
- package/lib/form-engine/types/index.d.ts +44 -1
- package/lib/shared/hooks/index.d.ts +1 -0
- package/lib/shared/hooks/index.js +1 -0
- package/lib/shared/hooks/usePagamioCombobox.d.ts +83 -0
- package/lib/shared/hooks/usePagamioCombobox.js +72 -0
- package/lib/styles.css +16 -0
- package/package.json +1 -1
|
@@ -5,8 +5,11 @@ import { formatPrice } from '../../helpers/utils';
|
|
|
5
5
|
import ImageComponent from '../ui/ImageComponent';
|
|
6
6
|
const LOW_STOCK_THRESHOLD = 5;
|
|
7
7
|
export function ProductCard({ product, onAddToCart, isAddingToCart, storeId, basePath = '/shop', featured = false, linkSuffix, currency = 'ZAR', renderLink, onNavigate, }) {
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
// Non-stock products (services / fees / digital goods) have no
|
|
9
|
+
// StockItem rows by design — they're always available
|
|
10
|
+
const isNonStock = product.isStockItem === false;
|
|
11
|
+
const hasStock = isNonStock || product.totalStock > 0;
|
|
12
|
+
const isLowStock = !isNonStock && hasStock && product.totalStock <= LOW_STOCK_THRESHOLD;
|
|
10
13
|
const isBackorderable = product.allowBackorders === true;
|
|
11
14
|
const canAddToCart = (hasStock || isBackorderable) && !!storeId;
|
|
12
15
|
const hasVariants = product.variants && product.variants.length > 0;
|
|
@@ -33,6 +33,13 @@ export interface EcommerceProduct {
|
|
|
33
33
|
totalStock: number;
|
|
34
34
|
inStock?: boolean;
|
|
35
35
|
allowBackorders?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* False for services / fees / digital goods that don't hold inventory.
|
|
38
|
+
* The card treats these as always available — no out-of-stock badge,
|
|
39
|
+
* no low-stock warning. Defaults to true (or undefined) for stock
|
|
40
|
+
* products which retain the existing behaviour.
|
|
41
|
+
*/
|
|
42
|
+
isStockItem?: boolean;
|
|
36
43
|
variants?: ProductVariant[];
|
|
37
44
|
}
|
|
38
45
|
export interface CartItemBundleItem {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { UsePagamioComboboxReturn } from '../../shared/hooks/usePagamioCombobox';
|
|
3
|
+
export interface PagamioSearchableComboboxProps<T extends {
|
|
4
|
+
id: string;
|
|
5
|
+
}> {
|
|
6
|
+
/** State from usePagamioCombobox(). */
|
|
7
|
+
combobox: UsePagamioComboboxReturn<T>;
|
|
8
|
+
/** Controlled selected id, or null when nothing is selected. */
|
|
9
|
+
value: string | null;
|
|
10
|
+
onChange: (id: string | null) => void;
|
|
11
|
+
/**
|
|
12
|
+
* Optional pre-resolved selected option. When set, the trigger shows its
|
|
13
|
+
* label without needing to fetch via `restoreIds`. If both this and the
|
|
14
|
+
* hook's `restoredOptions` are available, this wins.
|
|
15
|
+
*/
|
|
16
|
+
selectedOption?: T | null;
|
|
17
|
+
/** Trigger placeholder when nothing is selected. */
|
|
18
|
+
placeholder?: string;
|
|
19
|
+
/** Shown in dropdown when no results match. */
|
|
20
|
+
emptyMessage?: string;
|
|
21
|
+
/** Shown at the bottom of the list when the server returned a full page. */
|
|
22
|
+
cappedMessage?: string;
|
|
23
|
+
/** Shown while loading. */
|
|
24
|
+
loadingMessage?: string;
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
required?: boolean;
|
|
27
|
+
name?: string;
|
|
28
|
+
/** Allow clearing the selected value via an inline ✕. Default true. */
|
|
29
|
+
clearable?: boolean;
|
|
30
|
+
/** How to render an option as a string label. Default: `o.name` (must exist). */
|
|
31
|
+
getLabel?: (option: T) => string;
|
|
32
|
+
/** Optional custom row renderer inside the dropdown. */
|
|
33
|
+
renderOption?: (option: T, isSelected: boolean) => React.ReactNode;
|
|
34
|
+
/** Optional custom trigger label renderer. */
|
|
35
|
+
renderSelected?: (option: T) => React.ReactNode;
|
|
36
|
+
className?: string;
|
|
37
|
+
triggerClassName?: string;
|
|
38
|
+
contentClassName?: string;
|
|
39
|
+
}
|
|
40
|
+
export declare function PagamioSearchableCombobox<T extends {
|
|
41
|
+
id: string;
|
|
42
|
+
}>({ combobox, value, onChange, selectedOption, placeholder, emptyMessage, cappedMessage, loadingMessage, disabled, required, name, clearable, getLabel, renderOption, renderSelected, className, triggerClassName, contentClassName, }: PagamioSearchableComboboxProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* PagamioSearchableCombobox — server-driven, debounced, single-select combobox.
|
|
4
|
+
*
|
|
5
|
+
* Built on top of the existing Command (cmdk) primitive + Popover.
|
|
6
|
+
* State is owned by `usePagamioCombobox` (created by the caller).
|
|
7
|
+
*
|
|
8
|
+
* See .agent/conventions/lookup-endpoints.md (backend) for the contract.
|
|
9
|
+
*/
|
|
10
|
+
import { CheckIcon, ChevronDownIcon, Cross2Icon } from '@radix-ui/react-icons';
|
|
11
|
+
import * as React from 'react';
|
|
12
|
+
import { cn } from '../../helpers';
|
|
13
|
+
import { Command, CommandEmpty, CommandInput, CommandItem, CommandList } from './Command';
|
|
14
|
+
import { Popover, PopoverContent, PopoverTrigger } from './Popover';
|
|
15
|
+
function defaultGetLabel(option) {
|
|
16
|
+
// Assumes `name` exists. Override via `getLabel` for resources that use
|
|
17
|
+
// a different display field.
|
|
18
|
+
const o = option;
|
|
19
|
+
return o.name ?? option.id;
|
|
20
|
+
}
|
|
21
|
+
export function PagamioSearchableCombobox({ combobox, value, onChange, selectedOption, placeholder = 'Select…', emptyMessage = 'No results.', cappedMessage = 'Showing top results — refine your search.', loadingMessage = 'Loading…', disabled, required, name, clearable = true, getLabel = defaultGetLabel, renderOption, renderSelected, className, triggerClassName, contentClassName, }) {
|
|
22
|
+
const [open, setOpen] = React.useState(false);
|
|
23
|
+
// Resolve the option object for the current value: caller-supplied wins,
|
|
24
|
+
// then the hook's options (top-N), then restored.
|
|
25
|
+
const resolvedSelected = React.useMemo(() => {
|
|
26
|
+
if (selectedOption && selectedOption.id === value)
|
|
27
|
+
return selectedOption;
|
|
28
|
+
if (!value)
|
|
29
|
+
return null;
|
|
30
|
+
return combobox.options.find((o) => o.id === value) ?? combobox.restoredOptions.find((o) => o.id === value) ?? null;
|
|
31
|
+
}, [selectedOption, value, combobox.options, combobox.restoredOptions]);
|
|
32
|
+
const handleSelect = (id) => {
|
|
33
|
+
onChange(id === value ? null : id);
|
|
34
|
+
setOpen(false);
|
|
35
|
+
};
|
|
36
|
+
const handleClear = (e) => {
|
|
37
|
+
e.stopPropagation();
|
|
38
|
+
onChange(null);
|
|
39
|
+
};
|
|
40
|
+
return (_jsxs("div", { className: cn('w-full', className), children: [_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", role: "combobox", "aria-expanded": open, "aria-haspopup": "listbox", "aria-required": required, disabled: disabled, name: name, className: cn('flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border bg-background text-foreground px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground', 'focus:outline-none focus:ring-1 focus:ring-ring', 'disabled:cursor-not-allowed disabled:opacity-50', triggerClassName), children: [_jsx("span", { className: cn('line-clamp-1 text-left flex-1', !resolvedSelected && 'text-muted-foreground'), children: resolvedSelected
|
|
41
|
+
? renderSelected
|
|
42
|
+
? renderSelected(resolvedSelected)
|
|
43
|
+
: getLabel(resolvedSelected)
|
|
44
|
+
: placeholder }), clearable && resolvedSelected && !disabled ? (_jsx("span", { role: "button", tabIndex: -1, "aria-label": "Clear selection", onClick: handleClear, onMouseDown: (e) => e.preventDefault(), className: "ml-2 inline-flex h-4 w-4 items-center justify-center rounded-sm opacity-60 hover:opacity-100", children: _jsx(Cross2Icon, { className: "h-3 w-3" }) })) : (_jsx(ChevronDownIcon, { className: "ml-2 h-4 w-4 opacity-50 shrink-0" }))] }) }), _jsx(PopoverContent, { align: "start", sideOffset: 4, className: cn('p-0 w-[var(--radix-popover-trigger-width)] min-w-[240px]', contentClassName), children: _jsxs(Command, { shouldFilter: false, children: [_jsx(CommandInput, { placeholder: placeholder, value: combobox.search, onValueChange: combobox.setSearch }), _jsx(CommandList, { children: combobox.isLoading && combobox.options.length === 0 ? (_jsx("div", { className: "py-6 text-center text-sm text-muted-foreground", children: loadingMessage })) : combobox.error ? (_jsx("div", { className: "py-6 text-center text-sm text-destructive", children: combobox.error.message || 'Failed to load options.' })) : combobox.options.length === 0 ? (_jsx(CommandEmpty, { children: emptyMessage })) : (_jsxs(_Fragment, { children: [combobox.options.map((option) => {
|
|
45
|
+
const isSelected = option.id === value;
|
|
46
|
+
return (_jsx(CommandItem, { value: option.id, onSelect: () => handleSelect(option.id), children: renderOption ? (renderOption(option, isSelected)) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "flex-1 line-clamp-1", children: getLabel(option) }), isSelected ? _jsx(CheckIcon, { className: "ml-2 h-4 w-4 opacity-100" }) : null] })) }, option.id));
|
|
47
|
+
}), combobox.isCapped ? (_jsx("div", { className: "border-t px-3 py-2 text-xs text-muted-foreground", children: cappedMessage })) : null] })) })] }) })] }), name ? _jsx("input", { type: "hidden", name: name, value: value ?? '' }) : null] }));
|
|
48
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { UsePagamioComboboxReturn } from '../../shared/hooks/usePagamioCombobox';
|
|
3
|
+
export interface PagamioSearchableMultiComboboxProps<T extends {
|
|
4
|
+
id: string;
|
|
5
|
+
}> {
|
|
6
|
+
combobox: UsePagamioComboboxReturn<T>;
|
|
7
|
+
value: string[];
|
|
8
|
+
onChange: (ids: string[]) => void;
|
|
9
|
+
/**
|
|
10
|
+
* Pre-resolved selected options. Used to label chips for ids that aren't
|
|
11
|
+
* in the current top-N. If omitted, the component falls back to
|
|
12
|
+
* `combobox.restoredOptions` (configure `restoreIds` on the hook).
|
|
13
|
+
*/
|
|
14
|
+
selectedOptions?: readonly T[];
|
|
15
|
+
placeholder?: string;
|
|
16
|
+
emptyMessage?: string;
|
|
17
|
+
cappedMessage?: string;
|
|
18
|
+
loadingMessage?: string;
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
required?: boolean;
|
|
21
|
+
name?: string;
|
|
22
|
+
getLabel?: (option: T) => string;
|
|
23
|
+
renderOption?: (option: T, isSelected: boolean) => React.ReactNode;
|
|
24
|
+
renderChip?: (option: T, onRemove: () => void) => React.ReactNode;
|
|
25
|
+
className?: string;
|
|
26
|
+
triggerClassName?: string;
|
|
27
|
+
contentClassName?: string;
|
|
28
|
+
}
|
|
29
|
+
export declare function PagamioSearchableMultiCombobox<T extends {
|
|
30
|
+
id: string;
|
|
31
|
+
}>({ combobox, value, onChange, selectedOptions, placeholder, emptyMessage, cappedMessage, loadingMessage, disabled, required, name, getLabel, renderOption, renderChip, className, triggerClassName, contentClassName, }: PagamioSearchableMultiComboboxProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* PagamioSearchableMultiCombobox — server-driven, debounced, multi-select combobox.
|
|
4
|
+
*
|
|
5
|
+
* Sibling of PagamioSearchableCombobox. Differences:
|
|
6
|
+
* - `value` is `string[]`, `onChange` toggles ids.
|
|
7
|
+
* - The trigger shows chips for each selected option (resolved via the
|
|
8
|
+
* hook's `restoredOptions` OR `selectedOptions` prop).
|
|
9
|
+
* - The dropdown stays open while toggling — supports multi-pick flow.
|
|
10
|
+
*
|
|
11
|
+
* See .agent/conventions/lookup-endpoints.md (backend) for the contract.
|
|
12
|
+
*/
|
|
13
|
+
import { ChevronDownIcon, Cross2Icon } from '@radix-ui/react-icons';
|
|
14
|
+
import * as React from 'react';
|
|
15
|
+
import { cn } from '../../helpers';
|
|
16
|
+
import { Command, CommandEmpty, CommandInput, CommandItem, CommandList } from './Command';
|
|
17
|
+
import { Popover, PopoverContent, PopoverTrigger } from './Popover';
|
|
18
|
+
function defaultGetLabel(option) {
|
|
19
|
+
const o = option;
|
|
20
|
+
return o.name ?? option.id;
|
|
21
|
+
}
|
|
22
|
+
export function PagamioSearchableMultiCombobox({ combobox, value, onChange, selectedOptions, placeholder = 'Select…', emptyMessage = 'No results.', cappedMessage = 'Showing top results — refine your search.', loadingMessage = 'Loading…', disabled, required, name, getLabel = defaultGetLabel, renderOption, renderChip, className, triggerClassName, contentClassName, }) {
|
|
23
|
+
const [open, setOpen] = React.useState(false);
|
|
24
|
+
// Resolve labels for currently-selected ids. Caller's `selectedOptions`
|
|
25
|
+
// wins; otherwise look in the hook's options + restoredOptions.
|
|
26
|
+
const selectedById = React.useMemo(() => {
|
|
27
|
+
const map = new Map();
|
|
28
|
+
const sources = [selectedOptions ?? [], combobox.options, combobox.restoredOptions];
|
|
29
|
+
for (const source of sources) {
|
|
30
|
+
for (const option of source) {
|
|
31
|
+
if (!map.has(option.id))
|
|
32
|
+
map.set(option.id, option);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return map;
|
|
36
|
+
}, [selectedOptions, combobox.options, combobox.restoredOptions]);
|
|
37
|
+
const chips = React.useMemo(() => value.map((id) => selectedById.get(id)).filter((o) => o !== undefined), [value, selectedById]);
|
|
38
|
+
// Ids in `value` that we couldn't resolve to a label — render as raw id.
|
|
39
|
+
const unresolvedIds = React.useMemo(() => value.filter((id) => !selectedById.has(id)), [value, selectedById]);
|
|
40
|
+
const toggle = (id) => {
|
|
41
|
+
onChange(value.includes(id) ? value.filter((v) => v !== id) : [...value, id]);
|
|
42
|
+
};
|
|
43
|
+
const remove = (id) => onChange(value.filter((v) => v !== id));
|
|
44
|
+
const clearAll = (e) => {
|
|
45
|
+
e.stopPropagation();
|
|
46
|
+
onChange([]);
|
|
47
|
+
};
|
|
48
|
+
const hasSelection = value.length > 0;
|
|
49
|
+
return (_jsxs("div", { className: cn('w-full', className), children: [_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", role: "combobox", "aria-expanded": open, "aria-haspopup": "listbox", "aria-required": required, disabled: disabled, className: cn('flex min-h-9 w-full items-center justify-between whitespace-normal rounded-md border bg-background text-foreground px-2 py-1 text-sm shadow-sm ring-offset-background', 'focus:outline-none focus:ring-1 focus:ring-ring', 'disabled:cursor-not-allowed disabled:opacity-50', triggerClassName), children: [_jsx("div", { className: "flex flex-wrap gap-1 items-center flex-1 min-w-0", children: hasSelection ? (_jsxs(_Fragment, { children: [chips.map((option) => renderChip ? (_jsx(React.Fragment, { children: renderChip(option, () => remove(option.id)) }, option.id)) : (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-sm bg-muted px-1.5 py-0.5 text-xs text-foreground", children: [_jsx("span", { className: "line-clamp-1 max-w-[160px]", children: getLabel(option) }), _jsx("span", { role: "button", tabIndex: -1, "aria-label": `Remove ${getLabel(option)}`, onClick: (e) => {
|
|
50
|
+
e.stopPropagation();
|
|
51
|
+
remove(option.id);
|
|
52
|
+
}, onMouseDown: (e) => e.preventDefault(), className: "inline-flex h-3 w-3 items-center justify-center opacity-60 hover:opacity-100", children: _jsx(Cross2Icon, { className: "h-3 w-3" }) })] }, option.id))), unresolvedIds.map((id) => (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-sm bg-muted px-1.5 py-0.5 text-xs text-muted-foreground italic", children: [_jsx("span", { className: "line-clamp-1 max-w-[140px]", children: id }), _jsx("span", { role: "button", tabIndex: -1, "aria-label": "Remove", onClick: (e) => {
|
|
53
|
+
e.stopPropagation();
|
|
54
|
+
remove(id);
|
|
55
|
+
}, onMouseDown: (e) => e.preventDefault(), className: "inline-flex h-3 w-3 items-center justify-center opacity-60 hover:opacity-100", children: _jsx(Cross2Icon, { className: "h-3 w-3" }) })] }, id)))] })) : (_jsx("span", { className: "text-muted-foreground px-1", children: placeholder })) }), _jsxs("div", { className: "flex items-center", children: [hasSelection && !disabled ? (_jsx("span", { role: "button", tabIndex: -1, "aria-label": "Clear all", onClick: clearAll, onMouseDown: (e) => e.preventDefault(), className: "ml-2 inline-flex h-4 w-4 items-center justify-center rounded-sm opacity-60 hover:opacity-100", children: _jsx(Cross2Icon, { className: "h-3 w-3" }) })) : null, _jsx(ChevronDownIcon, { className: "ml-2 h-4 w-4 opacity-50 shrink-0" })] })] }) }), _jsx(PopoverContent, { align: "start", sideOffset: 4, className: cn('p-0 w-[var(--radix-popover-trigger-width)] min-w-[240px]', contentClassName), children: _jsxs(Command, { shouldFilter: false, children: [_jsx(CommandInput, { placeholder: placeholder, value: combobox.search, onValueChange: combobox.setSearch }), _jsx(CommandList, { children: combobox.isLoading && combobox.options.length === 0 ? (_jsx("div", { className: "py-6 text-center text-sm text-muted-foreground", children: loadingMessage })) : combobox.error ? (_jsx("div", { className: "py-6 text-center text-sm text-destructive", children: combobox.error.message || 'Failed to load options.' })) : combobox.options.length === 0 ? (_jsx(CommandEmpty, { children: emptyMessage })) : (_jsxs(_Fragment, { children: [combobox.options.map((option) => {
|
|
56
|
+
const isSelected = value.includes(option.id);
|
|
57
|
+
return (_jsx(CommandItem, { value: option.id, onSelect: () => toggle(option.id), children: renderOption ? (renderOption(option, isSelected)) : (_jsxs(_Fragment, { children: [_jsx("span", { className: cn('mr-2 inline-flex h-4 w-4 items-center justify-center rounded-sm border', isSelected ? 'bg-primary text-primary-foreground border-primary' : 'border-input'), children: isSelected ? '✓' : null }), _jsx("span", { className: "flex-1 line-clamp-1", children: getLabel(option) })] })) }, option.id));
|
|
58
|
+
}), combobox.isCapped ? (_jsx("div", { className: "border-t px-3 py-2 text-xs text-muted-foreground", children: cappedMessage })) : null] })) })] }) })] }), name ? value.map((id) => _jsx("input", { type: "hidden", name: name, value: id }, id)) : null] }));
|
|
59
|
+
}
|
|
@@ -6,6 +6,8 @@ export * from './Avatar';
|
|
|
6
6
|
export * from './Command';
|
|
7
7
|
export * from './Dialog';
|
|
8
8
|
export * from './Form';
|
|
9
|
+
export * from './PagamioSearchableCombobox';
|
|
10
|
+
export * from './PagamioSearchableMultiCombobox';
|
|
9
11
|
export * from './Popover';
|
|
10
12
|
export * from './Radio';
|
|
11
13
|
export * from './Select';
|
|
@@ -5,6 +5,8 @@ export * from './Avatar';
|
|
|
5
5
|
export * from './Command';
|
|
6
6
|
export * from './Dialog';
|
|
7
7
|
export * from './Form';
|
|
8
|
+
export * from './PagamioSearchableCombobox';
|
|
9
|
+
export * from './PagamioSearchableMultiCombobox';
|
|
8
10
|
export * from './Popover';
|
|
9
11
|
export * from './Radio';
|
|
10
12
|
export * from './Select';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Field } from '../../../types';
|
|
2
|
+
interface SearchableComboboxInputProps {
|
|
3
|
+
field: Field;
|
|
4
|
+
error?: {
|
|
5
|
+
message?: string;
|
|
6
|
+
};
|
|
7
|
+
value?: string | null;
|
|
8
|
+
onChange?: (value: string | null) => void;
|
|
9
|
+
onBlur?: () => void;
|
|
10
|
+
name?: string;
|
|
11
|
+
}
|
|
12
|
+
declare const SearchableComboboxInput: import("react").ForwardRefExoticComponent<SearchableComboboxInputProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
13
|
+
export default SearchableComboboxInput;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Form-engine input that wires `usePagamioCombobox` + `PagamioSearchableCombobox`
|
|
4
|
+
* into the form-engine's field declaration shape.
|
|
5
|
+
*
|
|
6
|
+
* Field declaration:
|
|
7
|
+
*
|
|
8
|
+
* {
|
|
9
|
+
* name: "storeId",
|
|
10
|
+
* label: "Store",
|
|
11
|
+
* type: "searchable-combobox",
|
|
12
|
+
* source: {
|
|
13
|
+
* fetcher: (params) => getStoresLookup({ businessUnitId, ...params }),
|
|
14
|
+
* queryKey: ["stores", "lookup", businessUnitId],
|
|
15
|
+
* },
|
|
16
|
+
* placeholder: "Search stores…",
|
|
17
|
+
* }
|
|
18
|
+
*/
|
|
19
|
+
import { forwardRef, useMemo } from 'react';
|
|
20
|
+
import { PagamioSearchableCombobox } from '../../../../components/ui/PagamioSearchableCombobox';
|
|
21
|
+
import { usePagamioCombobox } from '../../../../shared/hooks/usePagamioCombobox';
|
|
22
|
+
const SearchableComboboxInput = forwardRef(({ field, value, onChange }, ref) => {
|
|
23
|
+
const source = field.source;
|
|
24
|
+
if (!source) {
|
|
25
|
+
// Misconfigured field. Render a stable no-op so the form doesn't crash.
|
|
26
|
+
// The convention requires `source` for searchable-combobox.
|
|
27
|
+
return (_jsxs("div", { ref: ref, className: "rounded-md border border-destructive bg-destructive/10 px-3 py-2 text-sm text-destructive", children: ["Field ", _jsx("code", { children: field.name }), " of type ", _jsx("code", { children: "searchable-combobox" }), " is missing the ", _jsx("code", { children: "source" }), ' ', "spec."] }));
|
|
28
|
+
}
|
|
29
|
+
const restoreIds = useMemo(() => (value ? [value] : undefined), [value]);
|
|
30
|
+
const combobox = usePagamioCombobox({
|
|
31
|
+
queryKey: source.queryKey,
|
|
32
|
+
queryFn: source.fetcher,
|
|
33
|
+
pageSize: source.pageSize,
|
|
34
|
+
debounceMs: source.debounceMs,
|
|
35
|
+
restoreIds,
|
|
36
|
+
});
|
|
37
|
+
return (_jsx("div", { ref: ref, children: _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 }) }));
|
|
38
|
+
});
|
|
39
|
+
SearchableComboboxInput.displayName = 'SearchableComboboxInput';
|
|
40
|
+
export default SearchableComboboxInput;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Field } from '../../../types';
|
|
2
|
+
interface SearchableMultiComboboxInputProps {
|
|
3
|
+
field: Field;
|
|
4
|
+
error?: {
|
|
5
|
+
message?: string;
|
|
6
|
+
};
|
|
7
|
+
value?: string[];
|
|
8
|
+
onChange?: (value: string[]) => void;
|
|
9
|
+
onBlur?: () => void;
|
|
10
|
+
name?: string;
|
|
11
|
+
}
|
|
12
|
+
declare const SearchableMultiComboboxInput: import("react").ForwardRefExoticComponent<SearchableMultiComboboxInputProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
13
|
+
export default SearchableMultiComboboxInput;
|
package/lib/form-engine/components/inputs/searchable-multi-combobox/SearchableMultiComboboxInput.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Form-engine input that wires `usePagamioCombobox` +
|
|
4
|
+
* `PagamioSearchableMultiCombobox` into the form-engine's field declaration shape.
|
|
5
|
+
*
|
|
6
|
+
* Field declaration:
|
|
7
|
+
*
|
|
8
|
+
* {
|
|
9
|
+
* name: "productIds",
|
|
10
|
+
* label: "Products",
|
|
11
|
+
* type: "searchable-multi-combobox",
|
|
12
|
+
* source: {
|
|
13
|
+
* fetcher: (params) => getProductsLookup({ organizationId, ...params }),
|
|
14
|
+
* queryKey: ["products", "lookup", organizationId],
|
|
15
|
+
* },
|
|
16
|
+
* placeholder: "Add products…",
|
|
17
|
+
* }
|
|
18
|
+
*/
|
|
19
|
+
import { forwardRef, useMemo } from 'react';
|
|
20
|
+
import { PagamioSearchableMultiCombobox } from '../../../../components/ui/PagamioSearchableMultiCombobox';
|
|
21
|
+
import { usePagamioCombobox } from '../../../../shared/hooks/usePagamioCombobox';
|
|
22
|
+
const SearchableMultiComboboxInput = forwardRef(({ field, value, onChange }, ref) => {
|
|
23
|
+
const source = field.source;
|
|
24
|
+
if (!source) {
|
|
25
|
+
return (_jsxs("div", { ref: ref, className: "rounded-md border border-destructive bg-destructive/10 px-3 py-2 text-sm text-destructive", children: ["Field ", _jsx("code", { children: field.name }), " of type ", _jsx("code", { children: "searchable-multi-combobox" }), " is missing the", ' ', _jsx("code", { children: "source" }), " spec."] }));
|
|
26
|
+
}
|
|
27
|
+
const restoreIds = useMemo(() => (value && value.length > 0 ? value : undefined), [value]);
|
|
28
|
+
const combobox = usePagamioCombobox({
|
|
29
|
+
queryKey: source.queryKey,
|
|
30
|
+
queryFn: source.fetcher,
|
|
31
|
+
pageSize: source.pageSize,
|
|
32
|
+
debounceMs: source.debounceMs,
|
|
33
|
+
restoreIds,
|
|
34
|
+
});
|
|
35
|
+
return (_jsx("div", { ref: ref, children: _jsx(PagamioSearchableMultiCombobox, { combobox: combobox, value: value ?? [], onChange: (ids) => onChange?.(ids), placeholder: field.placeholder, disabled: field.disabled, required: field.validation?.required ? true : undefined, name: field.name, getLabel: source.getLabel }) }));
|
|
36
|
+
});
|
|
37
|
+
SearchableMultiComboboxInput.displayName = 'SearchableMultiComboboxInput';
|
|
38
|
+
export default SearchableMultiComboboxInput;
|
|
@@ -31,7 +31,7 @@ export const setupInputRegistry = async () => {
|
|
|
31
31
|
return getRegistryPromise();
|
|
32
32
|
};
|
|
33
33
|
const doSetup = async () => {
|
|
34
|
-
const [{ default: CardExpiryInput }, { default: CheckboxInput }, { default: ColorInput }, { default: CreditCardInput }, { default: DateInput }, { default: EmailInput }, { default: LabelInput }, { default: MultiSelectInputComponent }, { default: NumberInput }, { default: PasswordInput }, { default: RadioInput }, { default: SelectInput }, { default: TextareaInputFW }, { default: TimeInput }, { default: ToggleSwitchInput }, { default: UploadFieldForm }, { default: PhoneInput }, { default: SearchableMultiSelectInput },] = await Promise.all([
|
|
34
|
+
const [{ default: CardExpiryInput }, { default: CheckboxInput }, { default: ColorInput }, { default: CreditCardInput }, { default: DateInput }, { default: EmailInput }, { default: LabelInput }, { default: MultiSelectInputComponent }, { default: NumberInput }, { default: PasswordInput }, { default: RadioInput }, { default: SelectInput }, { default: TextareaInputFW }, { default: TimeInput }, { default: ToggleSwitchInput }, { default: UploadFieldForm }, { default: PhoneInput }, { default: SearchableMultiSelectInput }, { default: SearchableComboboxInput }, { default: SearchableMultiComboboxInput },] = await Promise.all([
|
|
35
35
|
import('../components/inputs/card-expiry-input/CardExpiryInput'),
|
|
36
36
|
import('../components/inputs/checkbox-input/CheckboxInput'),
|
|
37
37
|
import('../components/inputs/color-input/ColorInput'),
|
|
@@ -50,6 +50,8 @@ const doSetup = async () => {
|
|
|
50
50
|
import('../components/inputs/upload-field/UploadFieldForm'),
|
|
51
51
|
import('../../components/ui/PhoneInput'),
|
|
52
52
|
import('../components/inputs/searchable-multi-select/SearchableMultiSelectInput'),
|
|
53
|
+
import('../components/inputs/searchable-combobox/SearchableComboboxInput'),
|
|
54
|
+
import('../components/inputs/searchable-multi-combobox/SearchableMultiComboboxInput'),
|
|
53
55
|
]);
|
|
54
56
|
registerInput('card-expiry-input', CardExpiryInput);
|
|
55
57
|
registerInput('checkbox', CheckboxInput);
|
|
@@ -69,5 +71,7 @@ const doSetup = async () => {
|
|
|
69
71
|
registerInput('textarea', TextareaInputFW);
|
|
70
72
|
registerInput('time', TimeInput);
|
|
71
73
|
registerInput('searchable-multi-select', SearchableMultiSelectInput);
|
|
74
|
+
registerInput('searchable-combobox', SearchableComboboxInput);
|
|
75
|
+
registerInput('searchable-multi-combobox', SearchableMultiComboboxInput);
|
|
72
76
|
registryDone = true;
|
|
73
77
|
};
|
|
@@ -130,7 +130,45 @@ export interface DependencyValidation {
|
|
|
130
130
|
/**
|
|
131
131
|
* Available field types supported by the form engine
|
|
132
132
|
*/
|
|
133
|
-
export type FieldType = 'card-expiry-input' | 'checkbox' | 'color' | 'credit-card' | 'date' | 'email' | 'file' | 'multi-select' | 'number' | 'password' | 'radio' | 'searchable-multi-select' | 'select' | 'switch' | 'tel' | 'text' | 'textarea' | 'time';
|
|
133
|
+
export type FieldType = 'card-expiry-input' | 'checkbox' | 'color' | 'credit-card' | 'date' | 'email' | 'file' | 'multi-select' | 'number' | 'password' | 'radio' | 'searchable-combobox' | 'searchable-multi-combobox' | 'searchable-multi-select' | 'select' | 'switch' | 'tel' | 'text' | 'textarea' | 'time';
|
|
134
|
+
/**
|
|
135
|
+
* Source contract for server-driven combobox field types
|
|
136
|
+
* (`searchable-combobox`, `searchable-multi-combobox`).
|
|
137
|
+
*
|
|
138
|
+
* The form engine builds a `queryFn` from this spec and feeds it to
|
|
139
|
+
* `usePagamioCombobox`. See `.agent/conventions/lookup-endpoints.md`
|
|
140
|
+
* (backend) for the endpoint contract.
|
|
141
|
+
*/
|
|
142
|
+
export interface ComboboxOption {
|
|
143
|
+
id: string;
|
|
144
|
+
name: string;
|
|
145
|
+
[key: string]: unknown;
|
|
146
|
+
}
|
|
147
|
+
export interface ComboboxSourceSpec {
|
|
148
|
+
/**
|
|
149
|
+
* Function that fetches lookup results. The form engine calls this with
|
|
150
|
+
* `{ search, limit, ids }` per the lookup contract.
|
|
151
|
+
* Returns `{ data: T[] }` or `null`.
|
|
152
|
+
*/
|
|
153
|
+
fetcher: (params: {
|
|
154
|
+
search?: string;
|
|
155
|
+
limit?: number;
|
|
156
|
+
ids?: string[];
|
|
157
|
+
}) => Promise<{
|
|
158
|
+
data: ComboboxOption[];
|
|
159
|
+
} | null | undefined>;
|
|
160
|
+
/**
|
|
161
|
+
* Query key prefix. Combined with the fetched-search and ids inside
|
|
162
|
+
* `usePagamioCombobox`. Should include scope (organizationId, etc.).
|
|
163
|
+
*/
|
|
164
|
+
queryKey: readonly unknown[];
|
|
165
|
+
/** Optional: how to render the option label. Default: `option.name`. */
|
|
166
|
+
getLabel?: (option: ComboboxOption) => string;
|
|
167
|
+
/** Optional: per-field page size override. Default 20. */
|
|
168
|
+
pageSize?: number;
|
|
169
|
+
/** Optional: per-field debounce override (ms). Default 300. */
|
|
170
|
+
debounceMs?: number;
|
|
171
|
+
}
|
|
134
172
|
/**
|
|
135
173
|
* Defines the structure of a form field
|
|
136
174
|
*/
|
|
@@ -172,6 +210,11 @@ export interface Field {
|
|
|
172
210
|
/** callback for onChange event */
|
|
173
211
|
onChange?: (value: string) => void;
|
|
174
212
|
isHidden?: boolean;
|
|
213
|
+
/**
|
|
214
|
+
* Source spec for `searchable-combobox` / `searchable-multi-combobox` types.
|
|
215
|
+
* Tells the form engine how to fetch lookup results.
|
|
216
|
+
*/
|
|
217
|
+
source?: ComboboxSourceSpec;
|
|
175
218
|
}
|
|
176
219
|
export interface DependentFieldUpdate {
|
|
177
220
|
field: string;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* usePagamioCombobox — state hook for server-driven searchable combobox UIs.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the shape of `usePagamioTable`. The hook owns:
|
|
5
|
+
* - The raw + debounced search string
|
|
6
|
+
* - Top-N options fetched from the server (one query)
|
|
7
|
+
* - Optional restoration of full records for a known set of ids (second query)
|
|
8
|
+
* - `isCapped` (true when the server returned a full page; user should refine)
|
|
9
|
+
*
|
|
10
|
+
* Rendering is the caller's concern (see PagamioSearchableCombobox).
|
|
11
|
+
*
|
|
12
|
+
* Frontend contract (`/<resource>/lookup` endpoint):
|
|
13
|
+
* GET /api/v1/<resource>/lookup?search=&limit=&ids=&<scope>
|
|
14
|
+
* → { success, data: T[], timestamp }
|
|
15
|
+
* Slim payload (`{ id, name, ...minimal extras }`), no `meta`, no pagination.
|
|
16
|
+
* See `.agent/conventions/lookup-endpoints.md` (backend repo).
|
|
17
|
+
*/
|
|
18
|
+
import { type QueryKey } from '@tanstack/react-query';
|
|
19
|
+
export interface ComboboxQueryParams {
|
|
20
|
+
search?: string;
|
|
21
|
+
limit?: number;
|
|
22
|
+
ids?: string[];
|
|
23
|
+
}
|
|
24
|
+
export interface ComboboxFetchResult<T> {
|
|
25
|
+
data: T[];
|
|
26
|
+
}
|
|
27
|
+
export interface UsePagamioComboboxConfig<T extends {
|
|
28
|
+
id: string;
|
|
29
|
+
}> {
|
|
30
|
+
/** TanStack Query key. Should include scope params (orgId, buId). */
|
|
31
|
+
queryKey: QueryKey;
|
|
32
|
+
/**
|
|
33
|
+
* The fetcher. Called with `{ search, limit }` for top-N lookups and
|
|
34
|
+
* separately with `{ ids }` for restoring selected-state labels.
|
|
35
|
+
* Should return `{ data: T[] }` or `null`. The hook unwraps `data`.
|
|
36
|
+
*/
|
|
37
|
+
queryFn: (params: ComboboxQueryParams) => Promise<ComboboxFetchResult<T> | null | undefined>;
|
|
38
|
+
/**
|
|
39
|
+
* Whether the underlying queries are allowed to run. Same semantics as
|
|
40
|
+
* TanStack Query's `enabled`. Use for dependent pickers (e.g. store
|
|
41
|
+
* picker needs business unit selected first).
|
|
42
|
+
*/
|
|
43
|
+
enabled?: boolean;
|
|
44
|
+
/** Debounce ms for the search input. Default 300. */
|
|
45
|
+
debounceMs?: number;
|
|
46
|
+
/**
|
|
47
|
+
* How many rows to request per top-N fetch. Default 20. Should be ≤ the
|
|
48
|
+
* backend's hard cap (e.g. 50). When the server returns exactly this
|
|
49
|
+
* many rows, `isCapped` flips true to encourage the user to refine.
|
|
50
|
+
*/
|
|
51
|
+
pageSize?: number;
|
|
52
|
+
/** TanStack Query stale time (ms). Default 60_000. */
|
|
53
|
+
staleTime?: number;
|
|
54
|
+
/**
|
|
55
|
+
* Ids whose full records to fetch even if they aren't in the current
|
|
56
|
+
* search results. Used to render chips/labels for pre-selected values.
|
|
57
|
+
*/
|
|
58
|
+
restoreIds?: readonly string[];
|
|
59
|
+
}
|
|
60
|
+
export interface UsePagamioComboboxReturn<T extends {
|
|
61
|
+
id: string;
|
|
62
|
+
}> {
|
|
63
|
+
/** Raw input value (updates on every keystroke). */
|
|
64
|
+
search: string;
|
|
65
|
+
setSearch: (value: string) => void;
|
|
66
|
+
/** Debounced input — drives the actual server query. */
|
|
67
|
+
debouncedSearch: string;
|
|
68
|
+
/** Top-N options for the current debounced search. */
|
|
69
|
+
options: T[];
|
|
70
|
+
/** Records resolved via `restoreIds`. Empty array when no ids passed. */
|
|
71
|
+
restoredOptions: T[];
|
|
72
|
+
/** First-fetch loading. */
|
|
73
|
+
isLoading: boolean;
|
|
74
|
+
/** Any background refetch in flight. */
|
|
75
|
+
isFetching: boolean;
|
|
76
|
+
/** True iff the server returned `pageSize` rows — there may be more. */
|
|
77
|
+
isCapped: boolean;
|
|
78
|
+
/** Underlying error, if any. */
|
|
79
|
+
error: Error | null;
|
|
80
|
+
}
|
|
81
|
+
export declare function usePagamioCombobox<T extends {
|
|
82
|
+
id: string;
|
|
83
|
+
}>({ queryKey, queryFn, enabled, debounceMs, pageSize, staleTime, restoreIds, }: UsePagamioComboboxConfig<T>): UsePagamioComboboxReturn<T>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* usePagamioCombobox — state hook for server-driven searchable combobox UIs.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the shape of `usePagamioTable`. The hook owns:
|
|
5
|
+
* - The raw + debounced search string
|
|
6
|
+
* - Top-N options fetched from the server (one query)
|
|
7
|
+
* - Optional restoration of full records for a known set of ids (second query)
|
|
8
|
+
* - `isCapped` (true when the server returned a full page; user should refine)
|
|
9
|
+
*
|
|
10
|
+
* Rendering is the caller's concern (see PagamioSearchableCombobox).
|
|
11
|
+
*
|
|
12
|
+
* Frontend contract (`/<resource>/lookup` endpoint):
|
|
13
|
+
* GET /api/v1/<resource>/lookup?search=&limit=&ids=&<scope>
|
|
14
|
+
* → { success, data: T[], timestamp }
|
|
15
|
+
* Slim payload (`{ id, name, ...minimal extras }`), no `meta`, no pagination.
|
|
16
|
+
* See `.agent/conventions/lookup-endpoints.md` (backend repo).
|
|
17
|
+
*/
|
|
18
|
+
import { keepPreviousData, useQuery } from '@tanstack/react-query';
|
|
19
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
20
|
+
// ─── Hook ───────────────────────────────────────────────────────────────
|
|
21
|
+
const DEFAULT_DEBOUNCE_MS = 300;
|
|
22
|
+
const DEFAULT_PAGE_SIZE = 20;
|
|
23
|
+
const DEFAULT_STALE_TIME = 60_000;
|
|
24
|
+
export function usePagamioCombobox({ queryKey, queryFn, enabled = true, debounceMs = DEFAULT_DEBOUNCE_MS, pageSize = DEFAULT_PAGE_SIZE, staleTime = DEFAULT_STALE_TIME, restoreIds, }) {
|
|
25
|
+
const [search, setSearch] = useState('');
|
|
26
|
+
const [debouncedSearch, setDebouncedSearch] = useState('');
|
|
27
|
+
// Debounce raw input -> debounced value.
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (search === debouncedSearch)
|
|
30
|
+
return;
|
|
31
|
+
const timer = setTimeout(() => setDebouncedSearch(search), debounceMs);
|
|
32
|
+
return () => clearTimeout(timer);
|
|
33
|
+
}, [search, debouncedSearch, debounceMs]);
|
|
34
|
+
// ── Primary query: top-N for current debounced search ────────────────
|
|
35
|
+
const topNQuery = useQuery({
|
|
36
|
+
queryKey: [...queryKey, 'lookup', { search: debouncedSearch, limit: pageSize }],
|
|
37
|
+
queryFn: () => queryFn({
|
|
38
|
+
search: debouncedSearch || undefined,
|
|
39
|
+
limit: pageSize,
|
|
40
|
+
}),
|
|
41
|
+
enabled,
|
|
42
|
+
staleTime,
|
|
43
|
+
placeholderData: keepPreviousData,
|
|
44
|
+
});
|
|
45
|
+
const options = useMemo(() => topNQuery.data?.data ?? [], [topNQuery.data]);
|
|
46
|
+
const isCapped = options.length >= pageSize;
|
|
47
|
+
// ── Secondary query: resolve labels for known ids ────────────────────
|
|
48
|
+
// Sorted to keep the cache key stable.
|
|
49
|
+
const stableRestoreIds = useMemo(() => {
|
|
50
|
+
if (!restoreIds || restoreIds.length === 0)
|
|
51
|
+
return undefined;
|
|
52
|
+
return [...restoreIds].sort();
|
|
53
|
+
}, [restoreIds]);
|
|
54
|
+
const restoreQuery = useQuery({
|
|
55
|
+
queryKey: [...queryKey, 'lookup', 'restore', stableRestoreIds],
|
|
56
|
+
queryFn: () => queryFn({ ids: stableRestoreIds }),
|
|
57
|
+
enabled: enabled && !!stableRestoreIds && stableRestoreIds.length > 0,
|
|
58
|
+
staleTime,
|
|
59
|
+
});
|
|
60
|
+
const restoredOptions = useMemo(() => restoreQuery.data?.data ?? [], [restoreQuery.data]);
|
|
61
|
+
return {
|
|
62
|
+
search,
|
|
63
|
+
setSearch,
|
|
64
|
+
debouncedSearch,
|
|
65
|
+
options,
|
|
66
|
+
restoredOptions,
|
|
67
|
+
isLoading: topNQuery.isLoading || restoreQuery.isLoading,
|
|
68
|
+
isFetching: topNQuery.isFetching || restoreQuery.isFetching,
|
|
69
|
+
isCapped,
|
|
70
|
+
error: topNQuery.error ?? restoreQuery.error ?? null,
|
|
71
|
+
};
|
|
72
|
+
}
|
package/lib/styles.css
CHANGED
|
@@ -1450,6 +1450,9 @@ video {
|
|
|
1450
1450
|
.w-\[52px\] {
|
|
1451
1451
|
width: 52px;
|
|
1452
1452
|
}
|
|
1453
|
+
.w-\[var\(--radix-popover-trigger-width\)\] {
|
|
1454
|
+
width: var(--radix-popover-trigger-width);
|
|
1455
|
+
}
|
|
1453
1456
|
.w-auto {
|
|
1454
1457
|
width: auto;
|
|
1455
1458
|
}
|
|
@@ -1485,6 +1488,9 @@ video {
|
|
|
1485
1488
|
.min-w-\[20px\] {
|
|
1486
1489
|
min-width: 20px;
|
|
1487
1490
|
}
|
|
1491
|
+
.min-w-\[240px\] {
|
|
1492
|
+
min-width: 240px;
|
|
1493
|
+
}
|
|
1488
1494
|
.min-w-\[52px\] {
|
|
1489
1495
|
min-width: 52px;
|
|
1490
1496
|
}
|
|
@@ -1518,6 +1524,9 @@ video {
|
|
|
1518
1524
|
.max-w-\[100vw\] {
|
|
1519
1525
|
max-width: 100vw;
|
|
1520
1526
|
}
|
|
1527
|
+
.max-w-\[140px\] {
|
|
1528
|
+
max-width: 140px;
|
|
1529
|
+
}
|
|
1521
1530
|
.max-w-\[160px\] {
|
|
1522
1531
|
max-width: 160px;
|
|
1523
1532
|
}
|
|
@@ -1981,6 +1990,9 @@ video {
|
|
|
1981
1990
|
text-overflow: ellipsis;
|
|
1982
1991
|
white-space: nowrap;
|
|
1983
1992
|
}
|
|
1993
|
+
.whitespace-normal {
|
|
1994
|
+
white-space: normal;
|
|
1995
|
+
}
|
|
1984
1996
|
.whitespace-nowrap {
|
|
1985
1997
|
white-space: nowrap;
|
|
1986
1998
|
}
|
|
@@ -2839,6 +2851,10 @@ video {
|
|
|
2839
2851
|
padding-left: 0px;
|
|
2840
2852
|
padding-right: 0px;
|
|
2841
2853
|
}
|
|
2854
|
+
.px-1 {
|
|
2855
|
+
padding-left: 0.25rem;
|
|
2856
|
+
padding-right: 0.25rem;
|
|
2857
|
+
}
|
|
2842
2858
|
.px-1\.5 {
|
|
2843
2859
|
padding-left: 0.375rem;
|
|
2844
2860
|
padding-right: 0.375rem;
|
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.
|
|
4
|
+
"version": "0.8.333",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
7
7
|
"provenance": false
|