@exxatdesignux/ui 0.5.1 → 0.5.3
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/CHANGELOG.md +16 -0
- package/consumer-extras/cursor-rules/exxat-data-tables.mdc +8 -6
- package/consumer-extras/cursor-rules/exxat-ds-agents.mdc +2 -1
- package/consumer-extras/cursor-rules/exxat-hub-supported-views.mdc +54 -0
- package/consumer-extras/cursor-rules/exxat-nav-single-active.mdc +31 -0
- package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +8 -3
- package/consumer-extras/cursor-skills/exxat-ds-skill/references/data-table-pattern.md +15 -5
- package/consumer-extras/cursor-skills/exxat-token-economy/SKILL.md +11 -4
- package/consumer-extras/handbook/HANDBOOK.md +1 -1
- package/consumer-extras/handbook/reference-implementations.md +2 -2
- package/consumer-extras/patterns/data-views-pattern.md +6 -0
- package/consumer-extras/patterns/hub-supported-views-pattern.md +53 -0
- package/dist/components/data-table/filter-text-value-input.js +1 -1
- package/dist/components/data-table/filter-text-value-input.js.map +1 -1
- package/dist/components/data-table/index.js +16 -12
- package/dist/components/data-table/index.js.map +1 -1
- package/dist/components/data-table/pagination.js +16 -12
- package/dist/components/data-table/pagination.js.map +1 -1
- package/dist/components/data-views/data-row-list.js +1 -1
- package/dist/components/data-views/data-row-list.js.map +1 -1
- package/dist/components/data-views/hub-table.d.ts +8 -4
- package/dist/components/data-views/hub-table.js +31 -16
- package/dist/components/data-views/hub-table.js.map +1 -1
- package/dist/components/data-views/index.d.ts +1 -1
- package/dist/components/data-views/index.js +31 -16
- package/dist/components/data-views/index.js.map +1 -1
- package/dist/components/data-views/list-page-connected-view-body.d.ts +1 -1
- package/dist/components/data-views/list-page-connected-view-body.js +1 -0
- package/dist/components/data-views/list-page-connected-view-body.js.map +1 -1
- package/dist/components/table-properties/column-row.js +1 -1
- package/dist/components/table-properties/column-row.js.map +1 -1
- package/dist/components/table-properties/drawer-button.js +6 -5
- package/dist/components/table-properties/drawer-button.js.map +1 -1
- package/dist/components/table-properties/drawer.js +6 -5
- package/dist/components/table-properties/drawer.js.map +1 -1
- package/dist/components/table-properties/filter-card.js +2 -2
- package/dist/components/table-properties/filter-card.js.map +1 -1
- package/dist/components/table-properties/index.d.ts +1 -1
- package/dist/components/table-properties/index.js +6 -5
- package/dist/components/table-properties/index.js.map +1 -1
- package/dist/components/table-properties/sort-card.js +1 -1
- package/dist/components/table-properties/sort-card.js.map +1 -1
- package/dist/components/templates/index.d.ts +1 -1
- package/dist/components/templates/index.js +16 -6
- package/dist/components/templates/index.js.map +1 -1
- package/dist/components/templates/list-page.d.ts +4 -2
- package/dist/components/templates/list-page.js +16 -6
- package/dist/components/templates/list-page.js.map +1 -1
- package/dist/components/ui/banner.d.ts +2 -2
- package/dist/components/ui/banner.js +1 -1
- package/dist/components/ui/banner.js.map +1 -1
- package/dist/components/ui/coach-mark.js +1 -1
- package/dist/components/ui/coach-mark.js.map +1 -1
- package/dist/components/ui/context-menu.js +1 -1
- package/dist/components/ui/context-menu.js.map +1 -1
- package/dist/components/ui/date-picker-field.js +1 -1
- package/dist/components/ui/date-picker-field.js.map +1 -1
- package/dist/components/ui/dropdown-menu.js +2 -2
- package/dist/components/ui/dropdown-menu.js.map +1 -1
- package/dist/components/ui/export-drawer.js +3 -3
- package/dist/components/ui/export-drawer.js.map +1 -1
- package/dist/components/ui/hover-card.js +1 -1
- package/dist/components/ui/hover-card.js.map +1 -1
- package/dist/components/ui/key-metrics.js +6 -6
- package/dist/components/ui/key-metrics.js.map +1 -1
- package/dist/components/ui/page-header.js +1 -1
- package/dist/components/ui/page-header.js.map +1 -1
- package/dist/components/ui/popover.js +1 -1
- package/dist/components/ui/popover.js.map +1 -1
- package/dist/components/ui/select.js +1 -1
- package/dist/components/ui/select.js.map +1 -1
- package/dist/components/ui/sheet.js +1 -1
- package/dist/components/ui/sheet.js.map +1 -1
- package/dist/components/ui/sidebar.d.ts +1 -1
- package/dist/components/ui/sidebar.js +3 -3
- package/dist/components/ui/sidebar.js.map +1 -1
- package/dist/components/ui/tip.js +1 -1
- package/dist/components/ui/tip.js.map +1 -1
- package/dist/components/ui/tooltip.js +1 -1
- package/dist/components/ui/tooltip.js.map +1 -1
- package/dist/components/ui/view-segmented-control.js +1 -1
- package/dist/components/ui/view-segmented-control.js.map +1 -1
- package/dist/{data-list-view-registry-CyBoBML4.d.ts → data-list-view-registry-BstmlfQ3.d.ts} +16 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +151 -29
- package/dist/index.js.map +1 -1
- package/dist/lib/data-list-view-registry.d.ts +1 -1
- package/dist/lib/data-list-view-registry.js +17 -1
- package/dist/lib/data-list-view-registry.js.map +1 -1
- package/dist/lib/data-list-view-surface.d.ts +1 -1
- package/dist/lib/data-list-view-surface.js +1 -0
- package/dist/lib/data-list-view-surface.js.map +1 -1
- package/dist/lib/list-page-table-properties.d.ts +1 -1
- package/dist/lib/list-page-table-properties.js +1 -0
- package/dist/lib/list-page-table-properties.js.map +1 -1
- package/dist/lib/nav-active.d.ts +38 -0
- package/dist/lib/nav-active.js +104 -0
- package/dist/lib/nav-active.js.map +1 -0
- package/package.json +1 -1
- package/src/components/data-table/index.tsx +25 -17
- package/src/components/data-views/data-row-list.tsx +1 -1
- package/src/components/data-views/hub-table.tsx +9 -3
- package/src/components/templates/list-page.tsx +9 -3
- package/src/components/ui/banner.tsx +0 -2
- package/src/components/ui/coach-mark.tsx +1 -2
- package/src/components/ui/context-menu.tsx +1 -1
- package/src/components/ui/dropdown-menu.tsx +2 -2
- package/src/components/ui/hover-card.tsx +1 -1
- package/src/components/ui/key-metrics.tsx +4 -4
- package/src/components/ui/popover.tsx +1 -1
- package/src/components/ui/select.tsx +1 -1
- package/src/components/ui/sheet.tsx +1 -1
- package/src/components/ui/sidebar.tsx +3 -3
- package/src/components/ui/tooltip.tsx +1 -1
- package/src/index.ts +1 -0
- package/src/lib/data-list-view-registry.ts +31 -0
- package/src/lib/nav-active.ts +162 -0
- package/template/.claude/skills/exxat-ds-skill/SKILL.md +2 -1
- package/template/AGENTS.md +16 -1
- package/template/components/columns-client.tsx +3 -2
- package/template/components/columns-showcase.tsx +22 -18
- package/template/components/exxat-product-logo.tsx +1 -1
- package/template/components/library-table.tsx +62 -23
- package/template/components/new-library-item-form.tsx +0 -7
- package/template/components/product-wordmark.tsx +1 -1
- package/template/components/sidebar/app-sidebar.tsx +14 -106
- package/template/components/sidebar/secondary-nav.tsx +22 -4
- package/template/components/tokens-hub-auxiliary-views.tsx +301 -0
- package/template/components/tokens-themes-client.tsx +44 -16
- package/template/docs/HANDBOOK.md +1 -1
- package/template/docs/data-views-pattern.md +6 -0
- package/template/docs/glossary.md +2 -1
- package/template/docs/hub-supported-views-pattern.md +53 -0
- package/template/docs/reference-implementations.md +2 -2
- package/template/lib/full-hub-supported-views.ts +8 -0
- package/template/lib/library-supported-views.ts +5 -12
- package/template/package.json +11 -0
- package/tokens/hooks-index.json +2 -2
|
@@ -20,7 +20,7 @@ import '../../lib/table-properties-types.js';
|
|
|
20
20
|
import '../data-table/use-table-state.js';
|
|
21
21
|
import '../../lib/row-height.js';
|
|
22
22
|
import '../../lib/data-list-view.js';
|
|
23
|
-
import '../../data-list-view-registry-
|
|
23
|
+
import '../../data-list-view-registry-BstmlfQ3.js';
|
|
24
24
|
import '../../lib/data-list-display-options.js';
|
|
25
25
|
import '../../lib/list-page-table-properties.js';
|
|
26
26
|
import '../ui/collapsible.js';
|
|
@@ -178,7 +178,7 @@ function TooltipContent({
|
|
|
178
178
|
"data-slot": "tooltip-content",
|
|
179
179
|
sideOffset,
|
|
180
180
|
className: cn(
|
|
181
|
-
"z-50 inline-flex w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pe-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-
|
|
181
|
+
"z-50 inline-flex w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pe-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-end-2 data-[side=right]:slide-in-from-start-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
182
182
|
className
|
|
183
183
|
),
|
|
184
184
|
...props,
|
|
@@ -374,7 +374,7 @@ function DropdownMenuContent({
|
|
|
374
374
|
sideOffset,
|
|
375
375
|
align,
|
|
376
376
|
className: cn(
|
|
377
|
-
"z-50 max-h-(--radix-dropdown-menu-content-available-height) origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-
|
|
377
|
+
"z-50 max-h-(--radix-dropdown-menu-content-available-height) origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-end-2 data-[side=right]:slide-in-from-start-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:overflow-hidden data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
378
378
|
DROPDOWN_MENU_CONTENT_SURFACE_CLASS,
|
|
379
379
|
className
|
|
380
380
|
),
|
|
@@ -484,7 +484,7 @@ function PopoverContent({
|
|
|
484
484
|
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
485
485
|
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
486
486
|
"data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2",
|
|
487
|
-
"data-[side=left]:slide-in-from-
|
|
487
|
+
"data-[side=left]:slide-in-from-end-2 data-[side=right]:slide-in-from-start-2",
|
|
488
488
|
className
|
|
489
489
|
),
|
|
490
490
|
...props
|
|
@@ -2283,13 +2283,15 @@ function useBulkBarFixedToTableScrollEl(scrollRef, active, fullWidth) {
|
|
|
2283
2283
|
const scheduled = rafThrottle(apply);
|
|
2284
2284
|
const ro = new ResizeObserver(scheduled);
|
|
2285
2285
|
ro.observe(el);
|
|
2286
|
+
el.addEventListener("scroll", scheduled, { passive: true });
|
|
2286
2287
|
window.addEventListener("resize", scheduled, { passive: true });
|
|
2287
|
-
window.addEventListener("scroll", scheduled, { passive: true
|
|
2288
|
+
window.addEventListener("scroll", scheduled, { passive: true });
|
|
2288
2289
|
return () => {
|
|
2289
2290
|
scheduled.cancel();
|
|
2290
2291
|
ro.disconnect();
|
|
2292
|
+
el.removeEventListener("scroll", scheduled);
|
|
2291
2293
|
window.removeEventListener("resize", scheduled);
|
|
2292
|
-
window.removeEventListener("scroll", scheduled
|
|
2294
|
+
window.removeEventListener("scroll", scheduled);
|
|
2293
2295
|
};
|
|
2294
2296
|
}, [active, fullWidth, scrollRef]);
|
|
2295
2297
|
return style;
|
|
@@ -2456,11 +2458,13 @@ function DataTableInner({
|
|
|
2456
2458
|
};
|
|
2457
2459
|
update();
|
|
2458
2460
|
const scheduled = rafThrottle(update);
|
|
2459
|
-
|
|
2461
|
+
wrapEl.addEventListener("scroll", scheduled, { passive: true });
|
|
2462
|
+
window.addEventListener("scroll", scheduled, { passive: true });
|
|
2460
2463
|
window.addEventListener("resize", scheduled, { passive: true });
|
|
2461
2464
|
return () => {
|
|
2462
2465
|
scheduled.cancel();
|
|
2463
|
-
|
|
2466
|
+
wrapEl.removeEventListener("scroll", scheduled);
|
|
2467
|
+
window.removeEventListener("scroll", scheduled);
|
|
2464
2468
|
window.removeEventListener("resize", scheduled);
|
|
2465
2469
|
};
|
|
2466
2470
|
}, [showColumnHeaders, rows.length, displayCols.length]);
|
|
@@ -2897,11 +2901,11 @@ function DataTableInner({
|
|
|
2897
2901
|
const rowPy = rowHeight === "compact" ? "py-1" : rowHeight === "comfortable" ? "py-4" : "py-2.5";
|
|
2898
2902
|
const cs = cellStyle(col.key);
|
|
2899
2903
|
const tdBase = cn(
|
|
2900
|
-
`px-3 ${rowPy} align-middle`,
|
|
2904
|
+
`px-3 ${rowPy} align-middle max-w-0`,
|
|
2901
2905
|
showGridlines && !isEdgePin && "border-e border-border last:border-e-0",
|
|
2902
2906
|
"border-b border-border group-last/row:border-b-0",
|
|
2903
2907
|
isPinned && [
|
|
2904
|
-
"z-20 pinned-cell",
|
|
2908
|
+
"z-20 pinned-cell relative",
|
|
2905
2909
|
"bg-dt-row-bg",
|
|
2906
2910
|
"group-data-[state=selected]/row:bg-dt-row-selected",
|
|
2907
2911
|
"group-hover/row:bg-dt-row-hover",
|
|
@@ -2951,17 +2955,17 @@ function DataTableInner({
|
|
|
2951
2955
|
wrap && "[&_.truncate]:!whitespace-normal [&_.truncate]:!overflow-visible [&_.truncate]:!text-clip"
|
|
2952
2956
|
),
|
|
2953
2957
|
style: tdStyle,
|
|
2954
|
-
children: col.cell(row, {
|
|
2958
|
+
children: /* @__PURE__ */ jsx("div", { className: "min-w-0 overflow-hidden", children: col.cell(row, {
|
|
2955
2959
|
rowIndex,
|
|
2956
2960
|
selected: isSelected,
|
|
2957
2961
|
onSelect: (checked) => checked ? setSelected((prev) => /* @__PURE__ */ new Set([...prev, rowId])) : toggleRow(rowId)
|
|
2958
|
-
})
|
|
2962
|
+
}) })
|
|
2959
2963
|
},
|
|
2960
2964
|
col.key
|
|
2961
2965
|
);
|
|
2962
2966
|
}
|
|
2963
2967
|
const rawVal = String(row[col.key] ?? "");
|
|
2964
|
-
return /* @__PURE__ */ jsx("td", { className: cn(tdBase, "text-sm text-foreground/80"), style: tdStyle, children: /* @__PURE__ */ jsx("span", { className: wrap ? "whitespace-normal" : "block truncate", title: !wrap ? rawVal : void 0, children: rawVal }) }, col.key);
|
|
2968
|
+
return /* @__PURE__ */ jsx("td", { className: cn(tdBase, "text-sm text-foreground/80"), style: tdStyle, children: /* @__PURE__ */ jsx("div", { className: "min-w-0 overflow-hidden", children: /* @__PURE__ */ jsx("span", { className: wrap ? "whitespace-normal" : "block truncate", title: !wrap ? rawVal : void 0, children: rawVal }) }) }, col.key);
|
|
2965
2969
|
})
|
|
2966
2970
|
},
|
|
2967
2971
|
String(rowId)
|
|
@@ -3289,6 +3293,16 @@ var BY_VALUE = new Map(
|
|
|
3289
3293
|
DEFINITIONS.map((d) => [d.value, d])
|
|
3290
3294
|
);
|
|
3291
3295
|
var DATA_LIST_VIEW_REGISTRY = DEFINITIONS;
|
|
3296
|
+
var FULL_HUB_SUPPORTED_VIEWS = [
|
|
3297
|
+
"table",
|
|
3298
|
+
"list",
|
|
3299
|
+
"board",
|
|
3300
|
+
"dashboard",
|
|
3301
|
+
"folder",
|
|
3302
|
+
"panel",
|
|
3303
|
+
"tree-panel"
|
|
3304
|
+
];
|
|
3305
|
+
DATA_LIST_VIEW_REGISTRY.map((d) => d.value);
|
|
3292
3306
|
function dataListViewDefinition(view) {
|
|
3293
3307
|
const def = BY_VALUE.get(view);
|
|
3294
3308
|
if (!def) {
|
|
@@ -3683,7 +3697,7 @@ function SheetContent({
|
|
|
3683
3697
|
"data-slot": "sheet-content",
|
|
3684
3698
|
"data-side": side,
|
|
3685
3699
|
className: cn(
|
|
3686
|
-
"fixed z-50 flex flex-col gap-4 bg-background bg-clip-padding text-sm shadow-lg outline-none duration-300 ease-out data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=left]:inset-y-0 data-[side=left]:
|
|
3700
|
+
"fixed z-50 flex flex-col gap-4 bg-background bg-clip-padding text-sm shadow-lg outline-none duration-300 ease-out data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=left]:inset-y-0 data-[side=left]:start-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-e data-[side=right]:inset-y-0 data-[side=right]:end-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-s data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-[side=bottom]:data-open:slide-in-from-bottom-6 data-[side=left]:data-open:slide-in-from-start-6 data-[side=right]:data-open:slide-in-from-end-6 data-[side=top]:data-open:slide-in-from-top-6 data-closed:animate-out data-closed:fade-out-0 data-[side=bottom]:data-closed:slide-out-to-bottom-6 data-[side=left]:data-closed:slide-out-to-start-6 data-[side=right]:data-closed:slide-out-to-end-6 data-[side=top]:data-closed:slide-out-to-top-6",
|
|
3687
3701
|
className
|
|
3688
3702
|
),
|
|
3689
3703
|
...props,
|
|
@@ -3767,7 +3781,7 @@ function SelectContent({
|
|
|
3767
3781
|
{
|
|
3768
3782
|
"data-slot": "select-content",
|
|
3769
3783
|
"data-align-trigger": position === "item-aligned",
|
|
3770
|
-
className: cn("relative z-50 max-h-(--radix-select-content-available-height) min-w-36 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-
|
|
3784
|
+
className: cn("relative z-50 max-h-(--radix-select-content-available-height) min-w-36 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-end-2 data-[side=right]:slide-in-from-start-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 rtl:data-[side=left]:translate-x-1 data-[side=right]:translate-x-1 rtl:data-[side=right]:-translate-x-1 data-[side=top]:-translate-y-1", className),
|
|
3771
3785
|
position,
|
|
3772
3786
|
align,
|
|
3773
3787
|
...props,
|
|
@@ -5309,7 +5323,7 @@ function DataRowListVirtualized({
|
|
|
5309
5323
|
{
|
|
5310
5324
|
"data-index": vr.index,
|
|
5311
5325
|
ref: virtualizer.measureElement,
|
|
5312
|
-
className: cn("absolute
|
|
5326
|
+
className: cn("absolute start-0 top-0 w-full pb-2", rowClassName),
|
|
5313
5327
|
style: { transform: `translateY(${vr.start}px)` },
|
|
5314
5328
|
children: renderRow(row, vr.index)
|
|
5315
5329
|
},
|
|
@@ -5525,7 +5539,7 @@ function HubTable({
|
|
|
5525
5539
|
columns,
|
|
5526
5540
|
view,
|
|
5527
5541
|
onViewChange,
|
|
5528
|
-
supportedViewTypes,
|
|
5542
|
+
supportedViewTypes: supportedViewTypesProp,
|
|
5529
5543
|
hubLabel,
|
|
5530
5544
|
lifecycleTabLabel,
|
|
5531
5545
|
searchAriaLabel,
|
|
@@ -5561,6 +5575,7 @@ function HubTable({
|
|
|
5561
5575
|
boardColumnCountBadgeClassName,
|
|
5562
5576
|
boardEmptyColumnLabel
|
|
5563
5577
|
}) {
|
|
5578
|
+
const supportedViewTypes = supportedViewTypesProp ?? FULL_HUB_SUPPORTED_VIEWS;
|
|
5564
5579
|
const filterFields = React10.useMemo(() => columnsToFilterFields(columns), [columns]);
|
|
5565
5580
|
const fieldDefinitions = React10.useMemo(() => columnsToFieldDefinitions(columns), [columns]);
|
|
5566
5581
|
const resolveColumnLabel = React10.useCallback(
|