@crisp-ui-kit/crisp 0.7.5 → 0.7.6

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.
@@ -394,6 +394,15 @@ function CreateRecordDialog({ open, onOpenChange, columns, entityLabel, onCreate
394
394
  function RecordsTable({ labels, columns: columnsProp = exports.defaultColumns, rows: rowsProp = exports.defaultRows, selectedIds, onSelectionChange, showNew = true, allowAddColumn = true, onOpenRecord, onOpenRecordPage, avatarTone = "brand", className, }) {
395
395
  const [cols, setCols] = React.useState(columnsProp);
396
396
  const [data, setData] = React.useState(rowsProp);
397
+ // The table owns `cols`/`data` so it can rename a column, edit a cell, or
398
+ // append a record without a round trip. That state is seeded from the props,
399
+ // so without these it would be a mount-time SNAPSHOT: an app that renders
400
+ // `rows={[]}` and re-renders with fetched rows would show "No records yet"
401
+ // for ever. Re-seed whenever the caller hands us a new array -- caller wins,
402
+ // exactly as DataTable does for its columns. Local edits survive until then,
403
+ // because a caller passing a stable array never triggers this.
404
+ React.useEffect(() => setCols(columnsProp), [columnsProp]);
405
+ React.useEffect(() => setData(rowsProp), [rowsProp]);
397
406
  // Attio's multi-criteria sort: an ordered list of {field, direction} rows.
398
407
  const [sorts, setSorts] = React.useState([]);
399
408
  const [dragSort, setDragSort] = React.useState(null);
@@ -607,7 +616,20 @@ function RecordsTable({ labels, columns: columnsProp = exports.defaultColumns, r
607
616
  };
608
617
  const allSelected = visibleRows.length > 0 && visibleRows.every((r) => selected.has(r.id));
609
618
  const someSelected = selected.size > 0 && !allSelected;
610
- const toggleAll = () => setSelected(allSelected ? new Set() : new Set(visibleRows.map((r) => r.id)));
619
+ // The header checkbox speaks for the rows you can SEE. Rebuilding the whole
620
+ // set from `visibleRows` would silently drop anything an active filter is
621
+ // hiding -- on a surface with a bulk Delete, that loses a selection the user
622
+ // made. Add/remove the visible rows and leave the rest of the set alone.
623
+ const toggleAll = () => {
624
+ const next = new Set(selected);
625
+ for (const r of visibleRows) {
626
+ if (allSelected)
627
+ next.delete(r.id);
628
+ else
629
+ next.add(r.id);
630
+ }
631
+ setSelected(next);
632
+ };
611
633
  const toggleRow = (id) => {
612
634
  const next = new Set(selected);
613
635
  // shift-click → select the contiguous range from the anchor to this row
@@ -804,7 +826,7 @@ function RecordsTable({ labels, columns: columnsProp = exports.defaultColumns, r
804
826
  URL.revokeObjectURL(url);
805
827
  setOpenMenu(null);
806
828
  };
807
- const entityKey = cols.find((c) => c.type === "entity")?.key;
829
+ const entityCol = cols.find((c) => c.type === "entity");
808
830
  // "Add to list" → pin the selected rows (shows a ★ on the entity)
809
831
  const addToList = () => {
810
832
  setPinned((prev) => new Set([...prev, ...selected]));
@@ -814,7 +836,10 @@ function RecordsTable({ labels, columns: columnsProp = exports.defaultColumns, r
814
836
  const sendEmail = () => {
815
837
  const names = data
816
838
  .filter((r) => selected.has(r.id))
817
- .map((r) => entityKey ? cellText({ key: entityKey, label: "" }, r) : r.id);
839
+ // Pass the real column: `cellText` -> `rawText` reads `col.type` to pull
840
+ // an entity's `.name`, so a `{key, label}` stand-in with no `type` falls
841
+ // through to String(value) and yields "[object Object]".
842
+ .map((r) => (entityCol ? cellText(entityCol, r) : r.id));
818
843
  window.open(`mailto:?subject=${encodeURIComponent(names.join(", "))}`);
819
844
  };
820
845
  // "Run workflow" → a demo automation: duplicate the selected records
@@ -356,6 +356,15 @@ function CreateRecordDialog({ open, onOpenChange, columns, entityLabel, onCreate
356
356
  export function RecordsTable({ labels, columns: columnsProp = defaultColumns, rows: rowsProp = defaultRows, selectedIds, onSelectionChange, showNew = true, allowAddColumn = true, onOpenRecord, onOpenRecordPage, avatarTone = "brand", className, }) {
357
357
  const [cols, setCols] = React.useState(columnsProp);
358
358
  const [data, setData] = React.useState(rowsProp);
359
+ // The table owns `cols`/`data` so it can rename a column, edit a cell, or
360
+ // append a record without a round trip. That state is seeded from the props,
361
+ // so without these it would be a mount-time SNAPSHOT: an app that renders
362
+ // `rows={[]}` and re-renders with fetched rows would show "No records yet"
363
+ // for ever. Re-seed whenever the caller hands us a new array -- caller wins,
364
+ // exactly as DataTable does for its columns. Local edits survive until then,
365
+ // because a caller passing a stable array never triggers this.
366
+ React.useEffect(() => setCols(columnsProp), [columnsProp]);
367
+ React.useEffect(() => setData(rowsProp), [rowsProp]);
359
368
  // Attio's multi-criteria sort: an ordered list of {field, direction} rows.
360
369
  const [sorts, setSorts] = React.useState([]);
361
370
  const [dragSort, setDragSort] = React.useState(null);
@@ -569,7 +578,20 @@ export function RecordsTable({ labels, columns: columnsProp = defaultColumns, ro
569
578
  };
570
579
  const allSelected = visibleRows.length > 0 && visibleRows.every((r) => selected.has(r.id));
571
580
  const someSelected = selected.size > 0 && !allSelected;
572
- const toggleAll = () => setSelected(allSelected ? new Set() : new Set(visibleRows.map((r) => r.id)));
581
+ // The header checkbox speaks for the rows you can SEE. Rebuilding the whole
582
+ // set from `visibleRows` would silently drop anything an active filter is
583
+ // hiding -- on a surface with a bulk Delete, that loses a selection the user
584
+ // made. Add/remove the visible rows and leave the rest of the set alone.
585
+ const toggleAll = () => {
586
+ const next = new Set(selected);
587
+ for (const r of visibleRows) {
588
+ if (allSelected)
589
+ next.delete(r.id);
590
+ else
591
+ next.add(r.id);
592
+ }
593
+ setSelected(next);
594
+ };
573
595
  const toggleRow = (id) => {
574
596
  const next = new Set(selected);
575
597
  // shift-click → select the contiguous range from the anchor to this row
@@ -766,7 +788,7 @@ export function RecordsTable({ labels, columns: columnsProp = defaultColumns, ro
766
788
  URL.revokeObjectURL(url);
767
789
  setOpenMenu(null);
768
790
  };
769
- const entityKey = cols.find((c) => c.type === "entity")?.key;
791
+ const entityCol = cols.find((c) => c.type === "entity");
770
792
  // "Add to list" → pin the selected rows (shows a ★ on the entity)
771
793
  const addToList = () => {
772
794
  setPinned((prev) => new Set([...prev, ...selected]));
@@ -776,7 +798,10 @@ export function RecordsTable({ labels, columns: columnsProp = defaultColumns, ro
776
798
  const sendEmail = () => {
777
799
  const names = data
778
800
  .filter((r) => selected.has(r.id))
779
- .map((r) => entityKey ? cellText({ key: entityKey, label: "" }, r) : r.id);
801
+ // Pass the real column: `cellText` -> `rawText` reads `col.type` to pull
802
+ // an entity's `.name`, so a `{key, label}` stand-in with no `type` falls
803
+ // through to String(value) and yields "[object Object]".
804
+ .map((r) => (entityCol ? cellText(entityCol, r) : r.id));
780
805
  window.open(`mailto:?subject=${encodeURIComponent(names.join(", "))}`);
781
806
  };
782
807
  // "Run workflow" → a demo automation: duplicate the selected records
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crisp-ui-kit/crisp",
3
- "version": "0.7.5",
3
+ "version": "0.7.6",
4
4
  "description": "A lean React design system, measured pixel-for-pixel from Attio.",
5
5
  "license": "MIT",
6
6
  "type": "module",