@sustaina/shared-ui 1.24.0 → 1.26.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.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +136 -80
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +136 -80
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -104,6 +104,7 @@ interface AdvanceSearchProps {
|
|
|
104
104
|
onSearch?: (param: Params) => void;
|
|
105
105
|
onClear?: () => void;
|
|
106
106
|
shortDateFormat?: string;
|
|
107
|
+
filterFieldMap?: Record<string, string>;
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
declare const AdvanceSearch: React__default.FC<AdvanceSearchProps>;
|
|
@@ -567,6 +568,7 @@ type FormulaEditorProps = {
|
|
|
567
568
|
onSelectSuggestion?: (token: FormulaTokenAttributes, config: FormulaTokenConfig) => void;
|
|
568
569
|
field?: ControllerRenderProps<any, any>;
|
|
569
570
|
fieldState?: ControllerFieldState;
|
|
571
|
+
mode?: "edit" | "display";
|
|
570
572
|
};
|
|
571
573
|
|
|
572
574
|
declare const FormulaEditor: React__default.FC<FormulaEditorProps>;
|
package/dist/index.d.ts
CHANGED
|
@@ -104,6 +104,7 @@ interface AdvanceSearchProps {
|
|
|
104
104
|
onSearch?: (param: Params) => void;
|
|
105
105
|
onClear?: () => void;
|
|
106
106
|
shortDateFormat?: string;
|
|
107
|
+
filterFieldMap?: Record<string, string>;
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
declare const AdvanceSearch: React__default.FC<AdvanceSearchProps>;
|
|
@@ -567,6 +568,7 @@ type FormulaEditorProps = {
|
|
|
567
568
|
onSelectSuggestion?: (token: FormulaTokenAttributes, config: FormulaTokenConfig) => void;
|
|
568
569
|
field?: ControllerRenderProps<any, any>;
|
|
569
570
|
fieldState?: ControllerFieldState;
|
|
571
|
+
mode?: "edit" | "display";
|
|
570
572
|
};
|
|
571
573
|
|
|
572
574
|
declare const FormulaEditor: React__default.FC<FormulaEditorProps>;
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,6 @@ var clsx2 = require('clsx');
|
|
|
5
5
|
var tailwindMerge = require('tailwind-merge');
|
|
6
6
|
var jsxRuntime = require('react/jsx-runtime');
|
|
7
7
|
var React4 = require('react');
|
|
8
|
-
var dateFns = require('date-fns');
|
|
9
8
|
var lucideReact = require('lucide-react');
|
|
10
9
|
var reactDom = require('react-dom');
|
|
11
10
|
var SelectPrimitive = require('@radix-ui/react-select');
|
|
@@ -13,6 +12,7 @@ var reactHookForm = require('react-hook-form');
|
|
|
13
12
|
var reactSlot = require('@radix-ui/react-slot');
|
|
14
13
|
var LabelPrimitive = require('@radix-ui/react-label');
|
|
15
14
|
var classVarianceAuthority = require('class-variance-authority');
|
|
15
|
+
var dateFns = require('date-fns');
|
|
16
16
|
var PopoverPrimitive = require('@radix-ui/react-popover');
|
|
17
17
|
var CheckboxPrimitive = require('@radix-ui/react-checkbox');
|
|
18
18
|
var CollapsiblePrimitive = require('@radix-ui/react-collapsible');
|
|
@@ -3461,6 +3461,66 @@ function getBuilder(fieldType) {
|
|
|
3461
3461
|
return new JSONBuilder();
|
|
3462
3462
|
}
|
|
3463
3463
|
}
|
|
3464
|
+
var FILTER_FIELD_MAP = {
|
|
3465
|
+
timezone: "timezoneId",
|
|
3466
|
+
decimalSeparator: "decimalSeparatorId",
|
|
3467
|
+
country: "countryId",
|
|
3468
|
+
currency: "currencyId"
|
|
3469
|
+
};
|
|
3470
|
+
function transformFilterKeys(obj, fieldMap = FILTER_FIELD_MAP) {
|
|
3471
|
+
if (Array.isArray(obj)) {
|
|
3472
|
+
return obj.map((item) => transformFilterKeys(item, fieldMap));
|
|
3473
|
+
}
|
|
3474
|
+
if (obj && typeof obj === "object") {
|
|
3475
|
+
const newObj = {};
|
|
3476
|
+
for (const key in obj) {
|
|
3477
|
+
const mappedKey = fieldMap[key] ?? key;
|
|
3478
|
+
newObj[mappedKey] = transformFilterKeys(obj[key], fieldMap);
|
|
3479
|
+
}
|
|
3480
|
+
return newObj;
|
|
3481
|
+
}
|
|
3482
|
+
return obj;
|
|
3483
|
+
}
|
|
3484
|
+
var sanitizeInput = (val) => {
|
|
3485
|
+
if (!val) return val;
|
|
3486
|
+
if (typeof val !== "string") return "__INVALID_TYPE__";
|
|
3487
|
+
if (val.includes("\n") || val.includes("\r") || /[\u2028\u2029]/u.test(val))
|
|
3488
|
+
return "__INVALID_NEWLINE__";
|
|
3489
|
+
if (/\\\\/.test(val)) return "__INVALID_ESCAPE__";
|
|
3490
|
+
if (/\\(n|t|r|b|f|u[0-9a-fA-F]{4})/.test(val)) return "__INVALID_ESCAPE__";
|
|
3491
|
+
if (/\p{Cf}/u.test(val)) return "__INVALID_UNICODE_WHITESPACE__";
|
|
3492
|
+
if (/[\u00A0\u1680\u180E\u202F\u205F\u3000]/u.test(val)) return "__INVALID_UNICODE_WHITESPACE__";
|
|
3493
|
+
const trimmed = val.trim();
|
|
3494
|
+
if (/^\{.*\}$/s.test(trimmed) || /^\[.*\]$/s.test(trimmed)) return "__INVALID_JSON_LITERAL__";
|
|
3495
|
+
if (/\\\{/.test(val) || /\\\}/.test(val)) return "__INVALID_JSON_ESCAPE__";
|
|
3496
|
+
if (/[%_*~^]/.test(trimmed)) return "__INVALID_WILDCARD__";
|
|
3497
|
+
if (/[%><={}\\[\]"']/u.test(trimmed)) return "__INVALID_CHAR__";
|
|
3498
|
+
if (/\p{Cc}/u.test(val)) return "__INVALID_CONTROL_CHAR__";
|
|
3499
|
+
return trimmed.replace(/\s+/g, " ");
|
|
3500
|
+
};
|
|
3501
|
+
var numericTypes = ["number", "integer", "decimal"];
|
|
3502
|
+
var dateTypes = ["date", "datemonth"];
|
|
3503
|
+
var validateByFieldType = (value, fieldType) => {
|
|
3504
|
+
if (!value) return { valid: true };
|
|
3505
|
+
if (numericTypes.includes(fieldType)) {
|
|
3506
|
+
if (!/^\d+(\.\d+)?$/.test(value)) {
|
|
3507
|
+
return { valid: false, message: "Please enter a valid number." };
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
if (fieldType === "boolean") {
|
|
3511
|
+
if (!["true", "false"].includes(value.toLowerCase())) {
|
|
3512
|
+
return { valid: false, message: "Please enter a boolean value (true/false)." };
|
|
3513
|
+
}
|
|
3514
|
+
}
|
|
3515
|
+
if (dateTypes.includes(fieldType)) {
|
|
3516
|
+
const normalized = fieldType === "datemonth" ? `${value}-01` : value;
|
|
3517
|
+
const parsed = dateFns.parseISO(normalized);
|
|
3518
|
+
if (!dateFns.isValid(parsed)) {
|
|
3519
|
+
return { valid: false, message: "Invalid date format." };
|
|
3520
|
+
}
|
|
3521
|
+
}
|
|
3522
|
+
return { valid: true };
|
|
3523
|
+
};
|
|
3464
3524
|
var AdvanceSearch = ({
|
|
3465
3525
|
fields,
|
|
3466
3526
|
portalId,
|
|
@@ -3468,7 +3528,8 @@ var AdvanceSearch = ({
|
|
|
3468
3528
|
limitRows = 4,
|
|
3469
3529
|
onSearch,
|
|
3470
3530
|
onClear,
|
|
3471
|
-
shortDateFormat
|
|
3531
|
+
shortDateFormat,
|
|
3532
|
+
filterFieldMap = FILTER_FIELD_MAP
|
|
3472
3533
|
}) => {
|
|
3473
3534
|
const fieldsData = React4.useMemo(() => {
|
|
3474
3535
|
if (fields.length === 0) throw new Error("fields cannot be an empty array");
|
|
@@ -3520,72 +3581,75 @@ var AdvanceSearch = ({
|
|
|
3520
3581
|
const onSubmit = React4.useCallback(() => {
|
|
3521
3582
|
const operatorValidation = {};
|
|
3522
3583
|
rows.forEach((r) => {
|
|
3523
|
-
const
|
|
3524
|
-
if (!
|
|
3584
|
+
const ops = operatorsForField(r.fieldName);
|
|
3585
|
+
if (!ops.length || !ops.includes(r.operator))
|
|
3525
3586
|
operatorValidation[r.id] = "Please select an operator.";
|
|
3526
|
-
}
|
|
3527
3587
|
});
|
|
3528
3588
|
setOperatorErrors(operatorValidation);
|
|
3529
|
-
if (Object.keys(operatorValidation).length > 0)
|
|
3530
|
-
return;
|
|
3531
|
-
}
|
|
3589
|
+
if (Object.keys(operatorValidation).length > 0) return;
|
|
3532
3590
|
const currentValues = getValues();
|
|
3533
|
-
let
|
|
3534
|
-
const
|
|
3535
|
-
const
|
|
3536
|
-
const
|
|
3591
|
+
let hasError = false;
|
|
3592
|
+
const processedRows = rows.map((r) => {
|
|
3593
|
+
const startField = `value_${r.id}`;
|
|
3594
|
+
const endField = `value2_${r.id}`;
|
|
3595
|
+
let v1 = currentValues[startField] ?? "";
|
|
3596
|
+
let v2 = currentValues[endField] ?? "";
|
|
3597
|
+
const s1 = sanitizeInput(v1);
|
|
3598
|
+
if (s1?.startsWith("__INVALID")) {
|
|
3599
|
+
hasError = true;
|
|
3600
|
+
setError(startField, { type: "validate", message: "Invalid input." });
|
|
3601
|
+
return null;
|
|
3602
|
+
}
|
|
3603
|
+
v1 = s1 || "";
|
|
3604
|
+
const valid1 = validateByFieldType(v1, r.fieldType);
|
|
3605
|
+
if (!valid1.valid) {
|
|
3606
|
+
hasError = true;
|
|
3607
|
+
setError(startField, { type: "validate", message: valid1.message });
|
|
3608
|
+
return null;
|
|
3609
|
+
}
|
|
3537
3610
|
if (r.operator === "between") {
|
|
3538
|
-
const
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3611
|
+
const s2 = sanitizeInput(v2);
|
|
3612
|
+
if (s2?.startsWith("__INVALID")) {
|
|
3613
|
+
hasError = true;
|
|
3614
|
+
setError(endField, { type: "validate", message: "Invalid input." });
|
|
3615
|
+
return null;
|
|
3616
|
+
}
|
|
3617
|
+
v2 = s2 || "";
|
|
3618
|
+
const valid2 = validateByFieldType(v2, r.fieldType);
|
|
3619
|
+
if (!valid2.valid) {
|
|
3620
|
+
hasError = true;
|
|
3621
|
+
setError(endField, { type: "validate", message: valid2.message });
|
|
3622
|
+
return null;
|
|
3623
|
+
}
|
|
3624
|
+
if (v1 && v2 && ["date", "datemonth"].includes(r.fieldType)) {
|
|
3625
|
+
const d1 = parseRangeValue(v1, r.fieldType);
|
|
3626
|
+
const d2 = parseRangeValue(v2, r.fieldType);
|
|
3627
|
+
if (d1 && d2 && dateFns.isAfter(d1, d2)) {
|
|
3628
|
+
hasError = true;
|
|
3629
|
+
setError(startField, { type: "validate", message: "Start value must be before end value." });
|
|
3630
|
+
setError(endField, { type: "validate", message: "End value must be after start value." });
|
|
3631
|
+
return null;
|
|
3555
3632
|
}
|
|
3556
3633
|
}
|
|
3557
|
-
return {
|
|
3558
|
-
...r,
|
|
3559
|
-
value: startValue ?? "",
|
|
3560
|
-
value2: endValue ?? ""
|
|
3561
|
-
};
|
|
3634
|
+
return { ...r, value: v1, value2: v2 };
|
|
3562
3635
|
}
|
|
3563
|
-
return {
|
|
3564
|
-
...r,
|
|
3565
|
-
value: startValue ?? ""
|
|
3566
|
-
};
|
|
3636
|
+
return { ...r, value: v1 };
|
|
3567
3637
|
});
|
|
3568
|
-
if (
|
|
3569
|
-
|
|
3570
|
-
}
|
|
3638
|
+
if (hasError) return;
|
|
3639
|
+
const cleanedRows = processedRows.filter(Boolean);
|
|
3571
3640
|
const param = {
|
|
3572
|
-
AND:
|
|
3573
|
-
const builder = getBuilder(r.fieldType);
|
|
3574
|
-
return builder.build(r);
|
|
3575
|
-
}).filter(Boolean)
|
|
3641
|
+
AND: cleanedRows.map((r) => getBuilder(r.fieldType).build(r)).filter(Boolean)
|
|
3576
3642
|
};
|
|
3577
|
-
if (onSearch)
|
|
3578
|
-
onSearch(param);
|
|
3579
|
-
}
|
|
3643
|
+
if (onSearch) onSearch(transformFilterKeys(param, filterFieldMap));
|
|
3580
3644
|
}, [
|
|
3581
|
-
|
|
3582
|
-
getValues,
|
|
3583
|
-
onSearch,
|
|
3645
|
+
rows,
|
|
3584
3646
|
operatorsForField,
|
|
3647
|
+
getValues,
|
|
3585
3648
|
parseRangeValue,
|
|
3586
|
-
rows,
|
|
3587
3649
|
setError,
|
|
3588
|
-
setOperatorErrors
|
|
3650
|
+
setOperatorErrors,
|
|
3651
|
+
filterFieldMap,
|
|
3652
|
+
onSearch
|
|
3589
3653
|
]);
|
|
3590
3654
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3591
3655
|
ExpandCollapse_default,
|
|
@@ -3623,9 +3687,7 @@ var AdvanceSearch = ({
|
|
|
3623
3687
|
unregister(`value_${row.id}`);
|
|
3624
3688
|
unregister(`value2_${row.id}`);
|
|
3625
3689
|
},
|
|
3626
|
-
onClearValue: (which) =>
|
|
3627
|
-
clearValue(row.id, which);
|
|
3628
|
-
},
|
|
3690
|
+
onClearValue: (which) => clearValue(row.id, which),
|
|
3629
3691
|
disableAdd: limitRows !== void 0 && rows.length >= limitRows
|
|
3630
3692
|
},
|
|
3631
3693
|
row.id
|
|
@@ -3650,7 +3712,7 @@ var AdvanceSearch = ({
|
|
|
3650
3712
|
Button,
|
|
3651
3713
|
{
|
|
3652
3714
|
type: "submit",
|
|
3653
|
-
className: "w-full bg-
|
|
3715
|
+
className: "w-full bg-sus-green-2 text-white hover:bg-[#2f7c21] md:w-auto md:min-w-[120px]",
|
|
3654
3716
|
"data-testid": "advsearch-btn-search",
|
|
3655
3717
|
children: "Search"
|
|
3656
3718
|
}
|
|
@@ -6243,7 +6305,8 @@ var FormulaEditor = ({
|
|
|
6243
6305
|
onChange,
|
|
6244
6306
|
onSelectSuggestion,
|
|
6245
6307
|
field,
|
|
6246
|
-
fieldState
|
|
6308
|
+
fieldState,
|
|
6309
|
+
mode = "edit"
|
|
6247
6310
|
}) => {
|
|
6248
6311
|
const [isExpanded, setIsExpanded] = React4.useState(false);
|
|
6249
6312
|
const lastEmittedValueRef = React4.useRef(null);
|
|
@@ -6260,27 +6323,22 @@ var FormulaEditor = ({
|
|
|
6260
6323
|
const prefixMap = React4.useMemo(() => buildPrefixMap(normalizedConfigs), [normalizedConfigs]);
|
|
6261
6324
|
const configLookup = React4.useMemo(() => {
|
|
6262
6325
|
const lookup = /* @__PURE__ */ new Map();
|
|
6263
|
-
normalizedConfigs.forEach((config) =>
|
|
6264
|
-
lookup.set(config.prefix, config);
|
|
6265
|
-
});
|
|
6326
|
+
normalizedConfigs.forEach((config) => lookup.set(config.prefix, config));
|
|
6266
6327
|
return lookup;
|
|
6267
6328
|
}, [normalizedConfigs]);
|
|
6268
6329
|
const allowedOperators = React4.useMemo(() => operators.map((operator) => operator.value), [operators]);
|
|
6269
6330
|
const displayError = errorMessage ?? fieldState?.error?.message;
|
|
6270
6331
|
const hasError = Boolean(displayError);
|
|
6271
|
-
const
|
|
6332
|
+
const isEditorReadOnly = mode === "display";
|
|
6333
|
+
const isEditorDisabled = disabled || loading || isEditorReadOnly;
|
|
6272
6334
|
const convertValueToContent = React4.useCallback(
|
|
6273
6335
|
(input) => {
|
|
6274
6336
|
if (!input) return "";
|
|
6275
6337
|
const trimmed = input.trim();
|
|
6276
6338
|
if (!trimmed) return "";
|
|
6277
6339
|
const parsedJSON = tryParseJSON(trimmed);
|
|
6278
|
-
if (parsedJSON && parsedJSON.type === "doc")
|
|
6279
|
-
|
|
6280
|
-
}
|
|
6281
|
-
if (looksLikeHTML(trimmed)) {
|
|
6282
|
-
return input;
|
|
6283
|
-
}
|
|
6340
|
+
if (parsedJSON && parsedJSON.type === "doc") return parsedJSON;
|
|
6341
|
+
if (looksLikeHTML(trimmed)) return input;
|
|
6284
6342
|
return buildDocFromRaw(input, prefixMap, configLookup);
|
|
6285
6343
|
},
|
|
6286
6344
|
[configLookup, prefixMap]
|
|
@@ -6311,7 +6369,7 @@ var FormulaEditor = ({
|
|
|
6311
6369
|
hasError ? "border border-destructive" : "border focus-visible:border-ring",
|
|
6312
6370
|
"w-full rounded-lg bg-white px-4 py-3",
|
|
6313
6371
|
"overflow-y-auto whitespace-pre-wrap wrap-break-word focus:outline-none",
|
|
6314
|
-
|
|
6372
|
+
isEditorDisabled && "pointer-events-none",
|
|
6315
6373
|
editorClassName
|
|
6316
6374
|
),
|
|
6317
6375
|
...loading ? { "aria-busy": "true" } : {}
|
|
@@ -6320,8 +6378,8 @@ var FormulaEditor = ({
|
|
|
6320
6378
|
});
|
|
6321
6379
|
React4.useEffect(() => {
|
|
6322
6380
|
if (!editor) return;
|
|
6323
|
-
editor.setEditable(!
|
|
6324
|
-
}, [editor,
|
|
6381
|
+
editor.setEditable(!isEditorDisabled);
|
|
6382
|
+
}, [editor, isEditorDisabled]);
|
|
6325
6383
|
React4.useEffect(() => {
|
|
6326
6384
|
if (!editor || resolvedContent === void 0) return;
|
|
6327
6385
|
if (ignorePropValueRef.current && typeof value === "string" && value === lastEmittedValueRef.current) {
|
|
@@ -6355,9 +6413,7 @@ var FormulaEditor = ({
|
|
|
6355
6413
|
className: "relative",
|
|
6356
6414
|
"aria-busy": loading,
|
|
6357
6415
|
onFocus: () => {
|
|
6358
|
-
if (editor && !editor.isFocused)
|
|
6359
|
-
editor.chain().focus().run();
|
|
6360
|
-
}
|
|
6416
|
+
if (editor && !editor.isFocused) editor.chain().focus().run();
|
|
6361
6417
|
},
|
|
6362
6418
|
children: [
|
|
6363
6419
|
/* @__PURE__ */ jsxRuntime.jsx(react.EditorContent, { editor }),
|
|
@@ -6369,14 +6425,14 @@ var FormulaEditor = ({
|
|
|
6369
6425
|
spinnerClassName: "size-6 text-sus-blue-3"
|
|
6370
6426
|
}
|
|
6371
6427
|
),
|
|
6372
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6428
|
+
!isEditorReadOnly && /* @__PURE__ */ jsxRuntime.jsx(
|
|
6373
6429
|
Button,
|
|
6374
6430
|
{
|
|
6375
6431
|
type: "button",
|
|
6376
6432
|
variant: "ghost",
|
|
6377
6433
|
size: "icon",
|
|
6378
6434
|
className: "absolute bottom-2 right-4 h-6 w-6 rounded-full bg-white shadow",
|
|
6379
|
-
disabled:
|
|
6435
|
+
disabled: isEditorDisabled,
|
|
6380
6436
|
onClick: () => setIsExpanded((prev) => !prev),
|
|
6381
6437
|
children: isExpanded ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Minimize2, { className: "h-4 w-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Maximize2, { className: "h-4 w-4" })
|
|
6382
6438
|
}
|
|
@@ -6385,13 +6441,13 @@ var FormulaEditor = ({
|
|
|
6385
6441
|
}
|
|
6386
6442
|
),
|
|
6387
6443
|
hasError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-destructive", role: "alert", children: displayError }),
|
|
6388
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap justify-end gap-2 py-2", children: operators.map((operator) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
6444
|
+
mode === "edit" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap justify-end gap-2 py-2", children: operators.map((operator) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
6389
6445
|
Button,
|
|
6390
6446
|
{
|
|
6391
6447
|
type: "button",
|
|
6392
6448
|
onClick: () => insertOperator(operator.value),
|
|
6393
6449
|
className: "min-w-10 rounded-sm px-3 bg-sus-blue-3",
|
|
6394
|
-
disabled:
|
|
6450
|
+
disabled: isEditorDisabled,
|
|
6395
6451
|
children: operator.label
|
|
6396
6452
|
},
|
|
6397
6453
|
operator.value
|
|
@@ -6587,8 +6643,8 @@ var GridSettingsModal = ({
|
|
|
6587
6643
|
}
|
|
6588
6644
|
}, [isOpen, currentColumns, form]);
|
|
6589
6645
|
const addColumn = async () => {
|
|
6590
|
-
const
|
|
6591
|
-
if (
|
|
6646
|
+
const isValid6 = await trigger("columns");
|
|
6647
|
+
if (isValid6) {
|
|
6592
6648
|
append({ id: "" });
|
|
6593
6649
|
requestAnimationFrame(() => {
|
|
6594
6650
|
const container = scrollRef.current;
|