@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.esm.js
CHANGED
|
@@ -15484,7 +15484,10 @@ function formatRuleValue(value, unit) {
|
|
|
15484
15484
|
const number = value.toLocaleString('en-US', { maximumFractionDigits: 2 });
|
|
15485
15485
|
if (!unit)
|
|
15486
15486
|
return number;
|
|
15487
|
-
|
|
15487
|
+
// Symbols sit right after the number (`60%`, `34°`); words take a space (`2 ct`).
|
|
15488
|
+
return unit === '%' || unit === '°'
|
|
15489
|
+
? `${number}${unit}`
|
|
15490
|
+
: `${number} ${unit}`;
|
|
15488
15491
|
}
|
|
15489
15492
|
/** The help popover's rule list for a column's typical range; rules use short numbers (`1k`) that the field accepts as typed. */
|
|
15490
15493
|
function buildNumberRuleHelp({ low, high, exact, unit, } = DEFAULT_NUMBER_RULE_EXAMPLES) {
|
|
@@ -15707,7 +15710,7 @@ function getOptionRowClassName(selected = false, className) {
|
|
|
15707
15710
|
}
|
|
15708
15711
|
/** A selectable pill (chip options, range presets); one height and shape everywhere. */
|
|
15709
15712
|
function getPillClassName(selected) {
|
|
15710
|
-
return getSelectableOptionClassName(selected, 'inline-flex h-8 items-center gap-1.5 rounded-
|
|
15713
|
+
return getSelectableOptionClassName(selected, 'inline-flex h-8 items-center gap-1.5 rounded-sm px-3');
|
|
15711
15714
|
}
|
|
15712
15715
|
/** Search box above a list (pick lists, saved searches); matches the inputs' height and corners. */
|
|
15713
15716
|
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';
|
|
@@ -15739,8 +15742,10 @@ function getPanelClassName(variant, active, className) {
|
|
|
15739
15742
|
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);
|
|
15740
15743
|
}
|
|
15741
15744
|
function getTitleClassName(variant) {
|
|
15742
|
-
return cn('flex items-center gap-2 text-foreground',
|
|
15743
|
-
|
|
15745
|
+
return cn('flex items-center gap-2 text-foreground',
|
|
15746
|
+
// A section title is a field name: between the card title and grey helper text.
|
|
15747
|
+
variant === 'section'
|
|
15748
|
+
? 'text-xs font-medium text-foreground/70'
|
|
15744
15749
|
: 'text-sm font-semibold');
|
|
15745
15750
|
}
|
|
15746
15751
|
|
|
@@ -15888,14 +15893,15 @@ const LIST_COLUMN_MIN_WIDTH = '10rem';
|
|
|
15888
15893
|
const getOptionsScrollClassName = (fill) => fill ? OPTIONS_FILL_CLASS_NAME : OPTIONS_SCROLL_CLASS_NAME;
|
|
15889
15894
|
const LIST_ROW_CLASS_NAME = getOptionRowClassName();
|
|
15890
15895
|
const COUNT_BADGE_TONES = {
|
|
15891
|
-
neutral: 'bg-
|
|
15892
|
-
tinted: 'bg-primary/
|
|
15893
|
-
solid: 'bg-primary-foreground/20 text-primary-foreground',
|
|
15896
|
+
neutral: 'border-primary/30 bg-primary/5 text-primary',
|
|
15897
|
+
tinted: 'border-primary/30 bg-primary/5 text-primary',
|
|
15898
|
+
solid: 'border-primary-foreground/60 bg-primary-foreground/20 text-primary-foreground',
|
|
15894
15899
|
};
|
|
15895
15900
|
/** How many items carry the option, as a small round badge at the right. */
|
|
15896
|
-
const getCountBadgeClassName = (tone) => cn('inline-flex h-5 min-w-
|
|
15901
|
+
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]);
|
|
15897
15902
|
const COUNT_BADGE_CLASS_NAME = getCountBadgeClassName('neutral');
|
|
15898
|
-
|
|
15903
|
+
/** Plain digits (no thousands separators), so the badge stays compact. */
|
|
15904
|
+
const formatOptionCount = (count) => String(count);
|
|
15899
15905
|
/** Picking a value: a single-choice list replaces (or clears) it, a multi-choice list toggles it. */
|
|
15900
15906
|
const pickValue = (values, value, single) => {
|
|
15901
15907
|
if (!single)
|
|
@@ -15951,7 +15957,7 @@ function PickListFilter({ options, value, onChange, ariaLabel, layout = 'list',
|
|
|
15951
15957
|
if (layout === 'tiles') {
|
|
15952
15958
|
return (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) => (jsxs("button", { type: "button", "aria-pressed": value.includes(option.value), onClick: () => toggle(option), className: getTileClassName(value.includes(option.value), option.count !== undefined), children: [jsx(TruncatedLabel, { text: option.label }), option.count !== undefined && (jsx("span", { className: getCountBadgeClassName(value.includes(option.value) ? 'tinted' : 'neutral'), children: formatOptionCount(option.count) }))] }, option.value))) }));
|
|
15953
15959
|
}
|
|
15954
|
-
return (jsx(ScrollFade, { role: "group", "aria-label": ariaLabel, wrapperClassName: wrapperClassName, className: cn('flex flex-wrap content-start gap-1.5', scrollClassName), children: visibleOptions.map((option) => (jsxs("button", { type: "button", "aria-pressed": value.includes(option.value), onClick: () => toggle(option), className: getChipClassName(value.includes(option.value)), children: [jsx(OptionSwatch, { color: option.swatch, round: true }), jsx("span", { children: option.label }), option.count !== undefined && (jsx("span", { className:
|
|
15960
|
+
return (jsx(ScrollFade, { role: "group", "aria-label": ariaLabel, wrapperClassName: wrapperClassName, className: cn('flex flex-wrap content-start gap-1.5', scrollClassName), children: visibleOptions.map((option) => (jsxs("button", { type: "button", "aria-pressed": value.includes(option.value), onClick: () => toggle(option), className: getChipClassName(value.includes(option.value)), children: [jsx(OptionSwatch, { color: option.swatch, round: true }), jsx("span", { children: option.label }), option.count !== undefined && (jsx("span", { className: getCountBadgeClassName(value.includes(option.value) ? 'tinted' : 'neutral'), children: formatOptionCount(option.count) }))] }, option.value))) }));
|
|
15955
15961
|
}
|
|
15956
15962
|
|
|
15957
15963
|
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';
|
|
@@ -38566,6 +38572,70 @@ function fieldTypeIcon(type) {
|
|
|
38566
38572
|
return FIELD_TYPE_ICONS[type] ?? Type;
|
|
38567
38573
|
}
|
|
38568
38574
|
|
|
38575
|
+
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';
|
|
38576
|
+
/** Children laid out below the first one's line. */
|
|
38577
|
+
function countWrapped(items) {
|
|
38578
|
+
if (items.length === 0)
|
|
38579
|
+
return 0;
|
|
38580
|
+
const firstTop = items[0].offsetTop;
|
|
38581
|
+
return items.filter((item) => item.offsetTop > firstTop).length;
|
|
38582
|
+
}
|
|
38583
|
+
const getShowMoreLabel = (hiddenCount) => `+${hiddenCount} more`;
|
|
38584
|
+
|
|
38585
|
+
/**
|
|
38586
|
+
* Fits chips on one line with the "+N more" link right after the last one. Renders every chip, counts
|
|
38587
|
+
* how many wrap, then drops chips until the link itself fits; a resize starts the measurement over.
|
|
38588
|
+
*/
|
|
38589
|
+
function useChipOverflow(chips, enabled) {
|
|
38590
|
+
const [list, setList] = useState(null);
|
|
38591
|
+
const [expanded, setExpanded] = useState(false);
|
|
38592
|
+
const [visibleCount, setVisibleCount] = useState(chips.length);
|
|
38593
|
+
const collapsed = enabled && !expanded;
|
|
38594
|
+
const [measuredLength, setMeasuredLength] = useState(chips.length);
|
|
38595
|
+
if (measuredLength !== chips.length) {
|
|
38596
|
+
setMeasuredLength(chips.length);
|
|
38597
|
+
setVisibleCount(chips.length);
|
|
38598
|
+
}
|
|
38599
|
+
useLayoutEffect(() => {
|
|
38600
|
+
if (!collapsed || !list)
|
|
38601
|
+
return;
|
|
38602
|
+
const items = Array.from(list.children);
|
|
38603
|
+
if (visibleCount >= chips.length) {
|
|
38604
|
+
const wrapped = countWrapped(items);
|
|
38605
|
+
if (wrapped > 0)
|
|
38606
|
+
setVisibleCount(chips.length - wrapped);
|
|
38607
|
+
return;
|
|
38608
|
+
}
|
|
38609
|
+
// The last child is the "+N more" link; if it wrapped, give it one more chip's room.
|
|
38610
|
+
if (countWrapped(items) > 0 && visibleCount > 1)
|
|
38611
|
+
setVisibleCount(visibleCount - 1);
|
|
38612
|
+
}, [collapsed, list, visibleCount, chips.length]);
|
|
38613
|
+
useEffect(() => {
|
|
38614
|
+
if (!collapsed || !list)
|
|
38615
|
+
return;
|
|
38616
|
+
let width = list.clientWidth;
|
|
38617
|
+
const observer = new ResizeObserver(() => {
|
|
38618
|
+
if (list.clientWidth === width)
|
|
38619
|
+
return;
|
|
38620
|
+
width = list.clientWidth;
|
|
38621
|
+
setVisibleCount(chips.length);
|
|
38622
|
+
});
|
|
38623
|
+
observer.observe(list);
|
|
38624
|
+
return () => observer.disconnect();
|
|
38625
|
+
}, [collapsed, list, chips.length]);
|
|
38626
|
+
const shown = collapsed ? Math.min(visibleCount, chips.length) : chips.length;
|
|
38627
|
+
return {
|
|
38628
|
+
listRef: useCallback((node) => setList(node), []),
|
|
38629
|
+
expanded,
|
|
38630
|
+
toggle: () => {
|
|
38631
|
+
setExpanded((current) => !current);
|
|
38632
|
+
setVisibleCount(chips.length);
|
|
38633
|
+
},
|
|
38634
|
+
visibleChips: chips.slice(0, shown),
|
|
38635
|
+
hiddenCount: chips.length - shown,
|
|
38636
|
+
};
|
|
38637
|
+
}
|
|
38638
|
+
|
|
38569
38639
|
const getRemoveChipLabel = (label) => `Remove ${label} filter`;
|
|
38570
38640
|
const FILTERED_BY_LABEL = 'Filtered by';
|
|
38571
38641
|
|
|
@@ -38573,16 +38643,33 @@ const FILTERED_BY_LABEL = 'Filtered by';
|
|
|
38573
38643
|
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';
|
|
38574
38644
|
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';
|
|
38575
38645
|
const CLEAR_ALL_CLASS = cn(FILTER_LINK_CLASS_NAME, 'underline');
|
|
38646
|
+
const SHOW_MORE_CLASS = cn(FILTER_LINK_CLASS_NAME, 'whitespace-nowrap px-1 underline underline-offset-2');
|
|
38576
38647
|
function getChipLabelText(label, variant) {
|
|
38577
38648
|
return variant === 'inline' ? `${label}:` : label;
|
|
38578
38649
|
}
|
|
38579
38650
|
|
|
38651
|
+
function Chip({ chip, variant, onRemove }) {
|
|
38652
|
+
return (jsxs(Fragment$2, { children: [jsx("span", { className: "shrink-0 font-semibold", children: getChipLabelText(chip.label, variant) }), jsx(TruncatedLabel, { text: chip.value }), onRemove && (jsx("button", { type: "button", "aria-label": getRemoveChipLabel(chip.label), onClick: () => onRemove(chip.id), className: REMOVE_BUTTON_CLASS, children: jsx(X$2, { "aria-hidden": "true", className: "size-3" }) }))] }));
|
|
38653
|
+
}
|
|
38654
|
+
/** One line of chips ending in "+N more"; Clear all and the actions stay at the right end. */
|
|
38655
|
+
function CollapsibleChips({ chips, onRemove, variant = 'inline', clearAll, actions, className, }) {
|
|
38656
|
+
const { listRef, expanded, toggle, visibleChips, hiddenCount } = useChipOverflow(chips.map((chip) => chip.id), true);
|
|
38657
|
+
const visible = new Set(visibleChips);
|
|
38658
|
+
return (
|
|
38659
|
+
// Centred, so the label and Clear all line up with buttons beside the row even when the chips wrap.
|
|
38660
|
+
jsxs("div", { className: cn('flex items-center gap-1.5', className), children: [variant === 'inline' && (jsx("span", { className: "mr-0.5 flex h-7 shrink-0 items-center text-xs text-muted-foreground", children: FILTERED_BY_LABEL })), jsxs("ul", { ref: listRef, className: "flex min-w-0 flex-1 flex-wrap items-center gap-1.5", children: [chips
|
|
38661
|
+
.filter((chip) => visible.has(chip.id))
|
|
38662
|
+
.map((chip) => (jsx("li", { className: CHIP_CLASS, children: jsx(Chip, { chip: chip, variant: variant, onRemove: onRemove }) }, chip.id))), (expanded || hiddenCount > 0) && (jsx("li", { className: "flex h-7 shrink-0 items-center", children: jsx("button", { type: "button", "aria-expanded": expanded, onClick: toggle, className: SHOW_MORE_CLASS, children: expanded ? 'Show less' : getShowMoreLabel(hiddenCount) }) }))] }), (clearAll || actions) && (jsxs("div", { className: "ml-1.5 flex h-7 shrink-0 items-center gap-3", children: [clearAll, actions] }))] }));
|
|
38663
|
+
}
|
|
38580
38664
|
/** Applied filters as removable chips, with an optional Clear all. */
|
|
38581
|
-
function ActiveFilterChips({ chips, onRemove, onClearAll, variant = 'inline', actions, className, }) {
|
|
38665
|
+
function ActiveFilterChips({ chips, onRemove, onClearAll, variant = 'inline', actions, collapsible = false, className, }) {
|
|
38582
38666
|
if (chips.length === 0)
|
|
38583
38667
|
return null;
|
|
38584
38668
|
const clearAll = onClearAll && (jsx("button", { type: "button", onClick: onClearAll, className: CLEAR_ALL_CLASS, children: "Clear all" }));
|
|
38585
|
-
|
|
38669
|
+
if (collapsible) {
|
|
38670
|
+
return (jsx(CollapsibleChips, { chips: chips, onRemove: onRemove, variant: variant, clearAll: clearAll, actions: actions, className: className }));
|
|
38671
|
+
}
|
|
38672
|
+
return (jsxs("div", { className: cn('flex flex-wrap items-center gap-1.5', className), children: [variant === 'inline' && (jsx("span", { className: "mr-0.5 text-xs text-muted-foreground", children: FILTERED_BY_LABEL })), chips.map((chip) => (jsx("span", { className: CHIP_CLASS, children: jsx(Chip, { chip: chip, variant: variant, onRemove: onRemove }) }, chip.id))), actions ? (
|
|
38586
38673
|
// With actions, Clear all joins them at the row's right end.
|
|
38587
38674
|
jsxs("div", { className: "ml-auto flex items-center gap-3 pl-3", children: [clearAll, actions] })) : (clearAll)] }));
|
|
38588
38675
|
}
|
|
@@ -38784,14 +38871,18 @@ function getNextScaleRange(anchor, option) {
|
|
|
38784
38871
|
? { from: option, to: option }
|
|
38785
38872
|
: { from: anchor, to: option };
|
|
38786
38873
|
}
|
|
38874
|
+
const SCALE_POSITION_CLASS_NAMES = {
|
|
38875
|
+
// Unselected options look exactly like unselected pick-list options.
|
|
38876
|
+
outside: getSelectableOptionClassName(false),
|
|
38877
|
+
end: 'border-primary bg-primary font-medium text-primary-foreground',
|
|
38878
|
+
between: 'border-primary/30 bg-primary/10 font-medium text-primary',
|
|
38879
|
+
};
|
|
38880
|
+
/** A pill like the other pick lists' (`Single`, `Parcel`), coloured by where it sits in the range. */
|
|
38881
|
+
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]);
|
|
38787
38882
|
function getScaleButtonClassName(position, fill = false, hasCount = false) {
|
|
38788
38883
|
return cn('flex min-w-0 cursor-pointer items-center gap-2 rounded-md border transition-colors',
|
|
38789
38884
|
// With a count: name left, badge right (like a list row); without: name centred.
|
|
38790
|
-
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,
|
|
38791
|
-
// Unselected options look exactly like unselected pick-list tiles.
|
|
38792
|
-
position === 'outside' && getSelectableOptionClassName(false), position === 'end' &&
|
|
38793
|
-
'border-primary bg-primary font-medium text-primary-foreground', position === 'between' &&
|
|
38794
|
-
'border-primary/30 bg-primary/10 font-medium text-primary');
|
|
38885
|
+
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]);
|
|
38795
38886
|
}
|
|
38796
38887
|
/** The count badge follows the tile: plain, tinted inside the range, solid on an end. */
|
|
38797
38888
|
const getScaleCountTone = (position) => position === 'end' ? 'solid' : position === 'between' ? 'tinted' : 'neutral';
|
|
@@ -38811,8 +38902,14 @@ function useScaleRangeFilter({ value, onChange, }) {
|
|
|
38811
38902
|
}
|
|
38812
38903
|
|
|
38813
38904
|
/** Ordered-scale filter: click a start option, then an end option. */
|
|
38814
|
-
function ScaleRangeFilter({ options, value, onChange, ariaLabel, columns, fill = false, }) {
|
|
38905
|
+
function ScaleRangeFilter({ options, value, onChange, ariaLabel, layout = 'pills', columns, fill = false, }) {
|
|
38815
38906
|
const { selectOption } = useScaleRangeFilter({ value, onChange });
|
|
38907
|
+
if (layout === 'pills') {
|
|
38908
|
+
return (jsx("div", { role: "group", "aria-label": ariaLabel, className: "flex flex-wrap content-start gap-1.5", children: options.map((option) => {
|
|
38909
|
+
const position = getScalePosition(options, value, option.value);
|
|
38910
|
+
return (jsxs("button", { type: "button", "aria-pressed": position !== 'outside', "aria-label": option.label, onClick: () => selectOption(option.value), className: getScalePillClassName(position), children: [jsx("span", { children: option.label }), option.count !== undefined && (jsx("span", { className: getCountBadgeClassName(getScaleCountTone(position)), children: formatOptionCount(option.count) }))] }, option.value));
|
|
38911
|
+
}) }));
|
|
38912
|
+
}
|
|
38816
38913
|
return (jsx("div", { role: "group", "aria-label": ariaLabel, className: fill ? 'grid gap-2' : 'grid gap-1.5', style: getGridColumnsStyle(columns), children: options.map((option) => {
|
|
38817
38914
|
const position = getScalePosition(options, value, option.value);
|
|
38818
38915
|
return (jsxs("button", { type: "button", "aria-pressed": position !== 'outside', "aria-label": option.label, onClick: () => selectOption(option.value), className: getScaleButtonClassName(position, fill, option.count !== undefined), children: [jsx(TruncatedLabel, { text: option.label }), option.count !== undefined && (jsx("span", { className: getCountBadgeClassName(getScaleCountTone(position)), children: formatOptionCount(option.count) }))] }, option.value));
|
|
@@ -38826,7 +38923,7 @@ const TILE_KEYS = ['a', 'b', 'c', 'd'];
|
|
|
38826
38923
|
const CARD_CLASS_NAME = 'flex h-full min-w-0 flex-col gap-3 rounded-xl border border-border bg-card p-4';
|
|
38827
38924
|
|
|
38828
38925
|
function Pills({ count }) {
|
|
38829
|
-
return (jsx("div", { className: "flex flex-wrap gap-1.5", children: PILL_WIDTHS.slice(0, count).map((width) => (jsx(SquareSkeleton, { height: 32, width: width, rounded: "
|
|
38926
|
+
return (jsx("div", { className: "flex flex-wrap gap-1.5", children: PILL_WIDTHS.slice(0, count).map((width) => (jsx(SquareSkeleton, { height: 32, width: width, rounded: "sm" }, width))) }));
|
|
38830
38927
|
}
|
|
38831
38928
|
function Body({ variant, }) {
|
|
38832
38929
|
switch (variant) {
|
|
@@ -39136,70 +39233,6 @@ function TriStateFilter({ value, onChange, ariaLabel, compact = false, }) {
|
|
|
39136
39233
|
}) }));
|
|
39137
39234
|
}
|
|
39138
39235
|
|
|
39139
|
-
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';
|
|
39140
|
-
/** Children laid out below the first one's line. */
|
|
39141
|
-
function countWrapped(items) {
|
|
39142
|
-
if (items.length === 0)
|
|
39143
|
-
return 0;
|
|
39144
|
-
const firstTop = items[0].offsetTop;
|
|
39145
|
-
return items.filter((item) => item.offsetTop > firstTop).length;
|
|
39146
|
-
}
|
|
39147
|
-
const getShowMoreLabel = (hiddenCount) => `+${hiddenCount} more`;
|
|
39148
|
-
|
|
39149
|
-
/**
|
|
39150
|
-
* Fits chips on one line with the "+N more" link right after the last one. Renders every chip, counts
|
|
39151
|
-
* how many wrap, then drops chips until the link itself fits; a resize starts the measurement over.
|
|
39152
|
-
*/
|
|
39153
|
-
function useChipOverflow(chips, enabled) {
|
|
39154
|
-
const [list, setList] = useState(null);
|
|
39155
|
-
const [expanded, setExpanded] = useState(false);
|
|
39156
|
-
const [visibleCount, setVisibleCount] = useState(chips.length);
|
|
39157
|
-
const collapsed = enabled && !expanded;
|
|
39158
|
-
const [measuredLength, setMeasuredLength] = useState(chips.length);
|
|
39159
|
-
if (measuredLength !== chips.length) {
|
|
39160
|
-
setMeasuredLength(chips.length);
|
|
39161
|
-
setVisibleCount(chips.length);
|
|
39162
|
-
}
|
|
39163
|
-
useLayoutEffect(() => {
|
|
39164
|
-
if (!collapsed || !list)
|
|
39165
|
-
return;
|
|
39166
|
-
const items = Array.from(list.children);
|
|
39167
|
-
if (visibleCount >= chips.length) {
|
|
39168
|
-
const wrapped = countWrapped(items);
|
|
39169
|
-
if (wrapped > 0)
|
|
39170
|
-
setVisibleCount(chips.length - wrapped);
|
|
39171
|
-
return;
|
|
39172
|
-
}
|
|
39173
|
-
// The last child is the "+N more" link; if it wrapped, give it one more chip's room.
|
|
39174
|
-
if (countWrapped(items) > 0 && visibleCount > 1)
|
|
39175
|
-
setVisibleCount(visibleCount - 1);
|
|
39176
|
-
}, [collapsed, list, visibleCount, chips.length]);
|
|
39177
|
-
useEffect(() => {
|
|
39178
|
-
if (!collapsed || !list)
|
|
39179
|
-
return;
|
|
39180
|
-
let width = list.clientWidth;
|
|
39181
|
-
const observer = new ResizeObserver(() => {
|
|
39182
|
-
if (list.clientWidth === width)
|
|
39183
|
-
return;
|
|
39184
|
-
width = list.clientWidth;
|
|
39185
|
-
setVisibleCount(chips.length);
|
|
39186
|
-
});
|
|
39187
|
-
observer.observe(list);
|
|
39188
|
-
return () => observer.disconnect();
|
|
39189
|
-
}, [collapsed, list, chips.length]);
|
|
39190
|
-
const shown = collapsed ? Math.min(visibleCount, chips.length) : chips.length;
|
|
39191
|
-
return {
|
|
39192
|
-
listRef: useCallback((node) => setList(node), []),
|
|
39193
|
-
expanded,
|
|
39194
|
-
toggle: () => {
|
|
39195
|
-
setExpanded((current) => !current);
|
|
39196
|
-
setVisibleCount(chips.length);
|
|
39197
|
-
},
|
|
39198
|
-
visibleChips: chips.slice(0, shown),
|
|
39199
|
-
hiddenCount: chips.length - shown,
|
|
39200
|
-
};
|
|
39201
|
-
}
|
|
39202
|
-
|
|
39203
39236
|
/** Read-only filter summaries of a saved search, as pills; optionally one line ending in "+N more". */
|
|
39204
39237
|
function SavedSearchChipList({ chips, emptyText = 'No filters', collapsible = false, className, }) {
|
|
39205
39238
|
const { listRef, expanded, toggle, visibleChips, hiddenCount } = useChipOverflow(chips, collapsible);
|
|
@@ -77023,7 +77056,7 @@ const getDropdownPlaceholder = (field) => field.inputPlaceholder ??
|
|
|
77023
77056
|
`${field.single ? 'Any' : 'Select'} ${field.label.toLowerCase()}`;
|
|
77024
77057
|
/** Popover variant: the same control and layout as the card (pills up to 4 options, then the list); tile grids drop to two columns. */
|
|
77025
77058
|
function toPopoverField(field) {
|
|
77026
|
-
if (field.type === 'scale' ||
|
|
77059
|
+
if ((field.type === 'scale' && field.layout === 'tiles') ||
|
|
77027
77060
|
(field.type === 'list' && resolveListLayout(field) === 'tiles')) {
|
|
77028
77061
|
return { ...field, columns: POPOVER_TILE_COLUMNS };
|
|
77029
77062
|
}
|
|
@@ -77056,7 +77089,7 @@ function FilterFieldControl({ field, value, onChange, fiscalYearStartMonth, fill
|
|
|
77056
77089
|
return (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 }));
|
|
77057
77090
|
}
|
|
77058
77091
|
case 'scale':
|
|
77059
|
-
return (jsx(ScaleRangeFilter, { ariaLabel: ariaLabel, options: field.options ?? [], value: valueOfType(value, 'scale')?.range, onChange: (range) => onChange(toScaleValue(range)), columns: field.columns, fill: fill }));
|
|
77092
|
+
return (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 }));
|
|
77060
77093
|
case 'number':
|
|
77061
77094
|
return (jsx(NumberRangeInputs, { ariaLabel: field.label, value: valueOfType(value, 'number')?.range, onChange: (range) => onChange(toNumberValue(range)), presets: field.presets, presetsLabel: field.presetsLabel }));
|
|
77062
77095
|
case 'ids':
|
|
@@ -77102,9 +77135,8 @@ function FilterField(props) {
|
|
|
77102
77135
|
return mode === 'cell' ? (jsx(FilterFieldCell, { ...rest })) : (jsx(FilterFieldCard, { mode: mode, ...rest }));
|
|
77103
77136
|
}
|
|
77104
77137
|
|
|
77105
|
-
/**
|
|
77138
|
+
/** Leaves without applying; the draft is discarded. */
|
|
77106
77139
|
const BACK_LABEL = 'Back without applying';
|
|
77107
|
-
const CLOSE_LABEL = 'Close without applying';
|
|
77108
77140
|
const LOADING_LABEL = 'Loading filters';
|
|
77109
77141
|
const ERROR_TITLE = "Couldn't load filters";
|
|
77110
77142
|
const DEFAULT_ERROR_MESSAGE = 'Something went wrong while loading the filters. Check your connection and try again.';
|
|
@@ -77174,10 +77206,12 @@ function MasonryItem({ children }) {
|
|
|
77174
77206
|
return (jsx("div", { style: span > 0 ? { gridRowEnd: `span ${span}` } : undefined, children: jsx("div", { ref: ref, className: "pb-3", children: children }) }));
|
|
77175
77207
|
}
|
|
77176
77208
|
|
|
77177
|
-
/** Full-page filter screen (e.g. an advanced search): header, scrolling card grid
|
|
77178
|
-
function FilterSurface({ title, headerActions,
|
|
77209
|
+
/** Full-page filter screen (e.g. an advanced search): one sticky header with every action, then the scrolling card grid. */
|
|
77210
|
+
function FilterSurface({ title, headerActions, primaryActions, chips, onClose, status = 'ready', errorMessage, onRetry, skeleton, children, }) {
|
|
77179
77211
|
useCloseOnEscape(onClose);
|
|
77180
|
-
|
|
77212
|
+
const ready = status === 'ready';
|
|
77213
|
+
const actions = ready ? primaryActions : undefined;
|
|
77214
|
+
return (jsxs("div", { role: "region", "aria-label": title, className: "flex h-full min-h-0 flex-col bg-background", children: [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: [jsx(IconButton, { appearance: "ghost", size: "sm", "aria-label": BACK_LABEL, title: BACK_LABEL, onClick: onClose, children: jsx(ArrowLeft, {}) }), jsx("h1", { className: "sr-only", children: title }), jsx("div", { className: "min-w-0 flex-1", children: ready && chips }), jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [headerActions, actions] })] }), jsx("div", { className: "min-h-0 flex-1 overflow-y-auto px-3 pb-3", children: status === 'loading' ? (jsx(FilterSurfaceSkeleton, { children: skeleton })) : status === 'error' ? (jsx(FilterSurfaceError, { message: errorMessage ?? DEFAULT_ERROR_MESSAGE, onRetry: onRetry })) : (jsx("div", { className: "flex flex-col gap-3", children: children })) })] }));
|
|
77181
77215
|
}
|
|
77182
77216
|
function FilterSurfaceGroup({ title, masonry = false, className, children, }) {
|
|
77183
77217
|
return (jsxs("section", { className: "flex flex-col gap-3", children: [title && (jsx("h2", { className: "text-xs font-semibold uppercase tracking-wide text-label", children: title })), jsx("div", { className: cn('grid grid-cols-1 items-start lg:grid-cols-6', masonry ? MASONRY_GRID_CLASS_NAME : 'gap-3', className), children: masonry
|