@caratlogic/design-system 0.1.36 → 0.1.38
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/Filters.types.d.ts +1 -1
- package/dist/components/molecules/Filters/Filters.types.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 +120 -89
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +120 -89
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -15707,7 +15707,7 @@ function getOptionRowClassName(selected = false, className) {
|
|
|
15707
15707
|
}
|
|
15708
15708
|
/** A selectable pill (chip options, range presets); one height and shape everywhere. */
|
|
15709
15709
|
function getPillClassName(selected) {
|
|
15710
|
-
return getSelectableOptionClassName(selected, 'inline-flex h-8 items-center gap-1.5 rounded-
|
|
15710
|
+
return getSelectableOptionClassName(selected, 'inline-flex h-8 items-center gap-1.5 rounded-sm px-3');
|
|
15711
15711
|
}
|
|
15712
15712
|
/** Search box above a list (pick lists, saved searches); matches the inputs' height and corners. */
|
|
15713
15713
|
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';
|
|
@@ -15888,14 +15888,15 @@ const LIST_COLUMN_MIN_WIDTH = '10rem';
|
|
|
15888
15888
|
const getOptionsScrollClassName = (fill) => fill ? OPTIONS_FILL_CLASS_NAME : OPTIONS_SCROLL_CLASS_NAME;
|
|
15889
15889
|
const LIST_ROW_CLASS_NAME = getOptionRowClassName();
|
|
15890
15890
|
const COUNT_BADGE_TONES = {
|
|
15891
|
-
neutral: 'bg-
|
|
15892
|
-
tinted: 'bg-primary/
|
|
15893
|
-
solid: 'bg-primary-foreground/20 text-primary-foreground',
|
|
15891
|
+
neutral: 'border-primary/30 bg-primary/5 text-primary',
|
|
15892
|
+
tinted: 'border-primary/30 bg-primary/5 text-primary',
|
|
15893
|
+
solid: 'border-primary-foreground/60 bg-primary-foreground/20 text-primary-foreground',
|
|
15894
15894
|
};
|
|
15895
15895
|
/** 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-
|
|
15896
|
+
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
15897
|
const COUNT_BADGE_CLASS_NAME = getCountBadgeClassName('neutral');
|
|
15898
|
-
|
|
15898
|
+
/** Plain digits (no thousands separators), so the badge stays compact. */
|
|
15899
|
+
const formatOptionCount = (count) => String(count);
|
|
15899
15900
|
/** Picking a value: a single-choice list replaces (or clears) it, a multi-choice list toggles it. */
|
|
15900
15901
|
const pickValue = (values, value, single) => {
|
|
15901
15902
|
if (!single)
|
|
@@ -15931,7 +15932,7 @@ function PickListFilter({ options, value, onChange, ariaLabel, layout = 'list',
|
|
|
15931
15932
|
});
|
|
15932
15933
|
const radioName = useId$2();
|
|
15933
15934
|
const scrollClassName = getOptionsScrollClassName(fill);
|
|
15934
|
-
//
|
|
15935
|
+
// Two-line rows (label + hint) read best in one column.
|
|
15935
15936
|
const multiColumn = fill && !options.some((option) => option.hint);
|
|
15936
15937
|
const wrapperClassName = fill ? FILL_WRAPPER_CLASS_NAME : undefined;
|
|
15937
15938
|
const toggle = (option) => onChange(pickValue(value, option.value, single));
|
|
@@ -15944,12 +15945,14 @@ function PickListFilter({ options, value, onChange, ariaLabel, layout = 'list',
|
|
|
15944
15945
|
? getGridColumnsStyle(undefined, LIST_COLUMN_MIN_WIDTH)
|
|
15945
15946
|
: undefined, wrapperClassName: wrapperClassName, children: visibleOptions.map((option) => (jsxs("label", { className: LIST_ROW_CLASS_NAME, children: [single ? (jsx("input", { type: "radio", name: radioName, checked: value.includes(option.value),
|
|
15946
15947
|
// A click on the picked row clears it (radios can't be unticked natively).
|
|
15947
|
-
onClick: () => toggle(option), onChange: () => undefined, className: LIST_RADIO_CLASS_NAME })) : (jsx(Checkbox, { className: LIST_CHECKBOX_CLASS_NAME, checked: value.includes(option.value), onCheckedChange: () => toggle(option) })), option.content ? (jsx("span", { className: "min-w-0 flex-1", children: option.content })) :
|
|
15948
|
+
onClick: () => toggle(option), onChange: () => undefined, className: LIST_RADIO_CLASS_NAME })) : (jsx(Checkbox, { className: LIST_CHECKBOX_CLASS_NAME, checked: value.includes(option.value), onCheckedChange: () => toggle(option) })), option.content ? (jsx("span", { className: "min-w-0 flex-1", children: option.content })) : option.hint ? (
|
|
15949
|
+
// The hint sits under the label so a long name keeps the row's full width.
|
|
15950
|
+
jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [jsx(TruncatedLabel, { text: option.label }), jsx(TruncatedLabel, { text: option.hint, className: "text-xs text-muted-foreground" })] })) : (jsx(TruncatedLabel, { text: option.label, className: "flex-1" })), jsx(OptionSwatch, { color: option.swatch }), option.count !== undefined && (jsx("span", { className: COUNT_BADGE_CLASS_NAME, children: formatOptionCount(option.count) }))] }, option.value))) })] }));
|
|
15948
15951
|
}
|
|
15949
15952
|
if (layout === 'tiles') {
|
|
15950
15953
|
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))) }));
|
|
15951
15954
|
}
|
|
15952
|
-
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:
|
|
15955
|
+
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))) }));
|
|
15953
15956
|
}
|
|
15954
15957
|
|
|
15955
15958
|
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';
|
|
@@ -38564,6 +38567,70 @@ function fieldTypeIcon(type) {
|
|
|
38564
38567
|
return FIELD_TYPE_ICONS[type] ?? Type;
|
|
38565
38568
|
}
|
|
38566
38569
|
|
|
38570
|
+
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';
|
|
38571
|
+
/** Children laid out below the first one's line. */
|
|
38572
|
+
function countWrapped(items) {
|
|
38573
|
+
if (items.length === 0)
|
|
38574
|
+
return 0;
|
|
38575
|
+
const firstTop = items[0].offsetTop;
|
|
38576
|
+
return items.filter((item) => item.offsetTop > firstTop).length;
|
|
38577
|
+
}
|
|
38578
|
+
const getShowMoreLabel = (hiddenCount) => `+${hiddenCount} more`;
|
|
38579
|
+
|
|
38580
|
+
/**
|
|
38581
|
+
* Fits chips on one line with the "+N more" link right after the last one. Renders every chip, counts
|
|
38582
|
+
* how many wrap, then drops chips until the link itself fits; a resize starts the measurement over.
|
|
38583
|
+
*/
|
|
38584
|
+
function useChipOverflow(chips, enabled) {
|
|
38585
|
+
const [list, setList] = useState(null);
|
|
38586
|
+
const [expanded, setExpanded] = useState(false);
|
|
38587
|
+
const [visibleCount, setVisibleCount] = useState(chips.length);
|
|
38588
|
+
const collapsed = enabled && !expanded;
|
|
38589
|
+
const [measuredLength, setMeasuredLength] = useState(chips.length);
|
|
38590
|
+
if (measuredLength !== chips.length) {
|
|
38591
|
+
setMeasuredLength(chips.length);
|
|
38592
|
+
setVisibleCount(chips.length);
|
|
38593
|
+
}
|
|
38594
|
+
useLayoutEffect(() => {
|
|
38595
|
+
if (!collapsed || !list)
|
|
38596
|
+
return;
|
|
38597
|
+
const items = Array.from(list.children);
|
|
38598
|
+
if (visibleCount >= chips.length) {
|
|
38599
|
+
const wrapped = countWrapped(items);
|
|
38600
|
+
if (wrapped > 0)
|
|
38601
|
+
setVisibleCount(chips.length - wrapped);
|
|
38602
|
+
return;
|
|
38603
|
+
}
|
|
38604
|
+
// The last child is the "+N more" link; if it wrapped, give it one more chip's room.
|
|
38605
|
+
if (countWrapped(items) > 0 && visibleCount > 1)
|
|
38606
|
+
setVisibleCount(visibleCount - 1);
|
|
38607
|
+
}, [collapsed, list, visibleCount, chips.length]);
|
|
38608
|
+
useEffect(() => {
|
|
38609
|
+
if (!collapsed || !list)
|
|
38610
|
+
return;
|
|
38611
|
+
let width = list.clientWidth;
|
|
38612
|
+
const observer = new ResizeObserver(() => {
|
|
38613
|
+
if (list.clientWidth === width)
|
|
38614
|
+
return;
|
|
38615
|
+
width = list.clientWidth;
|
|
38616
|
+
setVisibleCount(chips.length);
|
|
38617
|
+
});
|
|
38618
|
+
observer.observe(list);
|
|
38619
|
+
return () => observer.disconnect();
|
|
38620
|
+
}, [collapsed, list, chips.length]);
|
|
38621
|
+
const shown = collapsed ? Math.min(visibleCount, chips.length) : chips.length;
|
|
38622
|
+
return {
|
|
38623
|
+
listRef: useCallback((node) => setList(node), []),
|
|
38624
|
+
expanded,
|
|
38625
|
+
toggle: () => {
|
|
38626
|
+
setExpanded((current) => !current);
|
|
38627
|
+
setVisibleCount(chips.length);
|
|
38628
|
+
},
|
|
38629
|
+
visibleChips: chips.slice(0, shown),
|
|
38630
|
+
hiddenCount: chips.length - shown,
|
|
38631
|
+
};
|
|
38632
|
+
}
|
|
38633
|
+
|
|
38567
38634
|
const getRemoveChipLabel = (label) => `Remove ${label} filter`;
|
|
38568
38635
|
const FILTERED_BY_LABEL = 'Filtered by';
|
|
38569
38636
|
|
|
@@ -38571,16 +38638,33 @@ const FILTERED_BY_LABEL = 'Filtered by';
|
|
|
38571
38638
|
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';
|
|
38572
38639
|
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';
|
|
38573
38640
|
const CLEAR_ALL_CLASS = cn(FILTER_LINK_CLASS_NAME, 'underline');
|
|
38641
|
+
const SHOW_MORE_CLASS = cn(FILTER_LINK_CLASS_NAME, 'whitespace-nowrap px-1 underline underline-offset-2');
|
|
38574
38642
|
function getChipLabelText(label, variant) {
|
|
38575
38643
|
return variant === 'inline' ? `${label}:` : label;
|
|
38576
38644
|
}
|
|
38577
38645
|
|
|
38646
|
+
function Chip({ chip, variant, onRemove }) {
|
|
38647
|
+
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" }) }))] }));
|
|
38648
|
+
}
|
|
38649
|
+
/** One line of chips ending in "+N more"; Clear all and the actions stay at the right end. */
|
|
38650
|
+
function CollapsibleChips({ chips, onRemove, variant = 'inline', clearAll, actions, className, }) {
|
|
38651
|
+
const { listRef, expanded, toggle, visibleChips, hiddenCount } = useChipOverflow(chips.map((chip) => chip.id), true);
|
|
38652
|
+
const visible = new Set(visibleChips);
|
|
38653
|
+
return (
|
|
38654
|
+
// Centred, so the label and Clear all line up with buttons beside the row even when the chips wrap.
|
|
38655
|
+
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
|
|
38656
|
+
.filter((chip) => visible.has(chip.id))
|
|
38657
|
+
.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] }))] }));
|
|
38658
|
+
}
|
|
38578
38659
|
/** Applied filters as removable chips, with an optional Clear all. */
|
|
38579
|
-
function ActiveFilterChips({ chips, onRemove, onClearAll, variant = 'inline', actions, className, }) {
|
|
38660
|
+
function ActiveFilterChips({ chips, onRemove, onClearAll, variant = 'inline', actions, collapsible = false, className, }) {
|
|
38580
38661
|
if (chips.length === 0)
|
|
38581
38662
|
return null;
|
|
38582
38663
|
const clearAll = onClearAll && (jsx("button", { type: "button", onClick: onClearAll, className: CLEAR_ALL_CLASS, children: "Clear all" }));
|
|
38583
|
-
|
|
38664
|
+
if (collapsible) {
|
|
38665
|
+
return (jsx(CollapsibleChips, { chips: chips, onRemove: onRemove, variant: variant, clearAll: clearAll, actions: actions, className: className }));
|
|
38666
|
+
}
|
|
38667
|
+
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 ? (
|
|
38584
38668
|
// With actions, Clear all joins them at the row's right end.
|
|
38585
38669
|
jsxs("div", { className: "ml-auto flex items-center gap-3 pl-3", children: [clearAll, actions] })) : (clearAll)] }));
|
|
38586
38670
|
}
|
|
@@ -38782,14 +38866,18 @@ function getNextScaleRange(anchor, option) {
|
|
|
38782
38866
|
? { from: option, to: option }
|
|
38783
38867
|
: { from: anchor, to: option };
|
|
38784
38868
|
}
|
|
38869
|
+
const SCALE_POSITION_CLASS_NAMES = {
|
|
38870
|
+
// Unselected options look exactly like unselected pick-list options.
|
|
38871
|
+
outside: getSelectableOptionClassName(false),
|
|
38872
|
+
end: 'border-primary bg-primary font-medium text-primary-foreground',
|
|
38873
|
+
between: 'border-primary/30 bg-primary/10 font-medium text-primary',
|
|
38874
|
+
};
|
|
38875
|
+
/** A pill like the other pick lists' (`Single`, `Parcel`), coloured by where it sits in the range. */
|
|
38876
|
+
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]);
|
|
38785
38877
|
function getScaleButtonClassName(position, fill = false, hasCount = false) {
|
|
38786
38878
|
return cn('flex min-w-0 cursor-pointer items-center gap-2 rounded-md border transition-colors',
|
|
38787
38879
|
// With a count: name left, badge right (like a list row); without: name centred.
|
|
38788
|
-
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,
|
|
38789
|
-
// Unselected options look exactly like unselected pick-list tiles.
|
|
38790
|
-
position === 'outside' && getSelectableOptionClassName(false), position === 'end' &&
|
|
38791
|
-
'border-primary bg-primary font-medium text-primary-foreground', position === 'between' &&
|
|
38792
|
-
'border-primary/30 bg-primary/10 font-medium text-primary');
|
|
38880
|
+
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]);
|
|
38793
38881
|
}
|
|
38794
38882
|
/** The count badge follows the tile: plain, tinted inside the range, solid on an end. */
|
|
38795
38883
|
const getScaleCountTone = (position) => position === 'end' ? 'solid' : position === 'between' ? 'tinted' : 'neutral';
|
|
@@ -38809,8 +38897,14 @@ function useScaleRangeFilter({ value, onChange, }) {
|
|
|
38809
38897
|
}
|
|
38810
38898
|
|
|
38811
38899
|
/** Ordered-scale filter: click a start option, then an end option. */
|
|
38812
|
-
function ScaleRangeFilter({ options, value, onChange, ariaLabel, columns, fill = false, }) {
|
|
38900
|
+
function ScaleRangeFilter({ options, value, onChange, ariaLabel, layout = 'pills', columns, fill = false, }) {
|
|
38813
38901
|
const { selectOption } = useScaleRangeFilter({ value, onChange });
|
|
38902
|
+
if (layout === 'pills') {
|
|
38903
|
+
return (jsx("div", { role: "group", "aria-label": ariaLabel, className: "flex flex-wrap content-start gap-1.5", children: options.map((option) => {
|
|
38904
|
+
const position = getScalePosition(options, value, option.value);
|
|
38905
|
+
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));
|
|
38906
|
+
}) }));
|
|
38907
|
+
}
|
|
38814
38908
|
return (jsx("div", { role: "group", "aria-label": ariaLabel, className: fill ? 'grid gap-2' : 'grid gap-1.5', style: getGridColumnsStyle(columns), children: options.map((option) => {
|
|
38815
38909
|
const position = getScalePosition(options, value, option.value);
|
|
38816
38910
|
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));
|
|
@@ -38824,7 +38918,7 @@ const TILE_KEYS = ['a', 'b', 'c', 'd'];
|
|
|
38824
38918
|
const CARD_CLASS_NAME = 'flex h-full min-w-0 flex-col gap-3 rounded-xl border border-border bg-card p-4';
|
|
38825
38919
|
|
|
38826
38920
|
function Pills({ count }) {
|
|
38827
|
-
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: "
|
|
38921
|
+
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))) }));
|
|
38828
38922
|
}
|
|
38829
38923
|
function Body({ variant, }) {
|
|
38830
38924
|
switch (variant) {
|
|
@@ -39134,70 +39228,6 @@ function TriStateFilter({ value, onChange, ariaLabel, compact = false, }) {
|
|
|
39134
39228
|
}) }));
|
|
39135
39229
|
}
|
|
39136
39230
|
|
|
39137
|
-
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';
|
|
39138
|
-
/** Children laid out below the first one's line. */
|
|
39139
|
-
function countWrapped(items) {
|
|
39140
|
-
if (items.length === 0)
|
|
39141
|
-
return 0;
|
|
39142
|
-
const firstTop = items[0].offsetTop;
|
|
39143
|
-
return items.filter((item) => item.offsetTop > firstTop).length;
|
|
39144
|
-
}
|
|
39145
|
-
const getShowMoreLabel = (hiddenCount) => `+${hiddenCount} more`;
|
|
39146
|
-
|
|
39147
|
-
/**
|
|
39148
|
-
* Fits chips on one line with the "+N more" link right after the last one. Renders every chip, counts
|
|
39149
|
-
* how many wrap, then drops chips until the link itself fits; a resize starts the measurement over.
|
|
39150
|
-
*/
|
|
39151
|
-
function useChipOverflow(chips, enabled) {
|
|
39152
|
-
const [list, setList] = useState(null);
|
|
39153
|
-
const [expanded, setExpanded] = useState(false);
|
|
39154
|
-
const [visibleCount, setVisibleCount] = useState(chips.length);
|
|
39155
|
-
const collapsed = enabled && !expanded;
|
|
39156
|
-
const [measuredLength, setMeasuredLength] = useState(chips.length);
|
|
39157
|
-
if (measuredLength !== chips.length) {
|
|
39158
|
-
setMeasuredLength(chips.length);
|
|
39159
|
-
setVisibleCount(chips.length);
|
|
39160
|
-
}
|
|
39161
|
-
useLayoutEffect(() => {
|
|
39162
|
-
if (!collapsed || !list)
|
|
39163
|
-
return;
|
|
39164
|
-
const items = Array.from(list.children);
|
|
39165
|
-
if (visibleCount >= chips.length) {
|
|
39166
|
-
const wrapped = countWrapped(items);
|
|
39167
|
-
if (wrapped > 0)
|
|
39168
|
-
setVisibleCount(chips.length - wrapped);
|
|
39169
|
-
return;
|
|
39170
|
-
}
|
|
39171
|
-
// The last child is the "+N more" link; if it wrapped, give it one more chip's room.
|
|
39172
|
-
if (countWrapped(items) > 0 && visibleCount > 1)
|
|
39173
|
-
setVisibleCount(visibleCount - 1);
|
|
39174
|
-
}, [collapsed, list, visibleCount, chips.length]);
|
|
39175
|
-
useEffect(() => {
|
|
39176
|
-
if (!collapsed || !list)
|
|
39177
|
-
return;
|
|
39178
|
-
let width = list.clientWidth;
|
|
39179
|
-
const observer = new ResizeObserver(() => {
|
|
39180
|
-
if (list.clientWidth === width)
|
|
39181
|
-
return;
|
|
39182
|
-
width = list.clientWidth;
|
|
39183
|
-
setVisibleCount(chips.length);
|
|
39184
|
-
});
|
|
39185
|
-
observer.observe(list);
|
|
39186
|
-
return () => observer.disconnect();
|
|
39187
|
-
}, [collapsed, list, chips.length]);
|
|
39188
|
-
const shown = collapsed ? Math.min(visibleCount, chips.length) : chips.length;
|
|
39189
|
-
return {
|
|
39190
|
-
listRef: useCallback((node) => setList(node), []),
|
|
39191
|
-
expanded,
|
|
39192
|
-
toggle: () => {
|
|
39193
|
-
setExpanded((current) => !current);
|
|
39194
|
-
setVisibleCount(chips.length);
|
|
39195
|
-
},
|
|
39196
|
-
visibleChips: chips.slice(0, shown),
|
|
39197
|
-
hiddenCount: chips.length - shown,
|
|
39198
|
-
};
|
|
39199
|
-
}
|
|
39200
|
-
|
|
39201
39231
|
/** Read-only filter summaries of a saved search, as pills; optionally one line ending in "+N more". */
|
|
39202
39232
|
function SavedSearchChipList({ chips, emptyText = 'No filters', collapsible = false, className, }) {
|
|
39203
39233
|
const { listRef, expanded, toggle, visibleChips, hiddenCount } = useChipOverflow(chips, collapsible);
|
|
@@ -77021,7 +77051,7 @@ const getDropdownPlaceholder = (field) => field.inputPlaceholder ??
|
|
|
77021
77051
|
`${field.single ? 'Any' : 'Select'} ${field.label.toLowerCase()}`;
|
|
77022
77052
|
/** Popover variant: the same control and layout as the card (pills up to 4 options, then the list); tile grids drop to two columns. */
|
|
77023
77053
|
function toPopoverField(field) {
|
|
77024
|
-
if (field.type === 'scale' ||
|
|
77054
|
+
if ((field.type === 'scale' && field.layout === 'tiles') ||
|
|
77025
77055
|
(field.type === 'list' && resolveListLayout(field) === 'tiles')) {
|
|
77026
77056
|
return { ...field, columns: POPOVER_TILE_COLUMNS };
|
|
77027
77057
|
}
|
|
@@ -77054,7 +77084,7 @@ function FilterFieldControl({ field, value, onChange, fiscalYearStartMonth, fill
|
|
|
77054
77084
|
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 }));
|
|
77055
77085
|
}
|
|
77056
77086
|
case 'scale':
|
|
77057
|
-
return (jsx(ScaleRangeFilter, { ariaLabel: ariaLabel, options: field.options ?? [], value: valueOfType(value, 'scale')?.range, onChange: (range) => onChange(toScaleValue(range)), columns: field.columns, fill: fill }));
|
|
77087
|
+
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 }));
|
|
77058
77088
|
case 'number':
|
|
77059
77089
|
return (jsx(NumberRangeInputs, { ariaLabel: field.label, value: valueOfType(value, 'number')?.range, onChange: (range) => onChange(toNumberValue(range)), presets: field.presets, presetsLabel: field.presetsLabel }));
|
|
77060
77090
|
case 'ids':
|
|
@@ -77100,9 +77130,8 @@ function FilterField(props) {
|
|
|
77100
77130
|
return mode === 'cell' ? (jsx(FilterFieldCell, { ...rest })) : (jsx(FilterFieldCard, { mode: mode, ...rest }));
|
|
77101
77131
|
}
|
|
77102
77132
|
|
|
77103
|
-
/**
|
|
77133
|
+
/** Leaves without applying; the draft is discarded. */
|
|
77104
77134
|
const BACK_LABEL = 'Back without applying';
|
|
77105
|
-
const CLOSE_LABEL = 'Close without applying';
|
|
77106
77135
|
const LOADING_LABEL = 'Loading filters';
|
|
77107
77136
|
const ERROR_TITLE = "Couldn't load filters";
|
|
77108
77137
|
const DEFAULT_ERROR_MESSAGE = 'Something went wrong while loading the filters. Check your connection and try again.';
|
|
@@ -77172,10 +77201,12 @@ function MasonryItem({ children }) {
|
|
|
77172
77201
|
return (jsx("div", { style: span > 0 ? { gridRowEnd: `span ${span}` } : undefined, children: jsx("div", { ref: ref, className: "pb-3", children: children }) }));
|
|
77173
77202
|
}
|
|
77174
77203
|
|
|
77175
|
-
/** Full-page filter screen (e.g. an advanced search): header, scrolling card grid
|
|
77176
|
-
function FilterSurface({ title, headerActions,
|
|
77204
|
+
/** Full-page filter screen (e.g. an advanced search): one sticky header with every action, then the scrolling card grid. */
|
|
77205
|
+
function FilterSurface({ title, headerActions, primaryActions, chips, onClose, status = 'ready', errorMessage, onRetry, skeleton, children, }) {
|
|
77177
77206
|
useCloseOnEscape(onClose);
|
|
77178
|
-
|
|
77207
|
+
const ready = status === 'ready';
|
|
77208
|
+
const actions = ready ? primaryActions : undefined;
|
|
77209
|
+
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 })) })] }));
|
|
77179
77210
|
}
|
|
77180
77211
|
function FilterSurfaceGroup({ title, masonry = false, className, children, }) {
|
|
77181
77212
|
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
|