@campfire-interactive/ui 0.5.0 → 0.6.0

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/index.d.ts CHANGED
@@ -685,6 +685,29 @@ interface ComboboxProps<T extends string = string> {
685
685
  loading?: boolean;
686
686
  emptyLabel?: string;
687
687
  loadingLabel?: string;
688
+ /**
689
+ * How many rows the list renders at once. The rest are not dropped quietly —
690
+ * a notice takes the last row and says how many are shown, because a picker
691
+ * silently offering 20 of 200 is the same bug as a table silently paging.
692
+ * Pass `0` to render everything, which is right only for a list bounded by
693
+ * the domain rather than by the customer.
694
+ */
695
+ maxResults?: number;
696
+ /** Overrides the truncation notice. Receives how many rows are shown. */
697
+ truncatedLabel?: (shown: number) => string;
698
+ /**
699
+ * Fires as the list opens and closes. `Select` has had this since pm's
700
+ * click-to-edit cells needed it: those save on blur, and a portalled list
701
+ * moves focus off the trigger, so without knowing the list is open a cell
702
+ * closes itself the instant the user clicks into it.
703
+ */
704
+ onOpenChange?: (open: boolean) => void;
705
+ /**
706
+ * Forwarded to the trigger. Only useful alongside `onOpenChange`, which is
707
+ * what tells a commit-on-blur caller to ignore the blur the list itself
708
+ * caused.
709
+ */
710
+ onBlur?: React.FocusEventHandler<HTMLButtonElement>;
688
711
  /** Adds a "Clear" affordance to the trigger once something is selected. */
689
712
  clearable?: boolean;
690
713
  size?: 'sm' | 'md' | 'lg';
@@ -717,11 +740,15 @@ interface ComboboxProps<T extends string = string> {
717
740
  * typing a part number finds a row whose label is a description. Pass
718
741
  * `onSearchChange` to filter on the server instead.
719
742
  *
743
+ * `maxResults` caps what is rendered (50 by default) and says so in the last
744
+ * row. Options carrying a `group` are rendered under a heading, in the order
745
+ * the caller gives them — which is how a picker puts RECENTS above SEARCH.
746
+ *
720
747
  * Keyboard: ArrowUp/ArrowDown move, Home/End jump, Enter selects, Escape
721
748
  * closes. The list is `aria-activedescendant`-driven, so focus stays in the
722
749
  * search field and a screen reader announces each row as it is highlighted.
723
750
  */
724
- declare function Combobox<T extends string = string>({ options, value, onValueChange, placeholder, searchPlaceholder, onSearchChange, loading, emptyLabel, loadingLabel, clearable, size, invalid, disabled, id, name, className, style, fullWidth, ...aria }: ComboboxProps<T>): react.JSX.Element;
751
+ declare function Combobox<T extends string = string>({ options, value, onValueChange, placeholder, searchPlaceholder, onSearchChange, loading, emptyLabel, loadingLabel, maxResults, truncatedLabel, onOpenChange, onBlur, clearable, size, invalid, disabled, id, name, className, style, fullWidth, ...aria }: ComboboxProps<T>): react.JSX.Element;
725
752
 
726
753
  declare const TooltipProvider: react.FC<RadixTooltip.TooltipProviderProps>;
727
754
  declare const TooltipRoot: react.FC<RadixTooltip.TooltipProps>;
package/dist/index.js CHANGED
@@ -519,7 +519,7 @@ function FilterBar({
519
519
  out.push({ id: s.id, name: s.label, display: opt ? opt.label : v });
520
520
  }
521
521
  for (const tg of toggles) {
522
- if (value[tg.id] === true) out.push({ id: tg.id, name: tg.label, display: tg.label });
522
+ if (value[tg.id] === true) out.push({ id: tg.id, display: tg.label });
523
523
  }
524
524
  return out;
525
525
  }, [selects, toggles, value]);
@@ -571,14 +571,14 @@ function FilterBar({
571
571
  }
572
572
  ),
573
573
  applied.map((a) => /* @__PURE__ */ jsxs8("span", { className: "cfi-ui-filterbar__chip", children: [
574
- /* @__PURE__ */ jsx8("span", { className: "cfi-ui-filterbar__chip-name", children: a.name }),
574
+ a.name && /* @__PURE__ */ jsx8("span", { className: "cfi-ui-filterbar__chip-name", children: a.name }),
575
575
  a.display,
576
576
  /* @__PURE__ */ jsx8(
577
577
  "button",
578
578
  {
579
579
  type: "button",
580
580
  className: "cfi-ui-filterbar__chip-x",
581
- "aria-label": removeLabel(a.name),
581
+ "aria-label": removeLabel(a.name ?? String(a.display)),
582
582
  onClick: () => set(a.id, void 0),
583
583
  children: /* @__PURE__ */ jsx8(X, { size: 12, "aria-hidden": "true" })
584
584
  }
@@ -1067,11 +1067,11 @@ function CreatableSelect({
1067
1067
  }
1068
1068
 
1069
1069
  // src/Combobox.tsx
1070
- import { useEffect, useId as useId2, useMemo as useMemo3, useRef, useState as useState3 } from "react";
1070
+ import { Fragment as Fragment3, useEffect, useId as useId2, useMemo as useMemo3, useRef, useState as useState3 } from "react";
1071
1071
  import { Check as Check4, ChevronDown as ChevronDown3, Search, X as X3 } from "lucide-react";
1072
1072
 
1073
1073
  // src/Combobox.css
1074
- styleInject("@layer components {\n .cfi-ui-combobox {\n position: relative;\n display: inline-flex;\n align-items: center;\n }\n .cfi-ui-combobox--full {\n display: flex;\n width: 100%;\n }\n :where(.cfi-ui-combobox__trigger) {\n display: inline-flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--cfi-ui-space-sm);\n width: 100%;\n height: var(--cfi-ui-control-height-md);\n padding-inline: 0.625rem;\n background: var(--cfi-ui-surface);\n color: var(--cfi-ui-content);\n border: 1px solid var(--cfi-ui-border-strong);\n border-radius: var(--cfi-ui-radius-md);\n font-family: inherit;\n font-size: var(--cfi-ui-text-sm);\n line-height: normal;\n cursor: pointer;\n transition: border-color var(--cfi-ui-transition-fast);\n }\n .cfi-ui-combobox__trigger:hover:not(:disabled) {\n border-color: var(--cfi-ui-content-subtle);\n }\n .cfi-ui-combobox__trigger:disabled {\n background: var(--cfi-ui-surface-disabled);\n color: var(--cfi-ui-content-disabled);\n cursor: not-allowed;\n }\n .cfi-ui-combobox__trigger[data-invalid] {\n border-color: var(--cfi-ui-error);\n }\n .cfi-ui-combobox__trigger[data-size=sm] {\n height: var(--cfi-ui-control-height-sm);\n padding-inline: 0.5rem;\n font-size: var(--cfi-ui-text-xs);\n }\n .cfi-ui-combobox__trigger[data-size=lg] {\n height: var(--cfi-ui-control-height-lg);\n padding-inline: 0.875rem;\n font-size: var(--cfi-ui-text-base);\n }\n .cfi-ui-combobox__trigger[data-placeholder] .cfi-ui-combobox__value {\n color: var(--cfi-ui-content-muted);\n }\n .cfi-ui-combobox__value {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n text-align: left;\n }\n .cfi-ui-combobox__chevron {\n display: inline-flex;\n flex: none;\n color: var(--cfi-ui-content-muted);\n }\n .cfi-ui-combobox__chevron > svg,\n .cfi-ui-combobox__clear > svg {\n width: 0.875rem;\n height: 0.875rem;\n display: block;\n }\n .cfi-ui-combobox__clear {\n position: absolute;\n right: 1.75rem;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1.25rem;\n height: 1.25rem;\n padding: 0;\n border: 0;\n border-radius: var(--cfi-ui-radius-sm);\n background: transparent;\n color: var(--cfi-ui-content-muted);\n cursor: pointer;\n }\n .cfi-ui-combobox__clear:hover {\n background: var(--cfi-ui-surface-active);\n color: var(--cfi-ui-content);\n }\n .cfi-ui-combobox__panel {\n display: flex;\n flex-direction: column;\n overflow: hidden;\n max-height: min(22rem, var(--radix-popover-content-available-height, 22rem));\n }\n .cfi-ui-combobox__search {\n flex: none;\n display: flex;\n align-items: center;\n gap: var(--cfi-ui-space-sm);\n padding: var(--cfi-ui-space-sm);\n border-bottom: 1px solid var(--cfi-ui-border);\n color: var(--cfi-ui-content-muted);\n }\n .cfi-ui-combobox__search > svg {\n width: 0.875rem;\n height: 0.875rem;\n flex: none;\n }\n .cfi-ui-combobox__search input {\n flex: 1 1 auto;\n min-width: 0;\n border: 0;\n background: transparent;\n color: var(--cfi-ui-content);\n font-family: inherit;\n font-size: var(--cfi-ui-text-sm);\n outline: none;\n }\n .cfi-ui-combobox__search input::placeholder {\n color: var(--cfi-ui-content-muted);\n }\n .cfi-ui-combobox__list {\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n padding: var(--cfi-ui-space-xs);\n }\n .cfi-ui-combobox__option {\n align-items: flex-start;\n }\n .cfi-ui-combobox__option > .cfi-ui-select-item__indicator {\n padding-top: 0.125rem;\n }\n .cfi-ui-combobox__option-text {\n display: grid;\n gap: var(--cfi-ui-space-3xs);\n min-width: 0;\n }\n .cfi-ui-combobox__option-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .cfi-ui-combobox__option-hint {\n font-size: var(--cfi-ui-text-xs);\n color: var(--cfi-ui-content-muted);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n}\n");
1074
+ styleInject("@layer components {\n .cfi-ui-combobox {\n position: relative;\n display: inline-flex;\n align-items: center;\n }\n .cfi-ui-combobox--full {\n display: flex;\n width: 100%;\n }\n :where(.cfi-ui-combobox__trigger) {\n display: inline-flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--cfi-ui-space-sm);\n width: 100%;\n height: var(--cfi-ui-control-height-md);\n padding-inline: 0.625rem;\n background: var(--cfi-ui-surface);\n color: var(--cfi-ui-content);\n border: 1px solid var(--cfi-ui-border-strong);\n border-radius: var(--cfi-ui-radius-md);\n font-family: inherit;\n font-size: var(--cfi-ui-text-sm);\n line-height: normal;\n cursor: pointer;\n transition: border-color var(--cfi-ui-transition-fast);\n }\n .cfi-ui-combobox__trigger:hover:not(:disabled) {\n border-color: var(--cfi-ui-content-subtle);\n }\n .cfi-ui-combobox__trigger:disabled {\n background: var(--cfi-ui-surface-disabled);\n color: var(--cfi-ui-content-disabled);\n cursor: not-allowed;\n }\n .cfi-ui-combobox__trigger[data-invalid] {\n border-color: var(--cfi-ui-error);\n }\n .cfi-ui-combobox__trigger[data-size=sm] {\n height: var(--cfi-ui-control-height-sm);\n padding-inline: 0.5rem;\n font-size: var(--cfi-ui-text-xs);\n }\n .cfi-ui-combobox__trigger[data-size=lg] {\n height: var(--cfi-ui-control-height-lg);\n padding-inline: 0.875rem;\n font-size: var(--cfi-ui-text-base);\n }\n .cfi-ui-combobox__trigger[data-placeholder] .cfi-ui-combobox__value {\n color: var(--cfi-ui-content-muted);\n }\n .cfi-ui-combobox__value {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n text-align: left;\n }\n .cfi-ui-combobox__chevron {\n display: inline-flex;\n flex: none;\n color: var(--cfi-ui-content-muted);\n }\n .cfi-ui-combobox__chevron > svg,\n .cfi-ui-combobox__clear > svg {\n width: 0.875rem;\n height: 0.875rem;\n display: block;\n }\n .cfi-ui-combobox__clear {\n position: absolute;\n right: 1.75rem;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1.25rem;\n height: 1.25rem;\n padding: 0;\n border: 0;\n border-radius: var(--cfi-ui-radius-sm);\n background: transparent;\n color: var(--cfi-ui-content-muted);\n cursor: pointer;\n }\n .cfi-ui-combobox__clear:hover {\n background: var(--cfi-ui-surface-active);\n color: var(--cfi-ui-content);\n }\n .cfi-ui-combobox__panel {\n display: flex;\n flex-direction: column;\n overflow: hidden;\n max-height: min(22rem, var(--radix-popover-content-available-height, 22rem));\n }\n .cfi-ui-combobox__search {\n flex: none;\n display: flex;\n align-items: center;\n gap: var(--cfi-ui-space-sm);\n padding: var(--cfi-ui-space-sm);\n border-bottom: 1px solid var(--cfi-ui-border);\n color: var(--cfi-ui-content-muted);\n }\n .cfi-ui-combobox__search > svg {\n width: 0.875rem;\n height: 0.875rem;\n flex: none;\n }\n .cfi-ui-combobox__search input {\n flex: 1 1 auto;\n min-width: 0;\n border: 0;\n background: transparent;\n color: var(--cfi-ui-content);\n font-family: inherit;\n font-size: var(--cfi-ui-text-sm);\n outline: none;\n }\n .cfi-ui-combobox__search input::placeholder {\n color: var(--cfi-ui-content-muted);\n }\n .cfi-ui-combobox__list {\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n padding: var(--cfi-ui-space-xs);\n }\n .cfi-ui-combobox__option {\n align-items: flex-start;\n }\n .cfi-ui-combobox__option > .cfi-ui-select-item__indicator {\n padding-top: 0.125rem;\n }\n .cfi-ui-combobox__option-text {\n display: grid;\n gap: var(--cfi-ui-space-3xs);\n min-width: 0;\n }\n .cfi-ui-combobox__option-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .cfi-ui-combobox__option-hint {\n font-size: var(--cfi-ui-text-xs);\n color: var(--cfi-ui-content-muted);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .cfi-ui-combobox__group-label {\n padding: 0.375rem 0.5rem 0.25rem;\n margin: 0;\n font-size: var(--cfi-ui-text-xs);\n font-weight: var(--cfi-ui-weight-semibold);\n letter-spacing: var(--cfi-ui-tracking-caps);\n text-transform: uppercase;\n color: var(--cfi-ui-content-muted);\n }\n .cfi-ui-combobox__list > [role=group] + [role=group] .cfi-ui-combobox__group-label {\n margin-top: var(--cfi-ui-space-xs);\n padding-top: calc(0.25rem + var(--cfi-ui-space-xs));\n border-top: 1px solid var(--cfi-ui-border);\n }\n .cfi-ui-combobox__truncated {\n flex: none;\n margin: 0;\n padding: var(--cfi-ui-space-sm);\n border-top: 1px solid var(--cfi-ui-border);\n background: var(--cfi-ui-surface-muted);\n font-size: var(--cfi-ui-text-xs);\n color: var(--cfi-ui-content-muted);\n text-align: center;\n }\n}\n");
1075
1075
 
1076
1076
  // src/Combobox.tsx
1077
1077
  import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
@@ -1085,6 +1085,10 @@ function Combobox({
1085
1085
  loading = false,
1086
1086
  emptyLabel = "No matches",
1087
1087
  loadingLabel = "Searching...",
1088
+ maxResults = 50,
1089
+ truncatedLabel = (shown) => `Showing the first ${shown}. Keep typing to narrow.`,
1090
+ onOpenChange,
1091
+ onBlur,
1088
1092
  clearable = false,
1089
1093
  size = "md",
1090
1094
  invalid,
@@ -1110,8 +1114,25 @@ function Combobox({
1110
1114
  (o) => o.label.toLowerCase().includes(q) || (o.hint?.toLowerCase().includes(q) ?? false)
1111
1115
  );
1112
1116
  }, [options, search, onSearchChange]);
1113
- const selectable = filtered.filter((o) => !o.disabled);
1117
+ const visible = useMemo3(
1118
+ () => maxResults > 0 ? filtered.slice(0, maxResults) : filtered,
1119
+ [filtered, maxResults]
1120
+ );
1121
+ const truncated = visible.length < filtered.length;
1122
+ const selectable = visible.filter((o) => !o.disabled);
1114
1123
  const selected = options.find((o) => o.value === value) ?? null;
1124
+ const lastLabel = useRef(null);
1125
+ if (selected) lastLabel.current = { value: selected.value, label: selected.label };
1126
+ const selectedLabel = selected?.label ?? (lastLabel.current?.value === value ? lastLabel.current.label : null);
1127
+ const groups = useMemo3(() => {
1128
+ const runs = [];
1129
+ for (const option of visible) {
1130
+ const last = runs[runs.length - 1];
1131
+ if (last && last.group === option.group) last.options.push(option);
1132
+ else runs.push({ group: option.group, options: [option] });
1133
+ }
1134
+ return runs;
1135
+ }, [visible]);
1115
1136
  useEffect(() => {
1116
1137
  setHighlighted((h) => selectable.length === 0 ? 0 : Math.min(h, selectable.length - 1));
1117
1138
  }, [selectable.length]);
@@ -1126,9 +1147,13 @@ function Combobox({
1126
1147
  const el = listRef.current?.querySelector('[data-highlighted="true"]');
1127
1148
  el?.scrollIntoView({ block: "nearest" });
1128
1149
  }, [highlighted, open]);
1150
+ function changeOpen(next) {
1151
+ setOpen(next);
1152
+ onOpenChange?.(next);
1153
+ }
1129
1154
  function commit(option) {
1130
1155
  onValueChange(option.value);
1131
- setOpen(false);
1156
+ changeOpen(false);
1132
1157
  }
1133
1158
  function handleKeyDown(e) {
1134
1159
  if (e.key === "ArrowDown") {
@@ -1156,7 +1181,7 @@ function Combobox({
1156
1181
  setHighlighted(0);
1157
1182
  onSearchChange?.(next);
1158
1183
  }
1159
- return /* @__PURE__ */ jsxs15(PopoverRoot, { open, onOpenChange: setOpen, children: [
1184
+ return /* @__PURE__ */ jsxs15(PopoverRoot, { open, onOpenChange: changeOpen, children: [
1160
1185
  /* @__PURE__ */ jsx15(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsxs15(
1161
1186
  "div",
1162
1187
  {
@@ -1175,22 +1200,23 @@ function Combobox({
1175
1200
  disabled,
1176
1201
  "data-size": size,
1177
1202
  "data-invalid": invalid || void 0,
1178
- "data-placeholder": selected ? void 0 : "",
1203
+ "data-placeholder": selectedLabel == null ? "" : void 0,
1179
1204
  className: "cfi-ui-combobox__trigger",
1180
- onClick: () => setOpen((o) => !o),
1205
+ onClick: () => changeOpen(!open),
1206
+ onBlur,
1181
1207
  ...aria,
1182
1208
  children: [
1183
- /* @__PURE__ */ jsx15("span", { className: "cfi-ui-combobox__value", children: selected ? selected.label : placeholder }),
1209
+ /* @__PURE__ */ jsx15("span", { className: "cfi-ui-combobox__value", children: selectedLabel ?? placeholder }),
1184
1210
  /* @__PURE__ */ jsx15("span", { className: "cfi-ui-combobox__chevron", "aria-hidden": "true", children: /* @__PURE__ */ jsx15(ChevronDown3, {}) })
1185
1211
  ]
1186
1212
  }
1187
1213
  ),
1188
- clearable && selected && !disabled && /* @__PURE__ */ jsx15(
1214
+ clearable && selectedLabel != null && !disabled && /* @__PURE__ */ jsx15(
1189
1215
  "button",
1190
1216
  {
1191
1217
  type: "button",
1192
1218
  className: "cfi-ui-combobox__clear",
1193
- "aria-label": `Clear ${selected.label}`,
1219
+ "aria-label": `Clear ${selectedLabel}`,
1194
1220
  title: "Clear",
1195
1221
  onClick: () => onValueChange(null),
1196
1222
  children: /* @__PURE__ */ jsx15(X3, { "aria-hidden": "true" })
@@ -1238,34 +1264,42 @@ function Combobox({
1238
1264
  role: "listbox",
1239
1265
  "aria-busy": loading || void 0,
1240
1266
  className: "cfi-ui-combobox__list",
1241
- children: loading ? /* @__PURE__ */ jsx15("p", { className: "cfi-ui-select-empty", children: loadingLabel }) : filtered.length === 0 ? /* @__PURE__ */ jsx15("p", { className: "cfi-ui-select-empty", children: emptyLabel }) : filtered.map((option) => {
1242
- const index = selectable.indexOf(option);
1243
- const isHighlighted = index !== -1 && index === highlighted;
1244
- return /* @__PURE__ */ jsxs15(
1245
- "div",
1246
- {
1247
- id: `${listId}-${option.value}`,
1248
- role: "option",
1249
- "aria-selected": option.value === value,
1250
- "aria-disabled": option.disabled || void 0,
1251
- "data-highlighted": isHighlighted || void 0,
1252
- "data-disabled": option.disabled || void 0,
1253
- className: "cfi-ui-select-item cfi-ui-combobox__option",
1254
- onClick: () => !option.disabled && commit(option),
1255
- onPointerMove: () => index !== -1 && setHighlighted(index),
1256
- children: [
1257
- /* @__PURE__ */ jsx15("span", { className: "cfi-ui-select-item__indicator", children: option.value === value && /* @__PURE__ */ jsx15(Check4, { "aria-hidden": "true" }) }),
1258
- /* @__PURE__ */ jsxs15("span", { className: "cfi-ui-combobox__option-text", children: [
1259
- /* @__PURE__ */ jsx15("span", { className: "cfi-ui-combobox__option-label", children: option.label }),
1260
- option.hint && /* @__PURE__ */ jsx15("span", { className: "cfi-ui-combobox__option-hint", children: option.hint })
1261
- ] })
1262
- ]
1263
- },
1264
- option.value
1265
- );
1267
+ children: loading ? /* @__PURE__ */ jsx15("p", { className: "cfi-ui-select-empty", children: loadingLabel }) : visible.length === 0 ? /* @__PURE__ */ jsx15("p", { className: "cfi-ui-select-empty", children: emptyLabel }) : groups.map((run, runIndex) => {
1268
+ const rows = run.options.map((option) => {
1269
+ const index = selectable.indexOf(option);
1270
+ const isHighlighted = index !== -1 && index === highlighted;
1271
+ return /* @__PURE__ */ jsxs15(
1272
+ "div",
1273
+ {
1274
+ id: `${listId}-${option.value}`,
1275
+ role: "option",
1276
+ "aria-selected": option.value === value,
1277
+ "aria-disabled": option.disabled || void 0,
1278
+ "data-highlighted": isHighlighted || void 0,
1279
+ "data-disabled": option.disabled || void 0,
1280
+ className: "cfi-ui-select-item cfi-ui-combobox__option",
1281
+ onClick: () => !option.disabled && commit(option),
1282
+ onPointerMove: () => index !== -1 && setHighlighted(index),
1283
+ children: [
1284
+ /* @__PURE__ */ jsx15("span", { className: "cfi-ui-select-item__indicator", children: option.value === value && /* @__PURE__ */ jsx15(Check4, { "aria-hidden": "true" }) }),
1285
+ /* @__PURE__ */ jsxs15("span", { className: "cfi-ui-combobox__option-text", children: [
1286
+ /* @__PURE__ */ jsx15("span", { className: "cfi-ui-combobox__option-label", children: option.label }),
1287
+ option.hint && /* @__PURE__ */ jsx15("span", { className: "cfi-ui-combobox__option-hint", children: option.hint })
1288
+ ] })
1289
+ ]
1290
+ },
1291
+ option.value
1292
+ );
1293
+ });
1294
+ if (!run.group) return /* @__PURE__ */ jsx15(Fragment3, { children: rows }, `ungrouped-${runIndex}`);
1295
+ return /* @__PURE__ */ jsxs15("div", { role: "group", "aria-label": run.group, children: [
1296
+ /* @__PURE__ */ jsx15("p", { className: "cfi-ui-combobox__group-label", "aria-hidden": "true", children: run.group }),
1297
+ rows
1298
+ ] }, `${run.group}-${runIndex}`);
1266
1299
  })
1267
1300
  }
1268
- )
1301
+ ),
1302
+ !loading && truncated && /* @__PURE__ */ jsx15("p", { className: "cfi-ui-combobox__truncated", children: truncatedLabel(visible.length) })
1269
1303
  ]
1270
1304
  }
1271
1305
  )
@@ -1275,7 +1309,7 @@ function Combobox({
1275
1309
  // src/Tooltip.tsx
1276
1310
  import { forwardRef as forwardRef12 } from "react";
1277
1311
  import * as RadixTooltip from "@radix-ui/react-tooltip";
1278
- import { Fragment as Fragment3, jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
1312
+ import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
1279
1313
  var TooltipProvider = RadixTooltip.Provider;
1280
1314
  var TooltipRoot = RadixTooltip.Root;
1281
1315
  var TooltipTrigger = RadixTooltip.Trigger;
@@ -1297,7 +1331,7 @@ var TooltipContent = forwardRef12(function TooltipContent2({ className, children
1297
1331
  ) });
1298
1332
  });
1299
1333
  function Tooltip({ content, side = "top", align = "center", delay = 300, disabled, children }) {
1300
- if (disabled || content == null || content === "") return /* @__PURE__ */ jsx16(Fragment3, { children });
1334
+ if (disabled || content == null || content === "") return /* @__PURE__ */ jsx16(Fragment4, { children });
1301
1335
  return /* @__PURE__ */ jsx16(RadixTooltip.Provider, { delayDuration: delay, children: /* @__PURE__ */ jsxs16(TooltipRoot, { children: [
1302
1336
  /* @__PURE__ */ jsx16(TooltipTrigger, { asChild: true, children }),
1303
1337
  /* @__PURE__ */ jsx16(TooltipContent, { side, align, children: content })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@campfire-interactive/ui",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Shared UI primitives for the Campfire Suite — Radix behavior, plain CSS styled entirely from the design tokens, no Tailwind required",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",