avt-rct-lib 1.0.3 → 1.0.5
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.cjs +931 -126
- package/dist/index.d.cts +181 -24
- package/dist/index.d.ts +181 -24
- package/dist/index.js +921 -126
- package/package.json +1 -1
- package/src/styles/base.css +84 -23
package/dist/index.cjs
CHANGED
|
@@ -1212,130 +1212,6 @@ function SidebarItem({
|
|
|
1212
1212
|
}
|
|
1213
1213
|
);
|
|
1214
1214
|
}
|
|
1215
|
-
function SidebarSubMenu({
|
|
1216
|
-
icon,
|
|
1217
|
-
label,
|
|
1218
|
-
badge,
|
|
1219
|
-
defaultOpen = false,
|
|
1220
|
-
open: controlledOpen,
|
|
1221
|
-
onOpenChange,
|
|
1222
|
-
activeChild = false,
|
|
1223
|
-
children,
|
|
1224
|
-
className
|
|
1225
|
-
}) {
|
|
1226
|
-
const { collapsed } = useSidebar();
|
|
1227
|
-
const [internalOpen, setInternalOpen] = React3.useState(defaultOpen);
|
|
1228
|
-
const isOpen = controlledOpen ?? internalOpen;
|
|
1229
|
-
const toggle = () => {
|
|
1230
|
-
const next = !isOpen;
|
|
1231
|
-
setInternalOpen(next);
|
|
1232
|
-
onOpenChange?.(next);
|
|
1233
|
-
};
|
|
1234
|
-
if (collapsed) {
|
|
1235
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1236
|
-
"button",
|
|
1237
|
-
{
|
|
1238
|
-
type: "button",
|
|
1239
|
-
title: label,
|
|
1240
|
-
onClick: toggle,
|
|
1241
|
-
className: cn(
|
|
1242
|
-
"mx-auto flex h-10 w-10 min-h-[44px] min-w-[44px] items-center justify-center rounded-xl",
|
|
1243
|
-
"select-none outline-none transition-all duration-150",
|
|
1244
|
-
"focus-visible:ring-2 focus-visible:ring-[#007AFF]/30",
|
|
1245
|
-
activeChild ? "bg-[#007AFF] text-white shadow-[0_2px_8px_rgba(0,122,255,0.28)]" : "text-[#3C3C43] hover:bg-[#007AFF]/8 hover:text-[#007AFF]",
|
|
1246
|
-
className
|
|
1247
|
-
),
|
|
1248
|
-
children: icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("flex-shrink-0", !activeChild && "text-[#8E8E93]"), children: icon })
|
|
1249
|
-
}
|
|
1250
|
-
);
|
|
1251
|
-
}
|
|
1252
|
-
const triggerActive = activeChild && !isOpen;
|
|
1253
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("space-y-0.5", className), children: [
|
|
1254
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1255
|
-
"button",
|
|
1256
|
-
{
|
|
1257
|
-
type: "button",
|
|
1258
|
-
onClick: toggle,
|
|
1259
|
-
className: cn(
|
|
1260
|
-
"flex w-full items-center gap-2.5 rounded-xl px-3 py-2.5 min-h-[44px]",
|
|
1261
|
-
"text-[13px] font-medium select-none outline-none transition-all duration-150",
|
|
1262
|
-
"focus-visible:ring-2 focus-visible:ring-[#007AFF]/30",
|
|
1263
|
-
triggerActive ? "bg-[#007AFF] text-white shadow-[0_2px_8px_rgba(0,122,255,0.28)]" : "text-[#3C3C43] hover:bg-[#007AFF]/8 hover:text-[#007AFF]"
|
|
1264
|
-
),
|
|
1265
|
-
children: [
|
|
1266
|
-
icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("flex-shrink-0", !triggerActive && "text-[#8E8E93]"), children: icon }),
|
|
1267
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate text-left", children: label }),
|
|
1268
|
-
badge !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1269
|
-
"span",
|
|
1270
|
-
{
|
|
1271
|
-
className: cn(
|
|
1272
|
-
"flex h-5 min-w-5 items-center justify-center rounded-full px-1.5 text-[10px] font-bold",
|
|
1273
|
-
triggerActive ? "bg-white/20 text-white" : "bg-[#E5E5EA] text-[#8E8E93]"
|
|
1274
|
-
),
|
|
1275
|
-
children: badge
|
|
1276
|
-
}
|
|
1277
|
-
),
|
|
1278
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1279
|
-
"svg",
|
|
1280
|
-
{
|
|
1281
|
-
className: cn(
|
|
1282
|
-
"h-3.5 w-3.5 flex-shrink-0 transition-transform duration-200",
|
|
1283
|
-
triggerActive ? "text-white/60" : "text-[#C7C7CC]",
|
|
1284
|
-
isOpen && "rotate-90"
|
|
1285
|
-
),
|
|
1286
|
-
viewBox: "0 0 24 24",
|
|
1287
|
-
fill: "none",
|
|
1288
|
-
stroke: "currentColor",
|
|
1289
|
-
strokeWidth: "2.5",
|
|
1290
|
-
strokeLinecap: "round",
|
|
1291
|
-
strokeLinejoin: "round",
|
|
1292
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 18l6-6-6-6" })
|
|
1293
|
-
}
|
|
1294
|
-
)
|
|
1295
|
-
]
|
|
1296
|
-
}
|
|
1297
|
-
),
|
|
1298
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1299
|
-
"div",
|
|
1300
|
-
{
|
|
1301
|
-
className: cn(
|
|
1302
|
-
"overflow-hidden transition-all duration-200 ease-[cubic-bezier(0.16,1,0.3,1)]",
|
|
1303
|
-
isOpen ? "max-h-96 opacity-100" : "max-h-0 opacity-0 pointer-events-none"
|
|
1304
|
-
),
|
|
1305
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ml-3 space-y-0.5 border-l border-[#E5E5EA] py-0.5 pl-2", children })
|
|
1306
|
-
}
|
|
1307
|
-
)
|
|
1308
|
-
] });
|
|
1309
|
-
}
|
|
1310
|
-
function SidebarSubMenuItem({
|
|
1311
|
-
icon,
|
|
1312
|
-
label,
|
|
1313
|
-
active = false,
|
|
1314
|
-
href,
|
|
1315
|
-
as: Tag = "button",
|
|
1316
|
-
className,
|
|
1317
|
-
...props
|
|
1318
|
-
}) {
|
|
1319
|
-
const tagProps = href ? { href } : { type: "button" };
|
|
1320
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1321
|
-
Tag,
|
|
1322
|
-
{
|
|
1323
|
-
...tagProps,
|
|
1324
|
-
className: cn(
|
|
1325
|
-
"flex w-full items-center gap-2 rounded-lg px-2.5 py-2 min-h-[36px]",
|
|
1326
|
-
"text-[12px] font-medium select-none outline-none transition-all duration-150",
|
|
1327
|
-
"focus-visible:ring-2 focus-visible:ring-[#007AFF]/30",
|
|
1328
|
-
active ? "bg-[#007AFF] text-white shadow-[0_2px_8px_rgba(0,122,255,0.20)]" : "text-[#3C3C43] hover:bg-[#007AFF]/8 hover:text-[#007AFF]",
|
|
1329
|
-
className
|
|
1330
|
-
),
|
|
1331
|
-
...props,
|
|
1332
|
-
children: [
|
|
1333
|
-
icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("flex-shrink-0", !active && "text-[#8E8E93]"), children: icon }),
|
|
1334
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate text-left", children: label })
|
|
1335
|
-
]
|
|
1336
|
-
}
|
|
1337
|
-
);
|
|
1338
|
-
}
|
|
1339
1215
|
function SidebarFooter({ className, ...props }) {
|
|
1340
1216
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex-shrink-0 p-3", className), ...props });
|
|
1341
1217
|
}
|
|
@@ -1401,6 +1277,925 @@ function SidebarToggle({ collapsed, className, ...props }) {
|
|
|
1401
1277
|
}
|
|
1402
1278
|
);
|
|
1403
1279
|
}
|
|
1280
|
+
function useAnchoredPosition(anchorRef, open, {
|
|
1281
|
+
align = "start",
|
|
1282
|
+
sideOffset = 6,
|
|
1283
|
+
minWidth = 180,
|
|
1284
|
+
matchWidth = true,
|
|
1285
|
+
maxHeight = 280,
|
|
1286
|
+
viewportPadding = 8
|
|
1287
|
+
} = {}) {
|
|
1288
|
+
const [pos, setPos] = React3.useState(null);
|
|
1289
|
+
const update = React3.useCallback(() => {
|
|
1290
|
+
const el = anchorRef.current;
|
|
1291
|
+
if (!el) return;
|
|
1292
|
+
const rect = el.getBoundingClientRect();
|
|
1293
|
+
const vw = window.innerWidth;
|
|
1294
|
+
const vh = window.innerHeight;
|
|
1295
|
+
const width = matchWidth ? Math.max(minWidth, rect.width) : minWidth;
|
|
1296
|
+
let left;
|
|
1297
|
+
if (align === "end") left = rect.right - width;
|
|
1298
|
+
else if (align === "center") left = rect.left + rect.width / 2 - width / 2;
|
|
1299
|
+
else left = rect.left;
|
|
1300
|
+
left = Math.max(viewportPadding, Math.min(left, vw - width - viewportPadding));
|
|
1301
|
+
const spaceBelow = vh - rect.bottom - sideOffset - viewportPadding;
|
|
1302
|
+
const spaceAbove = rect.top - sideOffset - viewportPadding;
|
|
1303
|
+
const flipUp = spaceBelow < Math.min(maxHeight, 180) && spaceAbove > spaceBelow;
|
|
1304
|
+
const available = Math.max(120, flipUp ? spaceAbove : spaceBelow);
|
|
1305
|
+
const height = Math.min(maxHeight, available);
|
|
1306
|
+
const top = flipUp ? rect.top - sideOffset - height : rect.bottom + sideOffset;
|
|
1307
|
+
setPos({
|
|
1308
|
+
top,
|
|
1309
|
+
left,
|
|
1310
|
+
width,
|
|
1311
|
+
maxHeight: height,
|
|
1312
|
+
placement: flipUp ? "top" : "bottom"
|
|
1313
|
+
});
|
|
1314
|
+
}, [anchorRef, align, sideOffset, minWidth, matchWidth, maxHeight, viewportPadding]);
|
|
1315
|
+
React3.useEffect(() => {
|
|
1316
|
+
if (!open) {
|
|
1317
|
+
setPos(null);
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
update();
|
|
1321
|
+
window.addEventListener("scroll", update, true);
|
|
1322
|
+
window.addEventListener("resize", update);
|
|
1323
|
+
return () => {
|
|
1324
|
+
window.removeEventListener("scroll", update, true);
|
|
1325
|
+
window.removeEventListener("resize", update);
|
|
1326
|
+
};
|
|
1327
|
+
}, [open, update]);
|
|
1328
|
+
return pos;
|
|
1329
|
+
}
|
|
1330
|
+
var stack = [];
|
|
1331
|
+
var bound = false;
|
|
1332
|
+
function onGlobalKeydown(e) {
|
|
1333
|
+
if (e.key !== "Escape") return;
|
|
1334
|
+
const top = stack[stack.length - 1];
|
|
1335
|
+
if (!top) return;
|
|
1336
|
+
e.stopPropagation();
|
|
1337
|
+
top();
|
|
1338
|
+
}
|
|
1339
|
+
function bind() {
|
|
1340
|
+
if (bound || typeof document === "undefined") return;
|
|
1341
|
+
document.addEventListener("keydown", onGlobalKeydown, true);
|
|
1342
|
+
bound = true;
|
|
1343
|
+
}
|
|
1344
|
+
function unbind() {
|
|
1345
|
+
if (!bound || typeof document === "undefined") return;
|
|
1346
|
+
document.removeEventListener("keydown", onGlobalKeydown, true);
|
|
1347
|
+
bound = false;
|
|
1348
|
+
}
|
|
1349
|
+
function useEscapeDismiss(enabled, onDismiss) {
|
|
1350
|
+
const handlerRef = React3.useRef(onDismiss);
|
|
1351
|
+
handlerRef.current = onDismiss;
|
|
1352
|
+
React3.useEffect(() => {
|
|
1353
|
+
if (!enabled) return;
|
|
1354
|
+
const entry = () => handlerRef.current();
|
|
1355
|
+
stack.push(entry);
|
|
1356
|
+
bind();
|
|
1357
|
+
return () => {
|
|
1358
|
+
const i = stack.lastIndexOf(entry);
|
|
1359
|
+
if (i >= 0) stack.splice(i, 1);
|
|
1360
|
+
if (stack.length === 0) unbind();
|
|
1361
|
+
};
|
|
1362
|
+
}, [enabled]);
|
|
1363
|
+
}
|
|
1364
|
+
var triggerBase = [
|
|
1365
|
+
"flex w-full items-center gap-2 rounded-xl bg-[#F2F2F7] text-left",
|
|
1366
|
+
"border-0 outline-none select-none text-[#1C1C1E]",
|
|
1367
|
+
"transition-all duration-150",
|
|
1368
|
+
"focus-visible:bg-white focus-visible:shadow-[0_0_0_2px_rgba(0,122,255,0.22)]",
|
|
1369
|
+
"disabled:cursor-not-allowed disabled:opacity-50"
|
|
1370
|
+
].join(" ");
|
|
1371
|
+
var triggerSizes = {
|
|
1372
|
+
sm: "h-9 px-3 text-[13px]",
|
|
1373
|
+
md: "h-11 px-4 text-sm",
|
|
1374
|
+
lg: "h-12 px-5 text-[15px]"
|
|
1375
|
+
};
|
|
1376
|
+
var panelBase = [
|
|
1377
|
+
"fixed z-[9999] flex flex-col overflow-hidden avt-animate-slide-up",
|
|
1378
|
+
"rounded-xl border border-[#E5E5EA]/60 bg-white/95 backdrop-blur-2xl",
|
|
1379
|
+
"shadow-[0_8px_24px_rgba(0,0,0,0.10),_0_0_1px_rgba(0,0,0,0.06)]"
|
|
1380
|
+
].join(" ");
|
|
1381
|
+
function optionRowClass(state, className) {
|
|
1382
|
+
return cn(
|
|
1383
|
+
"flex w-full items-center gap-2.5 px-3.5 py-2.5 text-[13px] font-medium",
|
|
1384
|
+
"cursor-pointer select-none text-left outline-none",
|
|
1385
|
+
"transition-colors duration-100",
|
|
1386
|
+
// Mobile: jaga target sentuh minimal
|
|
1387
|
+
"min-h-[44px] sm:min-h-0",
|
|
1388
|
+
state.disabled ? "pointer-events-none opacity-40" : state.active ? "bg-[#F2F2F7]" : "hover:bg-[#F2F2F7]",
|
|
1389
|
+
state.selected ? "text-[#007AFF]" : "text-[#1C1C1E]",
|
|
1390
|
+
className
|
|
1391
|
+
);
|
|
1392
|
+
}
|
|
1393
|
+
var groupLabelClass = "px-3.5 pb-1 pt-2 text-[11px] font-semibold uppercase tracking-wider text-[#8E8E93]";
|
|
1394
|
+
var ChevronDownIcon = ({ className }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1395
|
+
"svg",
|
|
1396
|
+
{
|
|
1397
|
+
className,
|
|
1398
|
+
viewBox: "0 0 24 24",
|
|
1399
|
+
fill: "none",
|
|
1400
|
+
stroke: "currentColor",
|
|
1401
|
+
strokeWidth: "2.5",
|
|
1402
|
+
strokeLinecap: "round",
|
|
1403
|
+
strokeLinejoin: "round",
|
|
1404
|
+
"aria-hidden": "true",
|
|
1405
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 9l6 6 6-6" })
|
|
1406
|
+
}
|
|
1407
|
+
);
|
|
1408
|
+
var CheckIcon = ({ className }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1409
|
+
"svg",
|
|
1410
|
+
{
|
|
1411
|
+
className,
|
|
1412
|
+
viewBox: "0 0 24 24",
|
|
1413
|
+
fill: "none",
|
|
1414
|
+
stroke: "currentColor",
|
|
1415
|
+
strokeWidth: "3",
|
|
1416
|
+
strokeLinecap: "round",
|
|
1417
|
+
strokeLinejoin: "round",
|
|
1418
|
+
"aria-hidden": "true",
|
|
1419
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20 6L9 17l-5-5" })
|
|
1420
|
+
}
|
|
1421
|
+
);
|
|
1422
|
+
var SearchIcon2 = ({ className }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1423
|
+
"svg",
|
|
1424
|
+
{
|
|
1425
|
+
className,
|
|
1426
|
+
viewBox: "0 0 24 24",
|
|
1427
|
+
fill: "none",
|
|
1428
|
+
stroke: "currentColor",
|
|
1429
|
+
strokeWidth: "2.5",
|
|
1430
|
+
strokeLinecap: "round",
|
|
1431
|
+
"aria-hidden": "true",
|
|
1432
|
+
children: [
|
|
1433
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "11", cy: "11", r: "7" }),
|
|
1434
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20 20l-3.5-3.5" })
|
|
1435
|
+
]
|
|
1436
|
+
}
|
|
1437
|
+
);
|
|
1438
|
+
var ClearIcon = ({ className }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1439
|
+
"svg",
|
|
1440
|
+
{
|
|
1441
|
+
className,
|
|
1442
|
+
viewBox: "0 0 24 24",
|
|
1443
|
+
fill: "none",
|
|
1444
|
+
stroke: "currentColor",
|
|
1445
|
+
strokeWidth: "2.5",
|
|
1446
|
+
strokeLinecap: "round",
|
|
1447
|
+
"aria-hidden": "true",
|
|
1448
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6L6 18M6 6l12 12" })
|
|
1449
|
+
}
|
|
1450
|
+
);
|
|
1451
|
+
var SpinnerIcon = ({ className }) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { className: cn("animate-spin", className), viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
|
|
1452
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "3" }),
|
|
1453
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" })
|
|
1454
|
+
] });
|
|
1455
|
+
function groupOptions(options) {
|
|
1456
|
+
const groups = [];
|
|
1457
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1458
|
+
for (const option of options) {
|
|
1459
|
+
const key = option.group ?? "";
|
|
1460
|
+
let bucket = byName.get(key);
|
|
1461
|
+
if (!bucket) {
|
|
1462
|
+
bucket = { group: option.group, items: [], offset: 0 };
|
|
1463
|
+
byName.set(key, bucket);
|
|
1464
|
+
groups.push(bucket);
|
|
1465
|
+
}
|
|
1466
|
+
bucket.items.push(option);
|
|
1467
|
+
}
|
|
1468
|
+
let offset = 0;
|
|
1469
|
+
for (const bucket of groups) {
|
|
1470
|
+
bucket.offset = offset;
|
|
1471
|
+
offset += bucket.items.length;
|
|
1472
|
+
}
|
|
1473
|
+
return groups;
|
|
1474
|
+
}
|
|
1475
|
+
function nextEnabledIndex(options, from, dir) {
|
|
1476
|
+
const n = options.length;
|
|
1477
|
+
if (n === 0) return -1;
|
|
1478
|
+
let i = from < 0 ? dir === 1 ? -1 : 0 : from;
|
|
1479
|
+
for (let step = 0; step < n; step++) {
|
|
1480
|
+
i = (i + dir + n) % n;
|
|
1481
|
+
if (!options[i].disabled) return i;
|
|
1482
|
+
}
|
|
1483
|
+
return -1;
|
|
1484
|
+
}
|
|
1485
|
+
function defaultFilter(option, query) {
|
|
1486
|
+
const q = query.trim().toLowerCase();
|
|
1487
|
+
if (!q) return true;
|
|
1488
|
+
return option.label.toLowerCase().includes(q) || (option.description?.toLowerCase().includes(q) ?? false) || String(option.value).toLowerCase().includes(q);
|
|
1489
|
+
}
|
|
1490
|
+
var Select = React3__default.default.forwardRef(function Select2({
|
|
1491
|
+
options,
|
|
1492
|
+
value: controlledValue,
|
|
1493
|
+
defaultValue = null,
|
|
1494
|
+
onChange,
|
|
1495
|
+
placeholder = "Pilih\u2026",
|
|
1496
|
+
label,
|
|
1497
|
+
hint,
|
|
1498
|
+
error,
|
|
1499
|
+
size = "md",
|
|
1500
|
+
disabled = false,
|
|
1501
|
+
clearable = false,
|
|
1502
|
+
name,
|
|
1503
|
+
align = "start",
|
|
1504
|
+
maxHeight = 280,
|
|
1505
|
+
emptyText = "Tidak ada pilihan",
|
|
1506
|
+
containerClassName,
|
|
1507
|
+
panelClassName,
|
|
1508
|
+
onOpenChange,
|
|
1509
|
+
renderOption,
|
|
1510
|
+
searchable = false,
|
|
1511
|
+
searchPlaceholder = "Cari\u2026",
|
|
1512
|
+
filter = defaultFilter,
|
|
1513
|
+
onSearchChange,
|
|
1514
|
+
manualFilter = false,
|
|
1515
|
+
loading = false,
|
|
1516
|
+
loadingText = "Memuat\u2026",
|
|
1517
|
+
className,
|
|
1518
|
+
id,
|
|
1519
|
+
...props
|
|
1520
|
+
}, ref) {
|
|
1521
|
+
const reactId = React3__default.default.useId();
|
|
1522
|
+
const uid = id ?? `avt-select-${reactId}`;
|
|
1523
|
+
const listboxId = `${uid}-listbox`;
|
|
1524
|
+
const labelId = `${uid}-label`;
|
|
1525
|
+
const [open, setOpen] = React3.useState(false);
|
|
1526
|
+
const [query, setQuery] = React3.useState("");
|
|
1527
|
+
const [activeIndex, setActiveIndex] = React3.useState(-1);
|
|
1528
|
+
const [internalValue, setInternalValue] = React3.useState(defaultValue);
|
|
1529
|
+
const triggerRef = React3.useRef(null);
|
|
1530
|
+
const panelRef = React3.useRef(null);
|
|
1531
|
+
const listRef = React3.useRef(null);
|
|
1532
|
+
const typeahead = React3.useRef({ text: "", at: 0 });
|
|
1533
|
+
const focusOnMount = React3.useCallback((el) => {
|
|
1534
|
+
el?.focus();
|
|
1535
|
+
}, []);
|
|
1536
|
+
React3__default.default.useImperativeHandle(ref, () => triggerRef.current, []);
|
|
1537
|
+
const isControlled = controlledValue !== void 0;
|
|
1538
|
+
const value = isControlled ? controlledValue : internalValue;
|
|
1539
|
+
const selected = React3.useMemo(
|
|
1540
|
+
() => value == null ? null : options.find((o) => o.value === value) ?? null,
|
|
1541
|
+
[options, value]
|
|
1542
|
+
);
|
|
1543
|
+
const visible = React3.useMemo(() => {
|
|
1544
|
+
if (!searchable || manualFilter || !query.trim()) return options;
|
|
1545
|
+
return options.filter((o) => filter(o, query));
|
|
1546
|
+
}, [options, searchable, manualFilter, query, filter]);
|
|
1547
|
+
const groups = React3.useMemo(() => groupOptions(visible), [visible]);
|
|
1548
|
+
const flat = React3.useMemo(() => groups.flatMap((g) => g.items), [groups]);
|
|
1549
|
+
const changeOpen = React3.useCallback(
|
|
1550
|
+
(next) => {
|
|
1551
|
+
setOpen(next);
|
|
1552
|
+
onOpenChange?.(next);
|
|
1553
|
+
},
|
|
1554
|
+
[onOpenChange]
|
|
1555
|
+
);
|
|
1556
|
+
const dismiss = React3.useCallback(() => {
|
|
1557
|
+
changeOpen(false);
|
|
1558
|
+
triggerRef.current?.focus();
|
|
1559
|
+
}, [changeOpen]);
|
|
1560
|
+
useEscapeDismiss(open, dismiss);
|
|
1561
|
+
React3.useEffect(() => {
|
|
1562
|
+
if (!open) return;
|
|
1563
|
+
const handler = (e) => {
|
|
1564
|
+
const target = e.target;
|
|
1565
|
+
if (triggerRef.current?.contains(target)) return;
|
|
1566
|
+
if (panelRef.current?.contains(target)) return;
|
|
1567
|
+
changeOpen(false);
|
|
1568
|
+
};
|
|
1569
|
+
document.addEventListener("mousedown", handler, true);
|
|
1570
|
+
document.addEventListener("touchstart", handler, true);
|
|
1571
|
+
return () => {
|
|
1572
|
+
document.removeEventListener("mousedown", handler, true);
|
|
1573
|
+
document.removeEventListener("touchstart", handler, true);
|
|
1574
|
+
};
|
|
1575
|
+
}, [open, changeOpen]);
|
|
1576
|
+
React3.useEffect(() => {
|
|
1577
|
+
if (!open) setQuery("");
|
|
1578
|
+
}, [open]);
|
|
1579
|
+
React3.useEffect(() => {
|
|
1580
|
+
if (!open) {
|
|
1581
|
+
setActiveIndex(-1);
|
|
1582
|
+
return;
|
|
1583
|
+
}
|
|
1584
|
+
const pick = flat.findIndex((o) => value != null && o.value === value && !o.disabled);
|
|
1585
|
+
setActiveIndex(pick >= 0 ? pick : nextEnabledIndex(flat, -1, 1));
|
|
1586
|
+
}, [open, query]);
|
|
1587
|
+
React3.useEffect(() => {
|
|
1588
|
+
if (!open || activeIndex < 0) return;
|
|
1589
|
+
listRef.current?.querySelector(`[data-index="${activeIndex}"]`)?.scrollIntoView({ block: "nearest" });
|
|
1590
|
+
}, [open, activeIndex]);
|
|
1591
|
+
const commit = React3.useCallback(
|
|
1592
|
+
(option) => {
|
|
1593
|
+
if (option.disabled) return;
|
|
1594
|
+
if (!isControlled) setInternalValue(option.value);
|
|
1595
|
+
onChange?.(option.value, option);
|
|
1596
|
+
changeOpen(false);
|
|
1597
|
+
triggerRef.current?.focus();
|
|
1598
|
+
},
|
|
1599
|
+
[isControlled, onChange, changeOpen]
|
|
1600
|
+
);
|
|
1601
|
+
const clear = React3.useCallback(
|
|
1602
|
+
(e) => {
|
|
1603
|
+
e.stopPropagation();
|
|
1604
|
+
if (!isControlled) setInternalValue(null);
|
|
1605
|
+
onChange?.(null, null);
|
|
1606
|
+
},
|
|
1607
|
+
[isControlled, onChange]
|
|
1608
|
+
);
|
|
1609
|
+
const move = React3.useCallback(
|
|
1610
|
+
(dir) => setActiveIndex((i) => nextEnabledIndex(flat, i, dir)),
|
|
1611
|
+
[flat]
|
|
1612
|
+
);
|
|
1613
|
+
const runTypeahead = React3.useCallback(
|
|
1614
|
+
(char) => {
|
|
1615
|
+
const now = Date.now();
|
|
1616
|
+
const text = now - typeahead.current.at > 600 ? char : typeahead.current.text + char;
|
|
1617
|
+
typeahead.current = { text, at: now };
|
|
1618
|
+
const q = text.toLowerCase();
|
|
1619
|
+
const hit = flat.findIndex((o) => !o.disabled && o.label.toLowerCase().startsWith(q));
|
|
1620
|
+
if (hit >= 0) setActiveIndex(hit);
|
|
1621
|
+
},
|
|
1622
|
+
[flat]
|
|
1623
|
+
);
|
|
1624
|
+
const onNavKeyDown = React3.useCallback(
|
|
1625
|
+
(e) => {
|
|
1626
|
+
switch (e.key) {
|
|
1627
|
+
case "ArrowDown":
|
|
1628
|
+
e.preventDefault();
|
|
1629
|
+
move(1);
|
|
1630
|
+
return;
|
|
1631
|
+
case "ArrowUp":
|
|
1632
|
+
e.preventDefault();
|
|
1633
|
+
move(-1);
|
|
1634
|
+
return;
|
|
1635
|
+
case "Home":
|
|
1636
|
+
e.preventDefault();
|
|
1637
|
+
setActiveIndex(nextEnabledIndex(flat, -1, 1));
|
|
1638
|
+
return;
|
|
1639
|
+
case "End":
|
|
1640
|
+
e.preventDefault();
|
|
1641
|
+
setActiveIndex(nextEnabledIndex(flat, 0, -1));
|
|
1642
|
+
return;
|
|
1643
|
+
case "Enter":
|
|
1644
|
+
e.preventDefault();
|
|
1645
|
+
if (activeIndex >= 0 && flat[activeIndex]) commit(flat[activeIndex]);
|
|
1646
|
+
return;
|
|
1647
|
+
case "Tab":
|
|
1648
|
+
changeOpen(false);
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1651
|
+
},
|
|
1652
|
+
[move, flat, activeIndex, commit, changeOpen]
|
|
1653
|
+
);
|
|
1654
|
+
const onTriggerKeyDown = (e) => {
|
|
1655
|
+
if (disabled) return;
|
|
1656
|
+
if (!open) {
|
|
1657
|
+
if (e.key === "ArrowDown" || e.key === "ArrowUp" || e.key === "Enter" || e.key === " ") {
|
|
1658
|
+
e.preventDefault();
|
|
1659
|
+
changeOpen(true);
|
|
1660
|
+
}
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
if (e.key === " " && !searchable) {
|
|
1664
|
+
e.preventDefault();
|
|
1665
|
+
if (activeIndex >= 0 && flat[activeIndex]) commit(flat[activeIndex]);
|
|
1666
|
+
return;
|
|
1667
|
+
}
|
|
1668
|
+
onNavKeyDown(e);
|
|
1669
|
+
if (!searchable && e.key.length === 1 && !e.metaKey && !e.ctrlKey && !e.altKey) {
|
|
1670
|
+
runTypeahead(e.key);
|
|
1671
|
+
}
|
|
1672
|
+
};
|
|
1673
|
+
const showClear = clearable && selected != null && !disabled;
|
|
1674
|
+
const activeId = open && activeIndex >= 0 ? `${uid}-opt-${activeIndex}` : void 0;
|
|
1675
|
+
const comboProps = searchable ? { "aria-haspopup": "listbox" } : {
|
|
1676
|
+
role: "combobox",
|
|
1677
|
+
"aria-controls": open ? listboxId : void 0,
|
|
1678
|
+
"aria-activedescendant": activeId,
|
|
1679
|
+
"aria-haspopup": "listbox"
|
|
1680
|
+
};
|
|
1681
|
+
const pos = useAnchoredPosition(triggerRef, open, {
|
|
1682
|
+
align,
|
|
1683
|
+
matchWidth: true,
|
|
1684
|
+
maxHeight
|
|
1685
|
+
});
|
|
1686
|
+
const panel = open && pos && typeof document !== "undefined" ? reactDom.createPortal(
|
|
1687
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1688
|
+
"div",
|
|
1689
|
+
{
|
|
1690
|
+
ref: panelRef,
|
|
1691
|
+
className: cn(panelBase, panelClassName),
|
|
1692
|
+
style: {
|
|
1693
|
+
top: pos.top,
|
|
1694
|
+
left: pos.left,
|
|
1695
|
+
width: pos.width,
|
|
1696
|
+
maxHeight: pos.maxHeight
|
|
1697
|
+
},
|
|
1698
|
+
children: [
|
|
1699
|
+
searchable && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center border-b border-[#F2F2F7] px-3", children: [
|
|
1700
|
+
/* @__PURE__ */ jsxRuntime.jsx(SearchIcon2, { className: "pointer-events-none absolute left-3 h-3.5 w-3.5 text-[#8E8E93]" }),
|
|
1701
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1702
|
+
"input",
|
|
1703
|
+
{
|
|
1704
|
+
ref: focusOnMount,
|
|
1705
|
+
type: "text",
|
|
1706
|
+
role: "combobox",
|
|
1707
|
+
"aria-expanded": true,
|
|
1708
|
+
"aria-controls": listboxId,
|
|
1709
|
+
"aria-activedescendant": activeId,
|
|
1710
|
+
"aria-autocomplete": "list",
|
|
1711
|
+
"aria-label": searchPlaceholder,
|
|
1712
|
+
autoComplete: "off",
|
|
1713
|
+
spellCheck: false,
|
|
1714
|
+
value: query,
|
|
1715
|
+
placeholder: searchPlaceholder,
|
|
1716
|
+
onChange: (e) => {
|
|
1717
|
+
setQuery(e.target.value);
|
|
1718
|
+
onSearchChange?.(e.target.value);
|
|
1719
|
+
},
|
|
1720
|
+
onKeyDown: onNavKeyDown,
|
|
1721
|
+
className: cn(
|
|
1722
|
+
"h-10 w-full border-0 bg-transparent pl-6 pr-2 text-[13px] text-[#1C1C1E]",
|
|
1723
|
+
"outline-none placeholder:text-[#8E8E93]"
|
|
1724
|
+
)
|
|
1725
|
+
}
|
|
1726
|
+
),
|
|
1727
|
+
loading && /* @__PURE__ */ jsxRuntime.jsx(SpinnerIcon, { className: "absolute right-3 h-3.5 w-3.5 text-[#8E8E93]" })
|
|
1728
|
+
] }),
|
|
1729
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1730
|
+
"div",
|
|
1731
|
+
{
|
|
1732
|
+
ref: listRef,
|
|
1733
|
+
id: listboxId,
|
|
1734
|
+
role: "listbox",
|
|
1735
|
+
"aria-labelledby": label ? labelId : void 0,
|
|
1736
|
+
className: "avt-scroll min-h-0 flex-1 overflow-y-auto py-1.5",
|
|
1737
|
+
children: flat.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3.5 py-6 text-center text-[13px] text-[#8E8E93]", children: loading ? loadingText : emptyText }) : groups.map((group) => /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "group", "aria-label": group.group, children: [
|
|
1738
|
+
group.group && /* @__PURE__ */ jsxRuntime.jsx("div", { className: groupLabelClass, children: group.group }),
|
|
1739
|
+
group.items.map((option, i) => {
|
|
1740
|
+
const index = group.offset + i;
|
|
1741
|
+
const state = {
|
|
1742
|
+
selected: value != null && option.value === value,
|
|
1743
|
+
active: index === activeIndex
|
|
1744
|
+
};
|
|
1745
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1746
|
+
"div",
|
|
1747
|
+
{
|
|
1748
|
+
id: `${uid}-opt-${index}`,
|
|
1749
|
+
"data-index": index,
|
|
1750
|
+
role: "option",
|
|
1751
|
+
"aria-selected": state.selected,
|
|
1752
|
+
"aria-disabled": option.disabled || void 0,
|
|
1753
|
+
className: optionRowClass({
|
|
1754
|
+
...state,
|
|
1755
|
+
disabled: option.disabled
|
|
1756
|
+
}),
|
|
1757
|
+
onMouseEnter: () => !option.disabled && setActiveIndex(index),
|
|
1758
|
+
onClick: () => commit(option),
|
|
1759
|
+
children: renderOption ? renderOption(option, state) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1760
|
+
option.icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0 text-[#8E8E93]", children: option.icon }),
|
|
1761
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 flex-1", children: [
|
|
1762
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "block truncate", children: option.label }),
|
|
1763
|
+
option.description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block truncate text-[11px] font-normal text-[#8E8E93]", children: option.description })
|
|
1764
|
+
] }),
|
|
1765
|
+
state.selected && /* @__PURE__ */ jsxRuntime.jsx(CheckIcon, { className: "h-4 w-4 flex-shrink-0 text-[#007AFF]" })
|
|
1766
|
+
] })
|
|
1767
|
+
},
|
|
1768
|
+
option.value
|
|
1769
|
+
);
|
|
1770
|
+
})
|
|
1771
|
+
] }, group.group ?? "__default"))
|
|
1772
|
+
}
|
|
1773
|
+
)
|
|
1774
|
+
]
|
|
1775
|
+
}
|
|
1776
|
+
),
|
|
1777
|
+
document.body
|
|
1778
|
+
) : null;
|
|
1779
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-col gap-1.5", containerClassName), ...props, children: [
|
|
1780
|
+
label && /* @__PURE__ */ jsxRuntime.jsx("label", { id: labelId, htmlFor: uid, className: "text-[13px] font-medium text-[#3C3C43]", children: label }),
|
|
1781
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
1782
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1783
|
+
"button",
|
|
1784
|
+
{
|
|
1785
|
+
ref: triggerRef,
|
|
1786
|
+
id: uid,
|
|
1787
|
+
type: "button",
|
|
1788
|
+
disabled,
|
|
1789
|
+
"aria-expanded": open,
|
|
1790
|
+
"aria-invalid": error ? true : void 0,
|
|
1791
|
+
className: cn(
|
|
1792
|
+
triggerBase,
|
|
1793
|
+
triggerSizes[size],
|
|
1794
|
+
"pr-9",
|
|
1795
|
+
showClear && "pr-14",
|
|
1796
|
+
error && "bg-[#FF3B30]/6 focus-visible:shadow-[0_0_0_2px_rgba(255,59,48,0.22)]",
|
|
1797
|
+
open && "bg-white shadow-[0_0_0_2px_rgba(0,122,255,0.22)]",
|
|
1798
|
+
className
|
|
1799
|
+
),
|
|
1800
|
+
onClick: () => changeOpen(!open),
|
|
1801
|
+
onKeyDown: onTriggerKeyDown,
|
|
1802
|
+
...comboProps,
|
|
1803
|
+
children: [
|
|
1804
|
+
selected?.icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0 text-[#8E8E93]", children: selected.icon }),
|
|
1805
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("flex-1 truncate", !selected && "text-[#8E8E93]"), children: selected ? selected.label : placeholder }),
|
|
1806
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1807
|
+
ChevronDownIcon,
|
|
1808
|
+
{
|
|
1809
|
+
className: cn(
|
|
1810
|
+
"absolute right-3.5 h-4 w-4 flex-shrink-0 text-[#8E8E93]",
|
|
1811
|
+
"transition-transform duration-150",
|
|
1812
|
+
open && "rotate-180"
|
|
1813
|
+
)
|
|
1814
|
+
}
|
|
1815
|
+
)
|
|
1816
|
+
]
|
|
1817
|
+
}
|
|
1818
|
+
),
|
|
1819
|
+
showClear && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1820
|
+
"button",
|
|
1821
|
+
{
|
|
1822
|
+
type: "button",
|
|
1823
|
+
tabIndex: -1,
|
|
1824
|
+
"aria-label": "Hapus pilihan",
|
|
1825
|
+
onClick: clear,
|
|
1826
|
+
className: cn(
|
|
1827
|
+
"absolute right-8 top-1/2 flex h-5 w-5 -translate-y-1/2 items-center justify-center",
|
|
1828
|
+
"rounded-full bg-[#E5E5EA] text-[#8E8E93] transition-colors duration-150",
|
|
1829
|
+
"hover:bg-[#D1D1D6] hover:text-[#1C1C1E]"
|
|
1830
|
+
),
|
|
1831
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ClearIcon, { className: "h-2.5 w-2.5" })
|
|
1832
|
+
}
|
|
1833
|
+
)
|
|
1834
|
+
] }),
|
|
1835
|
+
name && /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name, value: value ?? "", readOnly: true }),
|
|
1836
|
+
error ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-[#FF3B30]", children: error }) : hint ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-[#8E8E93]", children: hint }) : null,
|
|
1837
|
+
panel
|
|
1838
|
+
] });
|
|
1839
|
+
});
|
|
1840
|
+
Select.displayName = "Select";
|
|
1841
|
+
var SelectSearch = React3__default.default.forwardRef(
|
|
1842
|
+
function SelectSearch2(props, ref) {
|
|
1843
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Select, { ref, searchable: true, ...props });
|
|
1844
|
+
}
|
|
1845
|
+
);
|
|
1846
|
+
SelectSearch.displayName = "SelectSearch";
|
|
1847
|
+
var FOCUSABLE = [
|
|
1848
|
+
"a[href]",
|
|
1849
|
+
"button:not([disabled])",
|
|
1850
|
+
"input:not([disabled])",
|
|
1851
|
+
"select:not([disabled])",
|
|
1852
|
+
"textarea:not([disabled])",
|
|
1853
|
+
'[tabindex]:not([tabindex="-1"])'
|
|
1854
|
+
].join(",");
|
|
1855
|
+
function useFocusTrap(containerRef, enabled = true, initialFocusRef) {
|
|
1856
|
+
React3.useEffect(() => {
|
|
1857
|
+
if (!enabled) return;
|
|
1858
|
+
const container = containerRef.current;
|
|
1859
|
+
if (!container) return;
|
|
1860
|
+
const previouslyFocused = document.activeElement;
|
|
1861
|
+
const focusables = () => Array.from(container.querySelectorAll(FOCUSABLE)).filter(
|
|
1862
|
+
(el) => el.getClientRects().length > 0
|
|
1863
|
+
);
|
|
1864
|
+
const raf = requestAnimationFrame(() => {
|
|
1865
|
+
const target = initialFocusRef?.current ?? focusables()[0] ?? container;
|
|
1866
|
+
target.focus();
|
|
1867
|
+
});
|
|
1868
|
+
const onKeyDown = (e) => {
|
|
1869
|
+
if (e.key !== "Tab") return;
|
|
1870
|
+
const items = focusables();
|
|
1871
|
+
if (items.length === 0) {
|
|
1872
|
+
e.preventDefault();
|
|
1873
|
+
return;
|
|
1874
|
+
}
|
|
1875
|
+
const first = items[0];
|
|
1876
|
+
const last = items[items.length - 1];
|
|
1877
|
+
const active = document.activeElement;
|
|
1878
|
+
const inside = container.contains(active);
|
|
1879
|
+
if (e.shiftKey) {
|
|
1880
|
+
if (active === first || !inside) {
|
|
1881
|
+
e.preventDefault();
|
|
1882
|
+
last.focus();
|
|
1883
|
+
}
|
|
1884
|
+
} else if (active === last || !inside) {
|
|
1885
|
+
e.preventDefault();
|
|
1886
|
+
first.focus();
|
|
1887
|
+
}
|
|
1888
|
+
};
|
|
1889
|
+
document.addEventListener("keydown", onKeyDown);
|
|
1890
|
+
return () => {
|
|
1891
|
+
cancelAnimationFrame(raf);
|
|
1892
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
1893
|
+
previouslyFocused?.focus?.();
|
|
1894
|
+
};
|
|
1895
|
+
}, [containerRef, enabled, initialFocusRef]);
|
|
1896
|
+
}
|
|
1897
|
+
var sizes2 = {
|
|
1898
|
+
sm: "max-w-sm",
|
|
1899
|
+
md: "max-w-md",
|
|
1900
|
+
lg: "max-w-lg",
|
|
1901
|
+
xl: "max-w-2xl",
|
|
1902
|
+
full: "max-w-[calc(100vw-2rem)]"
|
|
1903
|
+
};
|
|
1904
|
+
var lockCount = 0;
|
|
1905
|
+
var previousOverflow = "";
|
|
1906
|
+
function useBodyScrollLock(enabled) {
|
|
1907
|
+
React3.useEffect(() => {
|
|
1908
|
+
if (!enabled) return;
|
|
1909
|
+
if (lockCount === 0) {
|
|
1910
|
+
previousOverflow = document.body.style.overflow;
|
|
1911
|
+
document.body.style.overflow = "hidden";
|
|
1912
|
+
}
|
|
1913
|
+
lockCount++;
|
|
1914
|
+
return () => {
|
|
1915
|
+
lockCount--;
|
|
1916
|
+
if (lockCount === 0) document.body.style.overflow = previousOverflow;
|
|
1917
|
+
};
|
|
1918
|
+
}, [enabled]);
|
|
1919
|
+
}
|
|
1920
|
+
function Modal({
|
|
1921
|
+
open,
|
|
1922
|
+
onClose,
|
|
1923
|
+
title,
|
|
1924
|
+
description,
|
|
1925
|
+
footer,
|
|
1926
|
+
size = "md",
|
|
1927
|
+
closeOnOverlay = true,
|
|
1928
|
+
closeOnEscape = true,
|
|
1929
|
+
showClose = true,
|
|
1930
|
+
initialFocusRef,
|
|
1931
|
+
overlayClassName,
|
|
1932
|
+
bodyClassName,
|
|
1933
|
+
role = "dialog",
|
|
1934
|
+
className,
|
|
1935
|
+
children,
|
|
1936
|
+
...props
|
|
1937
|
+
}) {
|
|
1938
|
+
const reactId = React3__default.default.useId();
|
|
1939
|
+
const titleId = `avt-modal-${reactId}-title`;
|
|
1940
|
+
const descId = `avt-modal-${reactId}-desc`;
|
|
1941
|
+
const panelRef = React3.useRef(null);
|
|
1942
|
+
useEscapeDismiss(open && closeOnEscape, onClose);
|
|
1943
|
+
useBodyScrollLock(open);
|
|
1944
|
+
useFocusTrap(panelRef, open, initialFocusRef);
|
|
1945
|
+
if (!open || typeof document === "undefined") return null;
|
|
1946
|
+
const hasHeader = Boolean(title || description || showClose);
|
|
1947
|
+
return reactDom.createPortal(
|
|
1948
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fixed inset-0 z-[1000] flex items-center justify-center p-4 sm:p-6", children: [
|
|
1949
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1950
|
+
"div",
|
|
1951
|
+
{
|
|
1952
|
+
className: cn(
|
|
1953
|
+
"avt-animate-fade-in absolute inset-0 bg-black/30 backdrop-blur-[2px]",
|
|
1954
|
+
overlayClassName
|
|
1955
|
+
),
|
|
1956
|
+
onClick: closeOnOverlay ? onClose : void 0,
|
|
1957
|
+
"aria-hidden": "true"
|
|
1958
|
+
}
|
|
1959
|
+
),
|
|
1960
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1961
|
+
"div",
|
|
1962
|
+
{
|
|
1963
|
+
ref: panelRef,
|
|
1964
|
+
role,
|
|
1965
|
+
"aria-modal": "true",
|
|
1966
|
+
"aria-labelledby": title ? titleId : void 0,
|
|
1967
|
+
"aria-describedby": description ? descId : void 0,
|
|
1968
|
+
tabIndex: -1,
|
|
1969
|
+
className: cn(
|
|
1970
|
+
"avt-animate-modal relative z-10 flex w-full flex-col overflow-hidden outline-none",
|
|
1971
|
+
"rounded-2xl bg-white/95 backdrop-blur-2xl",
|
|
1972
|
+
"border border-[#E5E5EA]/60 shadow-[0_16px_48px_rgba(0,0,0,0.16)]",
|
|
1973
|
+
"max-h-[calc(100vh-2rem)]",
|
|
1974
|
+
sizes2[size],
|
|
1975
|
+
className
|
|
1976
|
+
),
|
|
1977
|
+
...props,
|
|
1978
|
+
children: [
|
|
1979
|
+
hasHeader && /* @__PURE__ */ jsxRuntime.jsxs(ModalHeader, { showClose, onClose, children: [
|
|
1980
|
+
title && /* @__PURE__ */ jsxRuntime.jsx(ModalTitle, { id: titleId, children: title }),
|
|
1981
|
+
description && /* @__PURE__ */ jsxRuntime.jsx(ModalDescription, { id: descId, children: description })
|
|
1982
|
+
] }),
|
|
1983
|
+
/* @__PURE__ */ jsxRuntime.jsx(ModalBody, { className: bodyClassName, children }),
|
|
1984
|
+
footer && /* @__PURE__ */ jsxRuntime.jsx(ModalFooter, { children: footer })
|
|
1985
|
+
]
|
|
1986
|
+
}
|
|
1987
|
+
)
|
|
1988
|
+
] }),
|
|
1989
|
+
document.body
|
|
1990
|
+
);
|
|
1991
|
+
}
|
|
1992
|
+
function ModalHeader({
|
|
1993
|
+
showClose,
|
|
1994
|
+
onClose,
|
|
1995
|
+
className,
|
|
1996
|
+
children,
|
|
1997
|
+
...props
|
|
1998
|
+
}) {
|
|
1999
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2000
|
+
"div",
|
|
2001
|
+
{
|
|
2002
|
+
className: cn(
|
|
2003
|
+
"flex items-start justify-between border-b border-[#F2F2F7] px-6 pb-4 pt-6",
|
|
2004
|
+
className
|
|
2005
|
+
),
|
|
2006
|
+
...props,
|
|
2007
|
+
children: [
|
|
2008
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1 pr-3", children }),
|
|
2009
|
+
showClose && onClose && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2010
|
+
"button",
|
|
2011
|
+
{
|
|
2012
|
+
type: "button",
|
|
2013
|
+
onClick: onClose,
|
|
2014
|
+
"aria-label": "Tutup",
|
|
2015
|
+
className: cn(
|
|
2016
|
+
"flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-full",
|
|
2017
|
+
"bg-[#F2F2F7] text-[#8E8E93] hover:bg-[#E5E5EA] hover:text-[#1C1C1E]",
|
|
2018
|
+
"transition-colors duration-150",
|
|
2019
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#007AFF]/40"
|
|
2020
|
+
),
|
|
2021
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2022
|
+
"svg",
|
|
2023
|
+
{
|
|
2024
|
+
className: "h-3.5 w-3.5",
|
|
2025
|
+
viewBox: "0 0 24 24",
|
|
2026
|
+
fill: "none",
|
|
2027
|
+
stroke: "currentColor",
|
|
2028
|
+
strokeWidth: "2.5",
|
|
2029
|
+
strokeLinecap: "round",
|
|
2030
|
+
"aria-hidden": "true",
|
|
2031
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6L6 18M6 6l12 12" })
|
|
2032
|
+
}
|
|
2033
|
+
)
|
|
2034
|
+
}
|
|
2035
|
+
)
|
|
2036
|
+
]
|
|
2037
|
+
}
|
|
2038
|
+
);
|
|
2039
|
+
}
|
|
2040
|
+
function ModalTitle({ className, ...props }) {
|
|
2041
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2042
|
+
"h2",
|
|
2043
|
+
{
|
|
2044
|
+
className: cn("text-[15px] font-semibold tracking-[-0.01em] text-[#1C1C1E]", className),
|
|
2045
|
+
...props
|
|
2046
|
+
}
|
|
2047
|
+
);
|
|
2048
|
+
}
|
|
2049
|
+
function ModalDescription({
|
|
2050
|
+
className,
|
|
2051
|
+
...props
|
|
2052
|
+
}) {
|
|
2053
|
+
return /* @__PURE__ */ jsxRuntime.jsx("p", { className: cn("mt-0.5 text-[13px] text-[#8E8E93]", className), ...props });
|
|
2054
|
+
}
|
|
2055
|
+
function ModalBody({ className, ...props }) {
|
|
2056
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("avt-scroll flex-1 overflow-y-auto px-6 py-5", className), ...props });
|
|
2057
|
+
}
|
|
2058
|
+
function ModalFooter({ className, ...props }) {
|
|
2059
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2060
|
+
"div",
|
|
2061
|
+
{
|
|
2062
|
+
className: cn(
|
|
2063
|
+
"flex items-center justify-end gap-2 px-6 py-4",
|
|
2064
|
+
"border-t border-[#F2F2F7] bg-[#FAFAFA]",
|
|
2065
|
+
className
|
|
2066
|
+
),
|
|
2067
|
+
...props
|
|
2068
|
+
}
|
|
2069
|
+
);
|
|
2070
|
+
}
|
|
2071
|
+
var accents = {
|
|
2072
|
+
info: { bg: "bg-[#007AFF]/10", fg: "text-[#007AFF]", confirm: "primary" },
|
|
2073
|
+
success: { bg: "bg-[#34C759]/10", fg: "text-[#34C759]", confirm: "primary" },
|
|
2074
|
+
warning: { bg: "bg-[#FF9500]/10", fg: "text-[#FF9500]", confirm: "primary" },
|
|
2075
|
+
danger: {
|
|
2076
|
+
bg: "bg-[#FF3B30]/10",
|
|
2077
|
+
fg: "text-[#FF3B30]",
|
|
2078
|
+
confirm: "destructive"
|
|
2079
|
+
},
|
|
2080
|
+
question: { bg: "bg-[#5856D6]/10", fg: "text-[#5856D6]", confirm: "primary" }
|
|
2081
|
+
};
|
|
2082
|
+
var iconProps = {
|
|
2083
|
+
className: "h-5 w-5",
|
|
2084
|
+
viewBox: "0 0 24 24",
|
|
2085
|
+
fill: "none",
|
|
2086
|
+
stroke: "currentColor",
|
|
2087
|
+
strokeWidth: "2.2",
|
|
2088
|
+
strokeLinecap: "round",
|
|
2089
|
+
strokeLinejoin: "round",
|
|
2090
|
+
"aria-hidden": true
|
|
2091
|
+
};
|
|
2092
|
+
var icons = {
|
|
2093
|
+
info: /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...iconProps, children: [
|
|
2094
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "9" }),
|
|
2095
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 11v5M12 8h.01" })
|
|
2096
|
+
] }),
|
|
2097
|
+
success: /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...iconProps, children: [
|
|
2098
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "9" }),
|
|
2099
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8.5 12.5l2.5 2.5 4.5-5" })
|
|
2100
|
+
] }),
|
|
2101
|
+
warning: /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...iconProps, children: [
|
|
2102
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10.3 4.3L2.6 17.6A2 2 0 004.3 20.6h15.4a2 2 0 001.7-3L13.7 4.3a2 2 0 00-3.4 0z" }),
|
|
2103
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 9v4M12 17h.01" })
|
|
2104
|
+
] }),
|
|
2105
|
+
danger: /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...iconProps, children: [
|
|
2106
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "9" }),
|
|
2107
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M15 9l-6 6M9 9l6 6" })
|
|
2108
|
+
] }),
|
|
2109
|
+
question: /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...iconProps, children: [
|
|
2110
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "9" }),
|
|
2111
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9.5 9.5a2.5 2.5 0 113.4 2.3c-.6.2-.9.8-.9 1.4v.3M12 17h.01" })
|
|
2112
|
+
] })
|
|
2113
|
+
};
|
|
2114
|
+
function Dialog({
|
|
2115
|
+
open,
|
|
2116
|
+
onClose,
|
|
2117
|
+
variant = "question",
|
|
2118
|
+
title,
|
|
2119
|
+
description,
|
|
2120
|
+
confirmText = "Konfirmasi",
|
|
2121
|
+
cancelText = "Batal",
|
|
2122
|
+
onConfirm,
|
|
2123
|
+
onCancel,
|
|
2124
|
+
loading = false,
|
|
2125
|
+
closeOnConfirm = true,
|
|
2126
|
+
hideCancel = false,
|
|
2127
|
+
closeOnOverlay = true,
|
|
2128
|
+
closeOnEscape = true,
|
|
2129
|
+
size = "sm",
|
|
2130
|
+
icon,
|
|
2131
|
+
children,
|
|
2132
|
+
className
|
|
2133
|
+
}) {
|
|
2134
|
+
const reactId = React3__default.default.useId();
|
|
2135
|
+
const titleId = `avt-dialog-${reactId}-title`;
|
|
2136
|
+
const descId = `avt-dialog-${reactId}-desc`;
|
|
2137
|
+
const [busy, setBusy] = React3.useState(false);
|
|
2138
|
+
const isBusy = loading || busy;
|
|
2139
|
+
const accent = accents[variant];
|
|
2140
|
+
const handleCancel = React3.useCallback(() => {
|
|
2141
|
+
if (isBusy) return;
|
|
2142
|
+
onCancel?.();
|
|
2143
|
+
onClose();
|
|
2144
|
+
}, [isBusy, onCancel, onClose]);
|
|
2145
|
+
const handleConfirm = React3.useCallback(async () => {
|
|
2146
|
+
if (isBusy) return;
|
|
2147
|
+
if (!onConfirm) {
|
|
2148
|
+
if (closeOnConfirm) onClose();
|
|
2149
|
+
return;
|
|
2150
|
+
}
|
|
2151
|
+
setBusy(true);
|
|
2152
|
+
try {
|
|
2153
|
+
await onConfirm();
|
|
2154
|
+
if (closeOnConfirm) onClose();
|
|
2155
|
+
} catch {
|
|
2156
|
+
} finally {
|
|
2157
|
+
setBusy(false);
|
|
2158
|
+
}
|
|
2159
|
+
}, [isBusy, onConfirm, closeOnConfirm, onClose]);
|
|
2160
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2161
|
+
Modal,
|
|
2162
|
+
{
|
|
2163
|
+
open,
|
|
2164
|
+
onClose: handleCancel,
|
|
2165
|
+
role: "alertdialog",
|
|
2166
|
+
size,
|
|
2167
|
+
showClose: false,
|
|
2168
|
+
closeOnOverlay: closeOnOverlay && !isBusy,
|
|
2169
|
+
closeOnEscape: closeOnEscape && !isBusy,
|
|
2170
|
+
"aria-labelledby": titleId,
|
|
2171
|
+
"aria-describedby": description ? descId : void 0,
|
|
2172
|
+
bodyClassName: "px-6 pb-2 pt-6",
|
|
2173
|
+
className,
|
|
2174
|
+
footer: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2175
|
+
!hideCancel && /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "secondary", onClick: handleCancel, disabled: isBusy, children: cancelText }),
|
|
2176
|
+
/* @__PURE__ */ jsxRuntime.jsx(Button, { variant: accent.confirm, onClick: handleConfirm, loading: isBusy, children: confirmText })
|
|
2177
|
+
] }),
|
|
2178
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-4", children: [
|
|
2179
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2180
|
+
"div",
|
|
2181
|
+
{
|
|
2182
|
+
className: cn(
|
|
2183
|
+
"flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full",
|
|
2184
|
+
accent.bg,
|
|
2185
|
+
accent.fg
|
|
2186
|
+
),
|
|
2187
|
+
children: icon ?? icons[variant]
|
|
2188
|
+
}
|
|
2189
|
+
),
|
|
2190
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1 pt-0.5", children: [
|
|
2191
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { id: titleId, className: "text-[15px] font-semibold tracking-[-0.01em] text-[#1C1C1E]", children: title }),
|
|
2192
|
+
description && /* @__PURE__ */ jsxRuntime.jsx("p", { id: descId, className: "mt-1 text-[13px] leading-relaxed text-[#8E8E93]", children: description }),
|
|
2193
|
+
children && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3.5", children })
|
|
2194
|
+
] })
|
|
2195
|
+
] })
|
|
2196
|
+
}
|
|
2197
|
+
);
|
|
2198
|
+
}
|
|
1404
2199
|
function useClickOutside(ref, handler, enabled = true) {
|
|
1405
2200
|
React3.useEffect(() => {
|
|
1406
2201
|
if (!enabled) return;
|
|
@@ -1427,6 +2222,7 @@ exports.CardFooter = CardFooter;
|
|
|
1427
2222
|
exports.CardHeader = CardHeader;
|
|
1428
2223
|
exports.CardTitle = CardTitle;
|
|
1429
2224
|
exports.DataTable = DataTable;
|
|
2225
|
+
exports.Dialog = Dialog;
|
|
1430
2226
|
exports.Drawer = Drawer;
|
|
1431
2227
|
exports.Input = Input;
|
|
1432
2228
|
exports.Menu = MenuRoot;
|
|
@@ -1438,6 +2234,12 @@ exports.MenuLabel = MenuLabel;
|
|
|
1438
2234
|
exports.MenuRoot = MenuRoot;
|
|
1439
2235
|
exports.MenuSeparator = MenuSeparator;
|
|
1440
2236
|
exports.MenuTrigger = MenuTrigger;
|
|
2237
|
+
exports.Modal = Modal;
|
|
2238
|
+
exports.ModalBody = ModalBody;
|
|
2239
|
+
exports.ModalDescription = ModalDescription;
|
|
2240
|
+
exports.ModalFooter = ModalFooter;
|
|
2241
|
+
exports.ModalHeader = ModalHeader;
|
|
2242
|
+
exports.ModalTitle = ModalTitle;
|
|
1441
2243
|
exports.Navbar = Navbar;
|
|
1442
2244
|
exports.NavbarBrand = NavbarBrand;
|
|
1443
2245
|
exports.NavbarCenter = NavbarCenter;
|
|
@@ -1447,6 +2249,8 @@ exports.NavbarLeft = NavbarLeft;
|
|
|
1447
2249
|
exports.NavbarRight = NavbarRight;
|
|
1448
2250
|
exports.NavbarSearch = NavbarSearch;
|
|
1449
2251
|
exports.Scrollbar = Scrollbar;
|
|
2252
|
+
exports.Select = Select;
|
|
2253
|
+
exports.SelectSearch = SelectSearch;
|
|
1450
2254
|
exports.Sheet = Sheet;
|
|
1451
2255
|
exports.SheetContent = SheetContent;
|
|
1452
2256
|
exports.SheetDescription = SheetDescription;
|
|
@@ -1461,8 +2265,6 @@ exports.SidebarFooter = SidebarFooter;
|
|
|
1461
2265
|
exports.SidebarGroup = SidebarGroup;
|
|
1462
2266
|
exports.SidebarHeader = SidebarHeader;
|
|
1463
2267
|
exports.SidebarItem = SidebarItem;
|
|
1464
|
-
exports.SidebarSubMenu = SidebarSubMenu;
|
|
1465
|
-
exports.SidebarSubMenuItem = SidebarSubMenuItem;
|
|
1466
2268
|
exports.SidebarToggle = SidebarToggle;
|
|
1467
2269
|
exports.SidebarUser = SidebarUser;
|
|
1468
2270
|
exports.Table = Table;
|
|
@@ -1486,7 +2288,10 @@ exports.colors = colors;
|
|
|
1486
2288
|
exports.dismissToast = dismissToast;
|
|
1487
2289
|
exports.generateId = generateId;
|
|
1488
2290
|
exports.toast = toast;
|
|
2291
|
+
exports.useAnchoredPosition = useAnchoredPosition;
|
|
1489
2292
|
exports.useClickOutside = useClickOutside;
|
|
2293
|
+
exports.useEscapeDismiss = useEscapeDismiss;
|
|
2294
|
+
exports.useFocusTrap = useFocusTrap;
|
|
1490
2295
|
exports.useKeydown = useKeydown;
|
|
1491
2296
|
exports.useToast = useToast;
|
|
1492
2297
|
exports.withScrollbar = withScrollbar;
|