@caratlogic/design-system 0.1.37 → 0.1.39
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/dist/components/molecules/Filters/ActiveFilterChips/ActiveFilterChips.d.ts +1 -1
- package/dist/components/molecules/Filters/ActiveFilterChips/ActiveFilterChips.d.ts.map +1 -1
- package/dist/components/molecules/Filters/ActiveFilterChips/ActiveFilterChips.types.d.ts +2 -0
- package/dist/components/molecules/Filters/ActiveFilterChips/ActiveFilterChips.types.d.ts.map +1 -1
- package/dist/components/molecules/Filters/ActiveFilterChips/ActiveFilterChips.utils.d.ts +1 -0
- package/dist/components/molecules/Filters/ActiveFilterChips/ActiveFilterChips.utils.d.ts.map +1 -1
- package/dist/components/molecules/Filters/FilterPanel/FilterPanel.utils.d.ts.map +1 -1
- package/dist/components/molecules/Filters/Filters.utils.d.ts.map +1 -1
- package/dist/components/molecules/Filters/PickListFilter/PickListFilter.d.ts.map +1 -1
- package/dist/components/molecules/Filters/PickListFilter/PickListFilter.utils.d.ts +2 -1
- package/dist/components/molecules/Filters/PickListFilter/PickListFilter.utils.d.ts.map +1 -1
- package/dist/components/molecules/Filters/ScaleRangeFilter/ScaleRangeFilter.d.ts +2 -2
- package/dist/components/molecules/Filters/ScaleRangeFilter/ScaleRangeFilter.d.ts.map +1 -1
- package/dist/components/molecules/Filters/ScaleRangeFilter/ScaleRangeFilter.types.d.ts +4 -1
- package/dist/components/molecules/Filters/ScaleRangeFilter/ScaleRangeFilter.types.d.ts.map +1 -1
- package/dist/components/molecules/Filters/ScaleRangeFilter/ScaleRangeFilter.utils.d.ts +2 -0
- package/dist/components/molecules/Filters/ScaleRangeFilter/ScaleRangeFilter.utils.d.ts.map +1 -1
- package/dist/components/molecules/Filters/ScaleRangeFilter/index.d.ts +1 -1
- package/dist/components/molecules/Filters/ScaleRangeFilter/index.d.ts.map +1 -1
- package/dist/components/organisms/Filters/FilterField/FilterField.d.ts.map +1 -1
- package/dist/components/organisms/Filters/FilterSurface/FilterSurface.accessibility.d.ts +1 -2
- package/dist/components/organisms/Filters/FilterSurface/FilterSurface.accessibility.d.ts.map +1 -1
- package/dist/components/organisms/Filters/FilterSurface/FilterSurface.d.ts +2 -2
- package/dist/components/organisms/Filters/FilterSurface/FilterSurface.d.ts.map +1 -1
- package/dist/components/organisms/Filters/FilterSurface/FilterSurface.types.d.ts +7 -7
- package/dist/components/organisms/Filters/FilterSurface/FilterSurface.types.d.ts.map +1 -1
- package/dist/index.cjs.js +124 -90
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +124 -90
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs.js
CHANGED
|
@@ -15514,7 +15514,10 @@ function formatRuleValue(value, unit) {
|
|
|
15514
15514
|
const number = value.toLocaleString('en-US', { maximumFractionDigits: 2 });
|
|
15515
15515
|
if (!unit)
|
|
15516
15516
|
return number;
|
|
15517
|
-
|
|
15517
|
+
// Symbols sit right after the number (`60%`, `34°`); words take a space (`2 ct`).
|
|
15518
|
+
return unit === '%' || unit === '°'
|
|
15519
|
+
? `${number}${unit}`
|
|
15520
|
+
: `${number} ${unit}`;
|
|
15518
15521
|
}
|
|
15519
15522
|
/** The help popover's rule list for a column's typical range; rules use short numbers (`1k`) that the field accepts as typed. */
|
|
15520
15523
|
function buildNumberRuleHelp({ low, high, exact, unit, } = DEFAULT_NUMBER_RULE_EXAMPLES) {
|
|
@@ -15737,7 +15740,7 @@ function getOptionRowClassName(selected = false, className) {
|
|
|
15737
15740
|
}
|
|
15738
15741
|
/** A selectable pill (chip options, range presets); one height and shape everywhere. */
|
|
15739
15742
|
function getPillClassName(selected) {
|
|
15740
|
-
return getSelectableOptionClassName(selected, 'inline-flex h-8 items-center gap-1.5 rounded-
|
|
15743
|
+
return getSelectableOptionClassName(selected, 'inline-flex h-8 items-center gap-1.5 rounded-sm px-3');
|
|
15741
15744
|
}
|
|
15742
15745
|
/** Search box above a list (pick lists, saved searches); matches the inputs' height and corners. */
|
|
15743
15746
|
const FILTER_SEARCH_BOX_CLASS_NAME = 'flex h-9 items-center gap-2 rounded-md border border-input bg-card px-3 focus-within:border-primary focus-within:ring-2 focus-within:ring-primary/30';
|
|
@@ -15769,8 +15772,10 @@ function getPanelClassName(variant, active, className) {
|
|
|
15769
15772
|
return cn('flex min-w-0 flex-col gap-3', variant === 'card' && 'rounded-xl border bg-card p-4', variant === 'card' && (active ? 'border-primary' : 'border-border'), variant === 'popover' && 'p-3', className);
|
|
15770
15773
|
}
|
|
15771
15774
|
function getTitleClassName(variant) {
|
|
15772
|
-
return cn('flex items-center gap-2 text-foreground',
|
|
15773
|
-
|
|
15775
|
+
return cn('flex items-center gap-2 text-foreground',
|
|
15776
|
+
// A section title is a field name: between the card title and grey helper text.
|
|
15777
|
+
variant === 'section'
|
|
15778
|
+
? 'text-xs font-medium text-foreground/70'
|
|
15774
15779
|
: 'text-sm font-semibold');
|
|
15775
15780
|
}
|
|
15776
15781
|
|
|
@@ -15918,14 +15923,15 @@ const LIST_COLUMN_MIN_WIDTH = '10rem';
|
|
|
15918
15923
|
const getOptionsScrollClassName = (fill) => fill ? OPTIONS_FILL_CLASS_NAME : OPTIONS_SCROLL_CLASS_NAME;
|
|
15919
15924
|
const LIST_ROW_CLASS_NAME = getOptionRowClassName();
|
|
15920
15925
|
const COUNT_BADGE_TONES = {
|
|
15921
|
-
neutral: 'bg-
|
|
15922
|
-
tinted: 'bg-primary/
|
|
15923
|
-
solid: 'bg-primary-foreground/20 text-primary-foreground',
|
|
15926
|
+
neutral: 'border-primary/30 bg-primary/5 text-primary',
|
|
15927
|
+
tinted: 'border-primary/30 bg-primary/5 text-primary',
|
|
15928
|
+
solid: 'border-primary-foreground/60 bg-primary-foreground/20 text-primary-foreground',
|
|
15924
15929
|
};
|
|
15925
15930
|
/** How many items carry the option, as a small round badge at the right. */
|
|
15926
|
-
const getCountBadgeClassName = (tone) => cn('inline-flex h-5 min-w-
|
|
15931
|
+
const getCountBadgeClassName = (tone) => cn('inline-flex h-5 min-w-7 shrink-0 items-center justify-center rounded-full border px-1.5 text-xs font-medium tabular-nums', COUNT_BADGE_TONES[tone]);
|
|
15927
15932
|
const COUNT_BADGE_CLASS_NAME = getCountBadgeClassName('neutral');
|
|
15928
|
-
|
|
15933
|
+
/** Plain digits (no thousands separators), so the badge stays compact. */
|
|
15934
|
+
const formatOptionCount = (count) => String(count);
|
|
15929
15935
|
/** Picking a value: a single-choice list replaces (or clears) it, a multi-choice list toggles it. */
|
|
15930
15936
|
const pickValue = (values, value, single) => {
|
|
15931
15937
|
if (!single)
|
|
@@ -15981,7 +15987,7 @@ function PickListFilter({ options, value, onChange, ariaLabel, layout = 'list',
|
|
|
15981
15987
|
if (layout === 'tiles') {
|
|
15982
15988
|
return (jsxRuntime.jsx(ScrollFade, { role: "group", "aria-label": ariaLabel, wrapperClassName: wrapperClassName, className: cn('grid content-start gap-1.5', scrollClassName), style: getGridColumnsStyle(columns), children: visibleOptions.map((option) => (jsxRuntime.jsxs("button", { type: "button", "aria-pressed": value.includes(option.value), onClick: () => toggle(option), className: getTileClassName(value.includes(option.value), option.count !== undefined), children: [jsxRuntime.jsx(TruncatedLabel, { text: option.label }), option.count !== undefined && (jsxRuntime.jsx("span", { className: getCountBadgeClassName(value.includes(option.value) ? 'tinted' : 'neutral'), children: formatOptionCount(option.count) }))] }, option.value))) }));
|
|
15983
15989
|
}
|
|
15984
|
-
return (jsxRuntime.jsx(ScrollFade, { role: "group", "aria-label": ariaLabel, wrapperClassName: wrapperClassName, className: cn('flex flex-wrap content-start gap-1.5', scrollClassName), children: visibleOptions.map((option) => (jsxRuntime.jsxs("button", { type: "button", "aria-pressed": value.includes(option.value), onClick: () => toggle(option), className: getChipClassName(value.includes(option.value)), children: [jsxRuntime.jsx(OptionSwatch, { color: option.swatch, round: true }), jsxRuntime.jsx("span", { children: option.label }), option.count !== undefined && (jsxRuntime.jsx("span", { className:
|
|
15990
|
+
return (jsxRuntime.jsx(ScrollFade, { role: "group", "aria-label": ariaLabel, wrapperClassName: wrapperClassName, className: cn('flex flex-wrap content-start gap-1.5', scrollClassName), children: visibleOptions.map((option) => (jsxRuntime.jsxs("button", { type: "button", "aria-pressed": value.includes(option.value), onClick: () => toggle(option), className: getChipClassName(value.includes(option.value)), children: [jsxRuntime.jsx(OptionSwatch, { color: option.swatch, round: true }), jsxRuntime.jsx("span", { children: option.label }), option.count !== undefined && (jsxRuntime.jsx("span", { className: getCountBadgeClassName(value.includes(option.value) ? 'tinted' : 'neutral'), children: formatOptionCount(option.count) }))] }, option.value))) }));
|
|
15985
15991
|
}
|
|
15986
15992
|
|
|
15987
15993
|
const TRIGGER_BASE = 'h-9 !w-fit shadow-[0_1px_2px_0_rgba(0,0,0,0.05)] gap-2 text-foreground bg-card hover:bg-muted/40 border border-border rounded-lg text-sm font-medium px-3';
|
|
@@ -38596,6 +38602,70 @@ function fieldTypeIcon(type) {
|
|
|
38596
38602
|
return FIELD_TYPE_ICONS[type] ?? Type;
|
|
38597
38603
|
}
|
|
38598
38604
|
|
|
38605
|
+
const SAVED_SEARCH_CHIP_CLASS_NAME = 'inline-flex h-6 min-w-0 max-w-full items-center rounded-full border border-primary/30 bg-primary/5 px-2 text-xs text-foreground';
|
|
38606
|
+
/** Children laid out below the first one's line. */
|
|
38607
|
+
function countWrapped(items) {
|
|
38608
|
+
if (items.length === 0)
|
|
38609
|
+
return 0;
|
|
38610
|
+
const firstTop = items[0].offsetTop;
|
|
38611
|
+
return items.filter((item) => item.offsetTop > firstTop).length;
|
|
38612
|
+
}
|
|
38613
|
+
const getShowMoreLabel = (hiddenCount) => `+${hiddenCount} more`;
|
|
38614
|
+
|
|
38615
|
+
/**
|
|
38616
|
+
* Fits chips on one line with the "+N more" link right after the last one. Renders every chip, counts
|
|
38617
|
+
* how many wrap, then drops chips until the link itself fits; a resize starts the measurement over.
|
|
38618
|
+
*/
|
|
38619
|
+
function useChipOverflow(chips, enabled) {
|
|
38620
|
+
const [list, setList] = React.useState(null);
|
|
38621
|
+
const [expanded, setExpanded] = React.useState(false);
|
|
38622
|
+
const [visibleCount, setVisibleCount] = React.useState(chips.length);
|
|
38623
|
+
const collapsed = enabled && !expanded;
|
|
38624
|
+
const [measuredLength, setMeasuredLength] = React.useState(chips.length);
|
|
38625
|
+
if (measuredLength !== chips.length) {
|
|
38626
|
+
setMeasuredLength(chips.length);
|
|
38627
|
+
setVisibleCount(chips.length);
|
|
38628
|
+
}
|
|
38629
|
+
React.useLayoutEffect(() => {
|
|
38630
|
+
if (!collapsed || !list)
|
|
38631
|
+
return;
|
|
38632
|
+
const items = Array.from(list.children);
|
|
38633
|
+
if (visibleCount >= chips.length) {
|
|
38634
|
+
const wrapped = countWrapped(items);
|
|
38635
|
+
if (wrapped > 0)
|
|
38636
|
+
setVisibleCount(chips.length - wrapped);
|
|
38637
|
+
return;
|
|
38638
|
+
}
|
|
38639
|
+
// The last child is the "+N more" link; if it wrapped, give it one more chip's room.
|
|
38640
|
+
if (countWrapped(items) > 0 && visibleCount > 1)
|
|
38641
|
+
setVisibleCount(visibleCount - 1);
|
|
38642
|
+
}, [collapsed, list, visibleCount, chips.length]);
|
|
38643
|
+
React.useEffect(() => {
|
|
38644
|
+
if (!collapsed || !list)
|
|
38645
|
+
return;
|
|
38646
|
+
let width = list.clientWidth;
|
|
38647
|
+
const observer = new ResizeObserver(() => {
|
|
38648
|
+
if (list.clientWidth === width)
|
|
38649
|
+
return;
|
|
38650
|
+
width = list.clientWidth;
|
|
38651
|
+
setVisibleCount(chips.length);
|
|
38652
|
+
});
|
|
38653
|
+
observer.observe(list);
|
|
38654
|
+
return () => observer.disconnect();
|
|
38655
|
+
}, [collapsed, list, chips.length]);
|
|
38656
|
+
const shown = collapsed ? Math.min(visibleCount, chips.length) : chips.length;
|
|
38657
|
+
return {
|
|
38658
|
+
listRef: React.useCallback((node) => setList(node), []),
|
|
38659
|
+
expanded,
|
|
38660
|
+
toggle: () => {
|
|
38661
|
+
setExpanded((current) => !current);
|
|
38662
|
+
setVisibleCount(chips.length);
|
|
38663
|
+
},
|
|
38664
|
+
visibleChips: chips.slice(0, shown),
|
|
38665
|
+
hiddenCount: chips.length - shown,
|
|
38666
|
+
};
|
|
38667
|
+
}
|
|
38668
|
+
|
|
38599
38669
|
const getRemoveChipLabel = (label) => `Remove ${label} filter`;
|
|
38600
38670
|
const FILTERED_BY_LABEL = 'Filtered by';
|
|
38601
38671
|
|
|
@@ -38603,16 +38673,33 @@ const FILTERED_BY_LABEL = 'Filtered by';
|
|
|
38603
38673
|
const CHIP_CLASS = 'inline-flex h-7 max-w-full items-center gap-1 rounded-md border border-primary/30 bg-primary/5 px-2.5 text-xs text-foreground';
|
|
38604
38674
|
const REMOVE_BUTTON_CLASS = 'inline-flex size-4 shrink-0 cursor-pointer items-center justify-center rounded-full text-muted-foreground outline-none hover:bg-primary/10 hover:text-foreground focus-visible:ring-2 focus-visible:ring-primary/30';
|
|
38605
38675
|
const CLEAR_ALL_CLASS = cn(FILTER_LINK_CLASS_NAME, 'underline');
|
|
38676
|
+
const SHOW_MORE_CLASS = cn(FILTER_LINK_CLASS_NAME, 'whitespace-nowrap px-1 underline underline-offset-2');
|
|
38606
38677
|
function getChipLabelText(label, variant) {
|
|
38607
38678
|
return variant === 'inline' ? `${label}:` : label;
|
|
38608
38679
|
}
|
|
38609
38680
|
|
|
38681
|
+
function Chip({ chip, variant, onRemove }) {
|
|
38682
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { className: "shrink-0 font-semibold", children: getChipLabelText(chip.label, variant) }), jsxRuntime.jsx(TruncatedLabel, { text: chip.value }), onRemove && (jsxRuntime.jsx("button", { type: "button", "aria-label": getRemoveChipLabel(chip.label), onClick: () => onRemove(chip.id), className: REMOVE_BUTTON_CLASS, children: jsxRuntime.jsx(X$2, { "aria-hidden": "true", className: "size-3" }) }))] }));
|
|
38683
|
+
}
|
|
38684
|
+
/** One line of chips ending in "+N more"; Clear all and the actions stay at the right end. */
|
|
38685
|
+
function CollapsibleChips({ chips, onRemove, variant = 'inline', clearAll, actions, className, }) {
|
|
38686
|
+
const { listRef, expanded, toggle, visibleChips, hiddenCount } = useChipOverflow(chips.map((chip) => chip.id), true);
|
|
38687
|
+
const visible = new Set(visibleChips);
|
|
38688
|
+
return (
|
|
38689
|
+
// Centred, so the label and Clear all line up with buttons beside the row even when the chips wrap.
|
|
38690
|
+
jsxRuntime.jsxs("div", { className: cn('flex items-center gap-1.5', className), children: [variant === 'inline' && (jsxRuntime.jsx("span", { className: "mr-0.5 flex h-7 shrink-0 items-center text-xs text-muted-foreground", children: FILTERED_BY_LABEL })), jsxRuntime.jsxs("ul", { ref: listRef, className: "flex min-w-0 flex-1 flex-wrap items-center gap-1.5", children: [chips
|
|
38691
|
+
.filter((chip) => visible.has(chip.id))
|
|
38692
|
+
.map((chip) => (jsxRuntime.jsx("li", { className: CHIP_CLASS, children: jsxRuntime.jsx(Chip, { chip: chip, variant: variant, onRemove: onRemove }) }, chip.id))), (expanded || hiddenCount > 0) && (jsxRuntime.jsx("li", { className: "flex h-7 shrink-0 items-center", children: jsxRuntime.jsx("button", { type: "button", "aria-expanded": expanded, onClick: toggle, className: SHOW_MORE_CLASS, children: expanded ? 'Show less' : getShowMoreLabel(hiddenCount) }) }))] }), (clearAll || actions) && (jsxRuntime.jsxs("div", { className: "ml-1.5 flex h-7 shrink-0 items-center gap-3", children: [clearAll, actions] }))] }));
|
|
38693
|
+
}
|
|
38610
38694
|
/** Applied filters as removable chips, with an optional Clear all. */
|
|
38611
|
-
function ActiveFilterChips({ chips, onRemove, onClearAll, variant = 'inline', actions, className, }) {
|
|
38695
|
+
function ActiveFilterChips({ chips, onRemove, onClearAll, variant = 'inline', actions, collapsible = false, className, }) {
|
|
38612
38696
|
if (chips.length === 0)
|
|
38613
38697
|
return null;
|
|
38614
38698
|
const clearAll = onClearAll && (jsxRuntime.jsx("button", { type: "button", onClick: onClearAll, className: CLEAR_ALL_CLASS, children: "Clear all" }));
|
|
38615
|
-
|
|
38699
|
+
if (collapsible) {
|
|
38700
|
+
return (jsxRuntime.jsx(CollapsibleChips, { chips: chips, onRemove: onRemove, variant: variant, clearAll: clearAll, actions: actions, className: className }));
|
|
38701
|
+
}
|
|
38702
|
+
return (jsxRuntime.jsxs("div", { className: cn('flex flex-wrap items-center gap-1.5', className), children: [variant === 'inline' && (jsxRuntime.jsx("span", { className: "mr-0.5 text-xs text-muted-foreground", children: FILTERED_BY_LABEL })), chips.map((chip) => (jsxRuntime.jsx("span", { className: CHIP_CLASS, children: jsxRuntime.jsx(Chip, { chip: chip, variant: variant, onRemove: onRemove }) }, chip.id))), actions ? (
|
|
38616
38703
|
// With actions, Clear all joins them at the row's right end.
|
|
38617
38704
|
jsxRuntime.jsxs("div", { className: "ml-auto flex items-center gap-3 pl-3", children: [clearAll, actions] })) : (clearAll)] }));
|
|
38618
38705
|
}
|
|
@@ -38814,14 +38901,18 @@ function getNextScaleRange(anchor, option) {
|
|
|
38814
38901
|
? { from: option, to: option }
|
|
38815
38902
|
: { from: anchor, to: option };
|
|
38816
38903
|
}
|
|
38904
|
+
const SCALE_POSITION_CLASS_NAMES = {
|
|
38905
|
+
// Unselected options look exactly like unselected pick-list options.
|
|
38906
|
+
outside: getSelectableOptionClassName(false),
|
|
38907
|
+
end: 'border-primary bg-primary font-medium text-primary-foreground',
|
|
38908
|
+
between: 'border-primary/30 bg-primary/10 font-medium text-primary',
|
|
38909
|
+
};
|
|
38910
|
+
/** A pill like the other pick lists' (`Single`, `Parcel`), coloured by where it sits in the range. */
|
|
38911
|
+
const getScalePillClassName = (position) => cn('inline-flex h-8 cursor-pointer items-center gap-1.5 rounded-sm border px-3 text-xs transition-colors', FILTER_FOCUS_RING, SCALE_POSITION_CLASS_NAMES[position]);
|
|
38817
38912
|
function getScaleButtonClassName(position, fill = false, hasCount = false) {
|
|
38818
38913
|
return cn('flex min-w-0 cursor-pointer items-center gap-2 rounded-md border transition-colors',
|
|
38819
38914
|
// With a count: name left, badge right (like a list row); without: name centred.
|
|
38820
|
-
hasCount ? 'justify-between text-left' : 'justify-center text-center', fill ? 'min-h-12 px-2 py-1.5 text-sm' : 'min-h-10 px-1.5 py-1 text-xs', FILTER_FOCUS_RING,
|
|
38821
|
-
// Unselected options look exactly like unselected pick-list tiles.
|
|
38822
|
-
position === 'outside' && getSelectableOptionClassName(false), position === 'end' &&
|
|
38823
|
-
'border-primary bg-primary font-medium text-primary-foreground', position === 'between' &&
|
|
38824
|
-
'border-primary/30 bg-primary/10 font-medium text-primary');
|
|
38915
|
+
hasCount ? 'justify-between text-left' : 'justify-center text-center', fill ? 'min-h-12 px-2 py-1.5 text-sm' : 'min-h-10 px-1.5 py-1 text-xs', FILTER_FOCUS_RING, SCALE_POSITION_CLASS_NAMES[position]);
|
|
38825
38916
|
}
|
|
38826
38917
|
/** The count badge follows the tile: plain, tinted inside the range, solid on an end. */
|
|
38827
38918
|
const getScaleCountTone = (position) => position === 'end' ? 'solid' : position === 'between' ? 'tinted' : 'neutral';
|
|
@@ -38841,8 +38932,14 @@ function useScaleRangeFilter({ value, onChange, }) {
|
|
|
38841
38932
|
}
|
|
38842
38933
|
|
|
38843
38934
|
/** Ordered-scale filter: click a start option, then an end option. */
|
|
38844
|
-
function ScaleRangeFilter({ options, value, onChange, ariaLabel, columns, fill = false, }) {
|
|
38935
|
+
function ScaleRangeFilter({ options, value, onChange, ariaLabel, layout = 'pills', columns, fill = false, }) {
|
|
38845
38936
|
const { selectOption } = useScaleRangeFilter({ value, onChange });
|
|
38937
|
+
if (layout === 'pills') {
|
|
38938
|
+
return (jsxRuntime.jsx("div", { role: "group", "aria-label": ariaLabel, className: "flex flex-wrap content-start gap-1.5", children: options.map((option) => {
|
|
38939
|
+
const position = getScalePosition(options, value, option.value);
|
|
38940
|
+
return (jsxRuntime.jsxs("button", { type: "button", "aria-pressed": position !== 'outside', "aria-label": option.label, onClick: () => selectOption(option.value), className: getScalePillClassName(position), children: [jsxRuntime.jsx("span", { children: option.label }), option.count !== undefined && (jsxRuntime.jsx("span", { className: getCountBadgeClassName(getScaleCountTone(position)), children: formatOptionCount(option.count) }))] }, option.value));
|
|
38941
|
+
}) }));
|
|
38942
|
+
}
|
|
38846
38943
|
return (jsxRuntime.jsx("div", { role: "group", "aria-label": ariaLabel, className: fill ? 'grid gap-2' : 'grid gap-1.5', style: getGridColumnsStyle(columns), children: options.map((option) => {
|
|
38847
38944
|
const position = getScalePosition(options, value, option.value);
|
|
38848
38945
|
return (jsxRuntime.jsxs("button", { type: "button", "aria-pressed": position !== 'outside', "aria-label": option.label, onClick: () => selectOption(option.value), className: getScaleButtonClassName(position, fill, option.count !== undefined), children: [jsxRuntime.jsx(TruncatedLabel, { text: option.label }), option.count !== undefined && (jsxRuntime.jsx("span", { className: getCountBadgeClassName(getScaleCountTone(position)), children: formatOptionCount(option.count) }))] }, option.value));
|
|
@@ -38856,7 +38953,7 @@ const TILE_KEYS = ['a', 'b', 'c', 'd'];
|
|
|
38856
38953
|
const CARD_CLASS_NAME = 'flex h-full min-w-0 flex-col gap-3 rounded-xl border border-border bg-card p-4';
|
|
38857
38954
|
|
|
38858
38955
|
function Pills({ count }) {
|
|
38859
|
-
return (jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1.5", children: PILL_WIDTHS.slice(0, count).map((width) => (jsxRuntime.jsx(SquareSkeleton, { height: 32, width: width, rounded: "
|
|
38956
|
+
return (jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1.5", children: PILL_WIDTHS.slice(0, count).map((width) => (jsxRuntime.jsx(SquareSkeleton, { height: 32, width: width, rounded: "sm" }, width))) }));
|
|
38860
38957
|
}
|
|
38861
38958
|
function Body({ variant, }) {
|
|
38862
38959
|
switch (variant) {
|
|
@@ -39166,70 +39263,6 @@ function TriStateFilter({ value, onChange, ariaLabel, compact = false, }) {
|
|
|
39166
39263
|
}) }));
|
|
39167
39264
|
}
|
|
39168
39265
|
|
|
39169
|
-
const SAVED_SEARCH_CHIP_CLASS_NAME = 'inline-flex h-6 min-w-0 max-w-full items-center rounded-full border border-primary/30 bg-primary/5 px-2 text-xs text-foreground';
|
|
39170
|
-
/** Children laid out below the first one's line. */
|
|
39171
|
-
function countWrapped(items) {
|
|
39172
|
-
if (items.length === 0)
|
|
39173
|
-
return 0;
|
|
39174
|
-
const firstTop = items[0].offsetTop;
|
|
39175
|
-
return items.filter((item) => item.offsetTop > firstTop).length;
|
|
39176
|
-
}
|
|
39177
|
-
const getShowMoreLabel = (hiddenCount) => `+${hiddenCount} more`;
|
|
39178
|
-
|
|
39179
|
-
/**
|
|
39180
|
-
* Fits chips on one line with the "+N more" link right after the last one. Renders every chip, counts
|
|
39181
|
-
* how many wrap, then drops chips until the link itself fits; a resize starts the measurement over.
|
|
39182
|
-
*/
|
|
39183
|
-
function useChipOverflow(chips, enabled) {
|
|
39184
|
-
const [list, setList] = React.useState(null);
|
|
39185
|
-
const [expanded, setExpanded] = React.useState(false);
|
|
39186
|
-
const [visibleCount, setVisibleCount] = React.useState(chips.length);
|
|
39187
|
-
const collapsed = enabled && !expanded;
|
|
39188
|
-
const [measuredLength, setMeasuredLength] = React.useState(chips.length);
|
|
39189
|
-
if (measuredLength !== chips.length) {
|
|
39190
|
-
setMeasuredLength(chips.length);
|
|
39191
|
-
setVisibleCount(chips.length);
|
|
39192
|
-
}
|
|
39193
|
-
React.useLayoutEffect(() => {
|
|
39194
|
-
if (!collapsed || !list)
|
|
39195
|
-
return;
|
|
39196
|
-
const items = Array.from(list.children);
|
|
39197
|
-
if (visibleCount >= chips.length) {
|
|
39198
|
-
const wrapped = countWrapped(items);
|
|
39199
|
-
if (wrapped > 0)
|
|
39200
|
-
setVisibleCount(chips.length - wrapped);
|
|
39201
|
-
return;
|
|
39202
|
-
}
|
|
39203
|
-
// The last child is the "+N more" link; if it wrapped, give it one more chip's room.
|
|
39204
|
-
if (countWrapped(items) > 0 && visibleCount > 1)
|
|
39205
|
-
setVisibleCount(visibleCount - 1);
|
|
39206
|
-
}, [collapsed, list, visibleCount, chips.length]);
|
|
39207
|
-
React.useEffect(() => {
|
|
39208
|
-
if (!collapsed || !list)
|
|
39209
|
-
return;
|
|
39210
|
-
let width = list.clientWidth;
|
|
39211
|
-
const observer = new ResizeObserver(() => {
|
|
39212
|
-
if (list.clientWidth === width)
|
|
39213
|
-
return;
|
|
39214
|
-
width = list.clientWidth;
|
|
39215
|
-
setVisibleCount(chips.length);
|
|
39216
|
-
});
|
|
39217
|
-
observer.observe(list);
|
|
39218
|
-
return () => observer.disconnect();
|
|
39219
|
-
}, [collapsed, list, chips.length]);
|
|
39220
|
-
const shown = collapsed ? Math.min(visibleCount, chips.length) : chips.length;
|
|
39221
|
-
return {
|
|
39222
|
-
listRef: React.useCallback((node) => setList(node), []),
|
|
39223
|
-
expanded,
|
|
39224
|
-
toggle: () => {
|
|
39225
|
-
setExpanded((current) => !current);
|
|
39226
|
-
setVisibleCount(chips.length);
|
|
39227
|
-
},
|
|
39228
|
-
visibleChips: chips.slice(0, shown),
|
|
39229
|
-
hiddenCount: chips.length - shown,
|
|
39230
|
-
};
|
|
39231
|
-
}
|
|
39232
|
-
|
|
39233
39266
|
/** Read-only filter summaries of a saved search, as pills; optionally one line ending in "+N more". */
|
|
39234
39267
|
function SavedSearchChipList({ chips, emptyText = 'No filters', collapsible = false, className, }) {
|
|
39235
39268
|
const { listRef, expanded, toggle, visibleChips, hiddenCount } = useChipOverflow(chips, collapsible);
|
|
@@ -77053,7 +77086,7 @@ const getDropdownPlaceholder = (field) => field.inputPlaceholder ??
|
|
|
77053
77086
|
`${field.single ? 'Any' : 'Select'} ${field.label.toLowerCase()}`;
|
|
77054
77087
|
/** Popover variant: the same control and layout as the card (pills up to 4 options, then the list); tile grids drop to two columns. */
|
|
77055
77088
|
function toPopoverField(field) {
|
|
77056
|
-
if (field.type === 'scale' ||
|
|
77089
|
+
if ((field.type === 'scale' && field.layout === 'tiles') ||
|
|
77057
77090
|
(field.type === 'list' && resolveListLayout(field) === 'tiles')) {
|
|
77058
77091
|
return { ...field, columns: POPOVER_TILE_COLUMNS };
|
|
77059
77092
|
}
|
|
@@ -77086,7 +77119,7 @@ function FilterFieldControl({ field, value, onChange, fiscalYearStartMonth, fill
|
|
|
77086
77119
|
return (jsxRuntime.jsx(PickListFilter, { ariaLabel: ariaLabel, options: field.options ?? [], value: valueOfType(value, 'list')?.values ?? [], onChange: (values) => onChange(toListValue(values)), layout: layout, searchPlaceholder: layout === 'list' ? field.searchPlaceholder : undefined, columns: field.columns, single: field.single }));
|
|
77087
77120
|
}
|
|
77088
77121
|
case 'scale':
|
|
77089
|
-
return (jsxRuntime.jsx(ScaleRangeFilter, { ariaLabel: ariaLabel, options: field.options ?? [], value: valueOfType(value, 'scale')?.range, onChange: (range) => onChange(toScaleValue(range)), columns: field.columns, fill: fill }));
|
|
77122
|
+
return (jsxRuntime.jsx(ScaleRangeFilter, { ariaLabel: ariaLabel, options: field.options ?? [], value: valueOfType(value, 'scale')?.range, onChange: (range) => onChange(toScaleValue(range)), layout: field.layout === 'tiles' ? 'tiles' : 'pills', columns: field.columns, fill: fill }));
|
|
77090
77123
|
case 'number':
|
|
77091
77124
|
return (jsxRuntime.jsx(NumberRangeInputs, { ariaLabel: field.label, value: valueOfType(value, 'number')?.range, onChange: (range) => onChange(toNumberValue(range)), presets: field.presets, presetsLabel: field.presetsLabel }));
|
|
77092
77125
|
case 'ids':
|
|
@@ -77132,9 +77165,8 @@ function FilterField(props) {
|
|
|
77132
77165
|
return mode === 'cell' ? (jsxRuntime.jsx(FilterFieldCell, { ...rest })) : (jsxRuntime.jsx(FilterFieldCard, { mode: mode, ...rest }));
|
|
77133
77166
|
}
|
|
77134
77167
|
|
|
77135
|
-
/**
|
|
77168
|
+
/** Leaves without applying; the draft is discarded. */
|
|
77136
77169
|
const BACK_LABEL = 'Back without applying';
|
|
77137
|
-
const CLOSE_LABEL = 'Close without applying';
|
|
77138
77170
|
const LOADING_LABEL = 'Loading filters';
|
|
77139
77171
|
const ERROR_TITLE = "Couldn't load filters";
|
|
77140
77172
|
const DEFAULT_ERROR_MESSAGE = 'Something went wrong while loading the filters. Check your connection and try again.';
|
|
@@ -77204,10 +77236,12 @@ function MasonryItem({ children }) {
|
|
|
77204
77236
|
return (jsxRuntime.jsx("div", { style: span > 0 ? { gridRowEnd: `span ${span}` } : undefined, children: jsxRuntime.jsx("div", { ref: ref, className: "pb-3", children: children }) }));
|
|
77205
77237
|
}
|
|
77206
77238
|
|
|
77207
|
-
/** Full-page filter screen (e.g. an advanced search): header, scrolling card grid
|
|
77208
|
-
function FilterSurface({ title, headerActions,
|
|
77239
|
+
/** Full-page filter screen (e.g. an advanced search): one sticky header with every action, then the scrolling card grid. */
|
|
77240
|
+
function FilterSurface({ title, headerActions, primaryActions, chips, onClose, status = 'ready', errorMessage, onRetry, skeleton, children, }) {
|
|
77209
77241
|
useCloseOnEscape(onClose);
|
|
77210
|
-
|
|
77242
|
+
const ready = status === 'ready';
|
|
77243
|
+
const actions = ready ? primaryActions : undefined;
|
|
77244
|
+
return (jsxRuntime.jsxs("div", { role: "region", "aria-label": title, className: "flex h-full min-h-0 flex-col bg-background", children: [jsxRuntime.jsxs("header", { className: "sticky top-0 z-20 flex shrink-0 flex-wrap items-center gap-x-3 gap-y-2 bg-background px-3 py-2.5", children: [jsxRuntime.jsx(IconButton, { appearance: "ghost", size: "sm", "aria-label": BACK_LABEL, title: BACK_LABEL, onClick: onClose, children: jsxRuntime.jsx(ArrowLeft, {}) }), jsxRuntime.jsx("h1", { className: "sr-only", children: title }), jsxRuntime.jsx("div", { className: "min-w-0 flex-1", children: ready && chips }), jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [headerActions, actions] })] }), jsxRuntime.jsx("div", { className: "min-h-0 flex-1 overflow-y-auto px-3 pb-3", children: status === 'loading' ? (jsxRuntime.jsx(FilterSurfaceSkeleton, { children: skeleton })) : status === 'error' ? (jsxRuntime.jsx(FilterSurfaceError, { message: errorMessage ?? DEFAULT_ERROR_MESSAGE, onRetry: onRetry })) : (jsxRuntime.jsx("div", { className: "flex flex-col gap-3", children: children })) })] }));
|
|
77211
77245
|
}
|
|
77212
77246
|
function FilterSurfaceGroup({ title, masonry = false, className, children, }) {
|
|
77213
77247
|
return (jsxRuntime.jsxs("section", { className: "flex flex-col gap-3", children: [title && (jsxRuntime.jsx("h2", { className: "text-xs font-semibold uppercase tracking-wide text-label", children: title })), jsxRuntime.jsx("div", { className: cn('grid grid-cols-1 items-start lg:grid-cols-6', masonry ? MASONRY_GRID_CLASS_NAME : 'gap-3', className), children: masonry
|