@endfield-ui/react 0.1.0 → 0.2.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/LICENSE +21 -21
- package/README.md +55 -5
- package/dist/index.d.ts +204 -2
- package/dist/index.js +615 -91
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/components/README.md +8 -1
- package/src/components/avatar/Avatar.tsx +3 -0
- package/src/components/carousel/Carousel.tsx +7 -47
- package/src/components/carousel/snap-track.ts +74 -0
- package/src/components/hover-card/HoverCard.tsx +101 -0
- package/src/components/hover-card/index.ts +6 -0
- package/src/components/image-viewer/ImageViewer.tsx +427 -0
- package/src/components/image-viewer/index.ts +7 -0
- package/src/components/item-slot/ItemSlot.tsx +1 -0
- package/src/components/otp-input/OtpInput.tsx +211 -0
- package/src/components/otp-input/index.ts +7 -0
- package/src/components/page-header/PageHeader.tsx +182 -0
- package/src/components/page-header/index.ts +6 -0
- package/src/components/popover/Popover.tsx +4 -12
- package/src/components/popover/panel-style.ts +15 -0
- package/src/components/scroll-area/ScrollArea.tsx +137 -0
- package/src/components/scroll-area/index.ts +5 -0
- package/src/components/table/Table.tsx +1 -0
- package/src/components/tag-input/TagInput.tsx +1 -0
- package/src/components/toc/Toc.tsx +1 -0
- package/src/index.ts +5 -0
package/dist/index.js
CHANGED
|
@@ -13,6 +13,9 @@ import { AlertDialog } from "@base-ui/react/alert-dialog";
|
|
|
13
13
|
import { Dialog as Dialog$1 } from "@base-ui/react/dialog";
|
|
14
14
|
import { Drawer as Drawer$1 } from "@base-ui/react/drawer";
|
|
15
15
|
import { Menu as Menu$1 } from "@base-ui/react/menu";
|
|
16
|
+
import { PreviewCard } from "@base-ui/react/preview-card";
|
|
17
|
+
import { OTPField } from "@base-ui/react/otp-field";
|
|
18
|
+
import { ScrollArea as ScrollArea$1 } from "@base-ui/react/scroll-area";
|
|
16
19
|
import { Select as Select$1 } from "@base-ui/react/select";
|
|
17
20
|
import { Tooltip as Tooltip$1 } from "@base-ui/react/tooltip";
|
|
18
21
|
import { Collapsible } from "@base-ui/react/collapsible";
|
|
@@ -959,7 +962,7 @@ const alignClass$1 = {
|
|
|
959
962
|
end: "justify-end",
|
|
960
963
|
between: "justify-between"
|
|
961
964
|
};
|
|
962
|
-
const gapClass = {
|
|
965
|
+
const gapClass$1 = {
|
|
963
966
|
sm: "gap-2",
|
|
964
967
|
md: "gap-3"
|
|
965
968
|
};
|
|
@@ -973,7 +976,7 @@ function ButtonGroup({ align = "end", orientation = "horizontal", gap = "md", cl
|
|
|
973
976
|
role: "group",
|
|
974
977
|
...props,
|
|
975
978
|
"data-orientation": orientation,
|
|
976
|
-
className: cn("flex", gapClass[gap], orientation === "vertical" ? "flex-col items-stretch" : ["flex-wrap items-center", alignClass$1[align]], className)
|
|
979
|
+
className: cn("flex", gapClass$1[gap], orientation === "vertical" ? "flex-col items-stretch" : ["flex-wrap items-center", alignClass$1[align]], className)
|
|
977
980
|
});
|
|
978
981
|
}
|
|
979
982
|
//#endregion
|
|
@@ -1309,48 +1312,32 @@ function DashIndicator({ count, index, className, ...props }) {
|
|
|
1309
1312
|
});
|
|
1310
1313
|
}
|
|
1311
1314
|
//#endregion
|
|
1312
|
-
//#region src/components/carousel/
|
|
1313
|
-
const ratioClass$2 = {
|
|
1314
|
-
"16/9": "aspect-video",
|
|
1315
|
-
"4/3": "aspect-4/3",
|
|
1316
|
-
"1/1": "aspect-square",
|
|
1317
|
-
"3/4": "aspect-3/4",
|
|
1318
|
-
"21/9": "aspect-21/9"
|
|
1319
|
-
};
|
|
1320
|
-
const SlideContext = createContext(null);
|
|
1315
|
+
//#region src/components/carousel/snap-track.ts
|
|
1321
1316
|
/** 滚动停下之后多久算"停稳了"。没有 scrollend 事件的浏览器靠它 */
|
|
1322
1317
|
const SETTLE_MS$1 = 120;
|
|
1323
1318
|
/**
|
|
1324
|
-
*
|
|
1325
|
-
*
|
|
1319
|
+
* 一条横向滚动加滚动吸附的轨道,每一张和轨道一样宽。下标和滚动位置互相跟:
|
|
1320
|
+
* 下标变了轨道滚过去;用户自己滑的时候反过来,等滚动停稳,由停下的位置定下标。
|
|
1321
|
+
* 手势、惯性都是浏览器的。媒体轮播和图片查看共用,不对外导出。
|
|
1322
|
+
*
|
|
1323
|
+
* 把 `trackRef` 和 `onScroll` 交给那个滚动容器。
|
|
1326
1324
|
*/
|
|
1327
|
-
function
|
|
1328
|
-
const slides = Children.toArray(children).filter((child) => isValidElement(child));
|
|
1329
|
-
const count = slides.length;
|
|
1330
|
-
const last = Math.max(0, count - 1);
|
|
1331
|
-
const [rawIndex, setIndex] = useControllableState({
|
|
1332
|
-
value: indexProp,
|
|
1333
|
-
defaultValue: defaultIndex,
|
|
1334
|
-
onChange: onIndexChange
|
|
1335
|
-
});
|
|
1336
|
-
const index = Math.min(Math.max(rawIndex, 0), last);
|
|
1325
|
+
function useSnapTrack({ index, count, onSettle }) {
|
|
1337
1326
|
const trackRef = useRef(null);
|
|
1338
1327
|
const settleTimer = useRef(void 0);
|
|
1328
|
+
const last = Math.max(0, count - 1);
|
|
1339
1329
|
const indexRef = useRef(index);
|
|
1340
1330
|
indexRef.current = index;
|
|
1341
|
-
const
|
|
1342
|
-
if (count === 0) return;
|
|
1343
|
-
const target = loop ? (next + count) % count : Math.min(Math.max(next, 0), last);
|
|
1344
|
-
setIndex(target);
|
|
1345
|
-
if (!loop && (target === 0 || target === last) && document.activeElement instanceof HTMLButtonElement && trackRef.current?.parentElement?.contains(document.activeElement)) trackRef.current.focus({ preventScroll: true });
|
|
1346
|
-
};
|
|
1331
|
+
const placed = useRef(false);
|
|
1347
1332
|
useEffect(() => {
|
|
1333
|
+
const first = !placed.current;
|
|
1334
|
+
placed.current = true;
|
|
1348
1335
|
const track = trackRef.current;
|
|
1349
1336
|
if (!track || track.clientWidth === 0) return;
|
|
1350
1337
|
if (Math.round(track.scrollLeft / track.clientWidth) === index) return;
|
|
1351
1338
|
track.scrollTo?.({
|
|
1352
1339
|
left: index * track.clientWidth,
|
|
1353
|
-
behavior: "smooth"
|
|
1340
|
+
behavior: first ? "instant" : "smooth"
|
|
1354
1341
|
});
|
|
1355
1342
|
}, [index]);
|
|
1356
1343
|
useEffect(() => {
|
|
@@ -1366,15 +1353,54 @@ function Carousel({ index: indexProp, defaultIndex = 0, onIndexChange, loop = fa
|
|
|
1366
1353
|
return () => observer.disconnect();
|
|
1367
1354
|
}, []);
|
|
1368
1355
|
useEffect(() => () => clearTimeout(settleTimer.current), []);
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1356
|
+
return {
|
|
1357
|
+
trackRef,
|
|
1358
|
+
onScroll: useCallback(() => {
|
|
1359
|
+
clearTimeout(settleTimer.current);
|
|
1360
|
+
settleTimer.current = setTimeout(() => {
|
|
1361
|
+
const track = trackRef.current;
|
|
1362
|
+
if (!track || track.clientWidth === 0) return;
|
|
1363
|
+
const stopped = Math.round(track.scrollLeft / track.clientWidth);
|
|
1364
|
+
onSettle(Math.min(Math.max(stopped, 0), last));
|
|
1365
|
+
}, SETTLE_MS$1);
|
|
1366
|
+
}, [onSettle, last])
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
//#endregion
|
|
1370
|
+
//#region src/components/carousel/Carousel.tsx
|
|
1371
|
+
const ratioClass$3 = {
|
|
1372
|
+
"16/9": "aspect-video",
|
|
1373
|
+
"4/3": "aspect-4/3",
|
|
1374
|
+
"1/1": "aspect-square",
|
|
1375
|
+
"3/4": "aspect-3/4",
|
|
1376
|
+
"21/9": "aspect-21/9"
|
|
1377
|
+
};
|
|
1378
|
+
const SlideContext = createContext(null);
|
|
1379
|
+
/**
|
|
1380
|
+
* 媒体轮播:一次看一张的大幅媒体,前后翻。不自动播放。
|
|
1381
|
+
* 轨道是原生的横向滚动加滚动吸附,触屏滑动是浏览器自带的。
|
|
1382
|
+
*/
|
|
1383
|
+
function Carousel({ index: indexProp, defaultIndex = 0, onIndexChange, loop = false, ratio = "16/9", indicator = false, previousLabel = "上一张", nextLabel = "下一张", className, children, ...props }) {
|
|
1384
|
+
const slides = Children.toArray(children).filter((child) => isValidElement(child));
|
|
1385
|
+
const count = slides.length;
|
|
1386
|
+
const last = Math.max(0, count - 1);
|
|
1387
|
+
const [rawIndex, setIndex] = useControllableState({
|
|
1388
|
+
value: indexProp,
|
|
1389
|
+
defaultValue: defaultIndex,
|
|
1390
|
+
onChange: onIndexChange
|
|
1391
|
+
});
|
|
1392
|
+
const index = Math.min(Math.max(rawIndex, 0), last);
|
|
1393
|
+
const { trackRef, onScroll } = useSnapTrack({
|
|
1394
|
+
index,
|
|
1395
|
+
count,
|
|
1396
|
+
onSettle: setIndex
|
|
1397
|
+
});
|
|
1398
|
+
const go = (next) => {
|
|
1399
|
+
if (count === 0) return;
|
|
1400
|
+
const target = loop ? (next + count) % count : Math.min(Math.max(next, 0), last);
|
|
1401
|
+
setIndex(target);
|
|
1402
|
+
if (!loop && (target === 0 || target === last) && document.activeElement instanceof HTMLButtonElement && trackRef.current?.parentElement?.contains(document.activeElement)) trackRef.current.focus({ preventScroll: true });
|
|
1403
|
+
};
|
|
1378
1404
|
const onKeyDown = (event) => {
|
|
1379
1405
|
const next = {
|
|
1380
1406
|
ArrowLeft: index - 1,
|
|
@@ -1403,7 +1429,7 @@ function Carousel({ index: indexProp, defaultIndex = 0, onIndexChange, loop = fa
|
|
|
1403
1429
|
onKeyDown: (event) => {
|
|
1404
1430
|
if (event.target === event.currentTarget) onKeyDown(event);
|
|
1405
1431
|
},
|
|
1406
|
-
className: cn("flex snap-x snap-mandatory overflow-x-auto overscroll-x-contain bg-surface-muted [scrollbar-width:none]", focusRing, ratioClass$
|
|
1432
|
+
className: cn("flex snap-x snap-mandatory overflow-x-auto overscroll-x-contain bg-surface-muted [scrollbar-width:none]", focusRing, ratioClass$3[ratio]),
|
|
1407
1433
|
children: slides.map((slide, position) => /* @__PURE__ */ jsx(SlideContext, {
|
|
1408
1434
|
value: {
|
|
1409
1435
|
position,
|
|
@@ -3753,6 +3779,327 @@ function HazardStripe({ size = "md", className, ...props }) {
|
|
|
3753
3779
|
});
|
|
3754
3780
|
}
|
|
3755
3781
|
//#endregion
|
|
3782
|
+
//#region src/components/popover/panel-style.ts
|
|
3783
|
+
/**
|
|
3784
|
+
* 气泡卡片与悬浮卡共用的面板:下拉菜单的那一块,只把上下 4px 的内边距换成四周 16px。
|
|
3785
|
+
* 宽度跟着内容走,最宽 320px,也不超过视口里剩下的地方。
|
|
3786
|
+
* 要经过 `cn()`:后面的 `p-4` 要盖掉菜单面板自带的上下内边距
|
|
3787
|
+
*/
|
|
3788
|
+
const popoverPanel = [menuPanel, "flex w-max max-w-[min(20rem,var(--available-width))] flex-col gap-3 p-4 text-sm wrap-anywhere"];
|
|
3789
|
+
//#endregion
|
|
3790
|
+
//#region src/components/hover-card/HoverCard.tsx
|
|
3791
|
+
/**
|
|
3792
|
+
* 悬浮卡:指针停在一个链接上时,在旁边预览它的去处。面板和气泡卡片是同一块。
|
|
3793
|
+
*
|
|
3794
|
+
* 触屏上不出现,读屏也不会读到它:里面只放"点进去也看得到"的内容,
|
|
3795
|
+
* 不放只有这里才有的信息和操作。要操作的用 `Popover`,一句说明用 `Tooltip`。
|
|
3796
|
+
*/
|
|
3797
|
+
function HoverCard({ trigger, title, description, side = "bottom", align = "start", delay = 600, closeDelay = 300, open, defaultOpen, onOpenChange, className, children, ...props }) {
|
|
3798
|
+
const { anchorRef, portalRef } = usePortalScope();
|
|
3799
|
+
return /* @__PURE__ */ jsxs(PreviewCard.Root, {
|
|
3800
|
+
open,
|
|
3801
|
+
defaultOpen,
|
|
3802
|
+
onOpenChange: onOpenChange && ((next) => onOpenChange(next)),
|
|
3803
|
+
children: [/* @__PURE__ */ jsx(PreviewCard.Trigger, {
|
|
3804
|
+
ref: anchorRef,
|
|
3805
|
+
render: trigger,
|
|
3806
|
+
delay,
|
|
3807
|
+
closeDelay
|
|
3808
|
+
}), /* @__PURE__ */ jsx(PreviewCard.Portal, {
|
|
3809
|
+
ref: portalRef,
|
|
3810
|
+
children: /* @__PURE__ */ jsx(PreviewCard.Positioner, {
|
|
3811
|
+
side,
|
|
3812
|
+
align,
|
|
3813
|
+
sideOffset: 4,
|
|
3814
|
+
className: "z-(--z-overlay) outline-none",
|
|
3815
|
+
children: /* @__PURE__ */ jsxs(PreviewCard.Popup, {
|
|
3816
|
+
...props,
|
|
3817
|
+
"data-hover-card": "",
|
|
3818
|
+
className: cn(popoverPanel, className),
|
|
3819
|
+
children: [(title || description) && /* @__PURE__ */ jsxs("div", {
|
|
3820
|
+
className: "flex flex-col gap-1",
|
|
3821
|
+
children: [title && /* @__PURE__ */ jsx("p", {
|
|
3822
|
+
className: "text-base font-bold",
|
|
3823
|
+
children: title
|
|
3824
|
+
}), description && /* @__PURE__ */ jsx("p", {
|
|
3825
|
+
className: "text-ink-secondary",
|
|
3826
|
+
children: description
|
|
3827
|
+
})]
|
|
3828
|
+
}), children]
|
|
3829
|
+
})
|
|
3830
|
+
})
|
|
3831
|
+
})]
|
|
3832
|
+
});
|
|
3833
|
+
}
|
|
3834
|
+
//#endregion
|
|
3835
|
+
//#region src/components/viewfinder/Viewfinder.tsx
|
|
3836
|
+
const armClass = {
|
|
3837
|
+
sm: "[--bracket-arm:16px]",
|
|
3838
|
+
md: "[--bracket-arm:24px]"
|
|
3839
|
+
};
|
|
3840
|
+
const readoutPosition = {
|
|
3841
|
+
topLeft: "top-5 left-5",
|
|
3842
|
+
topRight: "top-5 right-5 text-right",
|
|
3843
|
+
bottomLeft: "bottom-5 left-5",
|
|
3844
|
+
bottomRight: "right-5 bottom-5 text-right"
|
|
3845
|
+
};
|
|
3846
|
+
/**
|
|
3847
|
+
* 取景角:框住一张图、一片地图区域、一个焦点,标的是"范围",不是选中。
|
|
3848
|
+
* 里面放被框住的内容;压在深色画面上时给它加 `data-theme="dark"`。
|
|
3849
|
+
*
|
|
3850
|
+
* 只用在展示性的画面上,一屏一处。取景角与准星是装饰,读数是真实信息、读屏读得到。
|
|
3851
|
+
*/
|
|
3852
|
+
function Viewfinder({ size = "sm", crosshair = false, readouts, className, children, ...props }) {
|
|
3853
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3854
|
+
...props,
|
|
3855
|
+
className: cn("relative text-ink", className),
|
|
3856
|
+
children: [
|
|
3857
|
+
children,
|
|
3858
|
+
/* @__PURE__ */ jsx("span", {
|
|
3859
|
+
"aria-hidden": "true",
|
|
3860
|
+
className: cn(decor, "corner-brackets absolute inset-3 [--bracket-color:currentColor] [--bracket-offset:0px] [--bracket-width:1.5px]", armClass[size])
|
|
3861
|
+
}),
|
|
3862
|
+
crosshair && /* @__PURE__ */ jsx("svg", {
|
|
3863
|
+
"aria-hidden": "true",
|
|
3864
|
+
focusable: "false",
|
|
3865
|
+
viewBox: "0 0 12 12",
|
|
3866
|
+
fill: "none",
|
|
3867
|
+
stroke: "currentColor",
|
|
3868
|
+
strokeWidth: 1.5,
|
|
3869
|
+
className: cn("pointer-events-none select-none forced-colors:hidden print:hidden", "absolute top-1/2 left-1/2 size-3 -translate-1/2 text-accent-ink"),
|
|
3870
|
+
children: /* @__PURE__ */ jsx("path", { d: "M6 0v12M0 6h12" })
|
|
3871
|
+
}),
|
|
3872
|
+
readouts && Object.keys(readoutPosition).map((corner) => readouts[corner] !== void 0 && /* @__PURE__ */ jsx("span", {
|
|
3873
|
+
"data-corner": corner,
|
|
3874
|
+
className: cn("pointer-events-none absolute font-tech text-xs leading-none text-ink-secondary tabular-nums", readoutPosition[corner]),
|
|
3875
|
+
children: readouts[corner]
|
|
3876
|
+
}, corner))
|
|
3877
|
+
]
|
|
3878
|
+
});
|
|
3879
|
+
}
|
|
3880
|
+
//#endregion
|
|
3881
|
+
//#region src/components/image-viewer/ImageViewer.tsx
|
|
3882
|
+
const ratioClass$2 = {
|
|
3883
|
+
"16/9": "aspect-video",
|
|
3884
|
+
"4/3": "aspect-4/3",
|
|
3885
|
+
"1/1": "aspect-square",
|
|
3886
|
+
"3/4": "aspect-3/4"
|
|
3887
|
+
};
|
|
3888
|
+
const ItemContext = createContext(null);
|
|
3889
|
+
/**
|
|
3890
|
+
* 图片查看:一组缩略图,点一张放大到整屏看,前后翻。
|
|
3891
|
+
* 大图那一层在亮暗两个主题下都是深色;翻页用的是媒体轮播的那条轨道,触屏可以左右滑。
|
|
3892
|
+
*
|
|
3893
|
+
* `className` 和其余属性给缩略图的列表。
|
|
3894
|
+
*/
|
|
3895
|
+
function ImageViewer({ "aria-label": label, open: openProp, onOpenChange, index: indexProp, defaultIndex = 0, onIndexChange, loop = false, ratio = "4/3", viewLabel = "查看大图", previousLabel = "上一张", nextLabel = "下一张", closeLabel = "关闭", className, children, ...props }) {
|
|
3896
|
+
const items = Children.toArray(children).filter((child) => isValidElement(child));
|
|
3897
|
+
const count = items.length;
|
|
3898
|
+
const last = Math.max(0, count - 1);
|
|
3899
|
+
const [open, setOpen] = useControllableState({
|
|
3900
|
+
value: openProp,
|
|
3901
|
+
defaultValue: false,
|
|
3902
|
+
onChange: onOpenChange
|
|
3903
|
+
});
|
|
3904
|
+
const [rawIndex, setIndex] = useControllableState({
|
|
3905
|
+
value: indexProp,
|
|
3906
|
+
defaultValue: defaultIndex,
|
|
3907
|
+
onChange: onIndexChange
|
|
3908
|
+
});
|
|
3909
|
+
const index = Math.min(Math.max(rawIndex, 0), last);
|
|
3910
|
+
const thumbs = useRef([]);
|
|
3911
|
+
const openAt = (position) => {
|
|
3912
|
+
setIndex(position);
|
|
3913
|
+
setOpen(true);
|
|
3914
|
+
};
|
|
3915
|
+
const register = (position, node) => {
|
|
3916
|
+
thumbs.current[position] = node;
|
|
3917
|
+
};
|
|
3918
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("ul", {
|
|
3919
|
+
...props,
|
|
3920
|
+
"aria-label": label,
|
|
3921
|
+
"data-ratio": ratio,
|
|
3922
|
+
className: cn("grid grid-cols-[repeat(auto-fill,minmax(7rem,1fr))] gap-2 text-ink", className),
|
|
3923
|
+
children: items.map((item, position) => /* @__PURE__ */ jsx(ItemContext, {
|
|
3924
|
+
value: {
|
|
3925
|
+
position,
|
|
3926
|
+
count,
|
|
3927
|
+
ratio,
|
|
3928
|
+
viewLabel,
|
|
3929
|
+
open: openAt,
|
|
3930
|
+
register
|
|
3931
|
+
},
|
|
3932
|
+
children: item
|
|
3933
|
+
}, position))
|
|
3934
|
+
}), /* @__PURE__ */ jsx(Dialog$1.Root, {
|
|
3935
|
+
open,
|
|
3936
|
+
onOpenChange: (next) => setOpen(next),
|
|
3937
|
+
children: /* @__PURE__ */ jsx(Dialog$1.Portal, { children: /* @__PURE__ */ jsx(Dialog$1.Viewport, {
|
|
3938
|
+
className: "fixed inset-0 z-(--z-overlay)",
|
|
3939
|
+
children: /* @__PURE__ */ jsx(Stage, {
|
|
3940
|
+
label,
|
|
3941
|
+
items,
|
|
3942
|
+
index,
|
|
3943
|
+
setIndex,
|
|
3944
|
+
loop,
|
|
3945
|
+
previousLabel,
|
|
3946
|
+
nextLabel,
|
|
3947
|
+
closeLabel,
|
|
3948
|
+
thumbs,
|
|
3949
|
+
onClose: () => setOpen(false)
|
|
3950
|
+
})
|
|
3951
|
+
}) })
|
|
3952
|
+
})] });
|
|
3953
|
+
}
|
|
3954
|
+
/** 大图层。只在打开的时候挂着:轨道一挂上就直接对到被点的那一张 */
|
|
3955
|
+
function Stage({ label, items, index, setIndex, loop, previousLabel, nextLabel, closeLabel, thumbs, onClose }) {
|
|
3956
|
+
const count = items.length;
|
|
3957
|
+
const last = Math.max(0, count - 1);
|
|
3958
|
+
const { trackRef, onScroll } = useSnapTrack({
|
|
3959
|
+
index,
|
|
3960
|
+
count,
|
|
3961
|
+
onSettle: setIndex
|
|
3962
|
+
});
|
|
3963
|
+
const popupRef = useRef(null);
|
|
3964
|
+
const indexRef = useRef(index);
|
|
3965
|
+
indexRef.current = index;
|
|
3966
|
+
const go = (next) => {
|
|
3967
|
+
if (count === 0) return;
|
|
3968
|
+
const target = loop ? (next + count) % count : Math.min(Math.max(next, 0), last);
|
|
3969
|
+
setIndex(target);
|
|
3970
|
+
if (!loop && (target === 0 || target === last) && document.activeElement instanceof HTMLButtonElement && document.activeElement.hasAttribute("data-image-viewer-nav")) popupRef.current?.focus({ preventScroll: true });
|
|
3971
|
+
};
|
|
3972
|
+
const onKeyDown = (event) => {
|
|
3973
|
+
if (event.defaultPrevented) return;
|
|
3974
|
+
if (event.target.closest("video, audio, input, textarea")) return;
|
|
3975
|
+
const next = {
|
|
3976
|
+
ArrowLeft: index - 1,
|
|
3977
|
+
ArrowRight: index + 1,
|
|
3978
|
+
Home: 0,
|
|
3979
|
+
End: last
|
|
3980
|
+
}[event.key];
|
|
3981
|
+
if (next === void 0) return;
|
|
3982
|
+
event.preventDefault();
|
|
3983
|
+
go(next);
|
|
3984
|
+
};
|
|
3985
|
+
const current = items[index]?.props;
|
|
3986
|
+
const many = count > 1;
|
|
3987
|
+
const captioned = Boolean(current?.title || current?.description);
|
|
3988
|
+
return /* @__PURE__ */ jsxs(Dialog$1.Popup, {
|
|
3989
|
+
"data-theme": "dark",
|
|
3990
|
+
"data-image-viewer": "",
|
|
3991
|
+
ref: popupRef,
|
|
3992
|
+
initialFocus: popupRef,
|
|
3993
|
+
finalFocus: () => thumbs.current[indexRef.current] ?? true,
|
|
3994
|
+
onKeyDown,
|
|
3995
|
+
className: cn("flex size-full flex-col bg-surface text-ink outline-none", "transition-opacity duration-(--duration-fast) ease-standard", "data-ending-style:opacity-0 data-starting-style:opacity-0"),
|
|
3996
|
+
children: [
|
|
3997
|
+
/* @__PURE__ */ jsxs("div", {
|
|
3998
|
+
className: cn(overlayHeader, "h-14 px-4"),
|
|
3999
|
+
children: [
|
|
4000
|
+
/* @__PURE__ */ jsx(Dialog$1.Title, {
|
|
4001
|
+
className: "sr-only",
|
|
4002
|
+
children: label
|
|
4003
|
+
}),
|
|
4004
|
+
many && /* @__PURE__ */ jsx("span", {
|
|
4005
|
+
"aria-hidden": "true",
|
|
4006
|
+
"data-count": "",
|
|
4007
|
+
className: "font-tech text-sm text-ink-secondary tabular-nums",
|
|
4008
|
+
children: `${index + 1} / ${count}`
|
|
4009
|
+
}),
|
|
4010
|
+
/* @__PURE__ */ jsx(Dialog$1.Close, {
|
|
4011
|
+
"aria-label": closeLabel,
|
|
4012
|
+
className: overlayClose,
|
|
4013
|
+
children: /* @__PURE__ */ jsx(Close, { size: 24 })
|
|
4014
|
+
})
|
|
4015
|
+
]
|
|
4016
|
+
}),
|
|
4017
|
+
/* @__PURE__ */ jsx(Viewfinder, {
|
|
4018
|
+
size: "md",
|
|
4019
|
+
className: "min-h-0 flex-1",
|
|
4020
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
4021
|
+
ref: trackRef,
|
|
4022
|
+
"data-image-viewer-track": "",
|
|
4023
|
+
onScroll,
|
|
4024
|
+
className: "absolute inset-0 flex snap-x snap-mandatory overflow-x-auto overscroll-x-contain [scrollbar-width:none]",
|
|
4025
|
+
children: items.map((item, position) => /* @__PURE__ */ jsx("div", {
|
|
4026
|
+
role: "group",
|
|
4027
|
+
"aria-roledescription": "幻灯片",
|
|
4028
|
+
"aria-label": `第 ${position + 1} 张,共 ${count} 张`,
|
|
4029
|
+
inert: position !== index,
|
|
4030
|
+
"data-current": position === index ? "" : void 0,
|
|
4031
|
+
onClick: (event) => {
|
|
4032
|
+
if (event.target === event.currentTarget) onClose();
|
|
4033
|
+
},
|
|
4034
|
+
className: cn("flex size-full shrink-0 snap-start items-center justify-center p-6 sm:p-10", "*:max-h-full *:max-w-full [&>img]:object-contain [&>video]:object-contain"),
|
|
4035
|
+
children: Math.abs(position - index) <= 1 ? item.props.children : null
|
|
4036
|
+
}, position))
|
|
4037
|
+
})
|
|
4038
|
+
}),
|
|
4039
|
+
(many || captioned) && /* @__PURE__ */ jsxs("div", {
|
|
4040
|
+
className: "flex shrink-0 flex-wrap items-start gap-x-6 gap-y-3 border-t border-line px-4 py-4",
|
|
4041
|
+
children: [many && /* @__PURE__ */ jsxs("div", {
|
|
4042
|
+
className: "flex shrink-0 gap-2",
|
|
4043
|
+
children: [/* @__PURE__ */ jsx(IconButton, {
|
|
4044
|
+
variant: "floating",
|
|
4045
|
+
"aria-label": previousLabel,
|
|
4046
|
+
"data-image-viewer-nav": "",
|
|
4047
|
+
disabled: !loop && index <= 0,
|
|
4048
|
+
onClick: () => go(index - 1),
|
|
4049
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, {})
|
|
4050
|
+
}), /* @__PURE__ */ jsx(IconButton, {
|
|
4051
|
+
variant: "floating",
|
|
4052
|
+
"aria-label": nextLabel,
|
|
4053
|
+
"data-image-viewer-nav": "",
|
|
4054
|
+
disabled: !loop && index >= last,
|
|
4055
|
+
onClick: () => go(index + 1),
|
|
4056
|
+
children: /* @__PURE__ */ jsx(ChevronRight, {})
|
|
4057
|
+
})]
|
|
4058
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
4059
|
+
"aria-live": "polite",
|
|
4060
|
+
className: "flex min-w-0 flex-1 basis-48 flex-col gap-1",
|
|
4061
|
+
children: [
|
|
4062
|
+
many && /* @__PURE__ */ jsx("span", {
|
|
4063
|
+
className: "sr-only",
|
|
4064
|
+
children: `第 ${index + 1} 张,共 ${count} 张`
|
|
4065
|
+
}),
|
|
4066
|
+
current?.title && /* @__PURE__ */ jsx("p", {
|
|
4067
|
+
className: "text-base font-bold wrap-anywhere",
|
|
4068
|
+
children: current.title
|
|
4069
|
+
}),
|
|
4070
|
+
current?.description && /* @__PURE__ */ jsx("p", {
|
|
4071
|
+
className: "max-h-24 overflow-y-auto text-sm wrap-anywhere text-ink-secondary",
|
|
4072
|
+
children: current.description
|
|
4073
|
+
})
|
|
4074
|
+
]
|
|
4075
|
+
})]
|
|
4076
|
+
})
|
|
4077
|
+
]
|
|
4078
|
+
});
|
|
4079
|
+
}
|
|
4080
|
+
/** 图片查看里的一张。直接放在 `ImageViewer` 里。 */
|
|
4081
|
+
function ImageViewerItem({ title, description: _description, thumbnail, "aria-label": ariaLabel, className, children, ...props }) {
|
|
4082
|
+
const item = useContext(ItemContext);
|
|
4083
|
+
if (!item) throw new Error("ImageViewerItem 要直接放在 ImageViewer 里面");
|
|
4084
|
+
const name = ariaLabel ?? `${item.viewLabel}:${typeof title === "string" ? title : `第 ${item.position + 1} 张,共 ${item.count} 张`}`;
|
|
4085
|
+
return /* @__PURE__ */ jsx("li", {
|
|
4086
|
+
...props,
|
|
4087
|
+
className: cn("min-w-0", className),
|
|
4088
|
+
children: /* @__PURE__ */ jsxs("button", {
|
|
4089
|
+
type: "button",
|
|
4090
|
+
ref: (node) => item.register(item.position, node),
|
|
4091
|
+
"aria-label": name,
|
|
4092
|
+
"aria-haspopup": "dialog",
|
|
4093
|
+
onClick: () => item.open(item.position),
|
|
4094
|
+
className: cn("group/thumb relative block w-full overflow-clip rounded-md bg-surface-muted", "[&>img]:size-full [&>img]:object-cover [&>svg]:size-full [&>video]:size-full [&>video]:object-cover", focusRing, ratioClass$2[item.ratio]),
|
|
4095
|
+
children: [thumbnail ?? children, /* @__PURE__ */ jsx("span", {
|
|
4096
|
+
"aria-hidden": "true",
|
|
4097
|
+
className: "pointer-events-none absolute inset-x-0 bottom-0 h-1 origin-left scale-x-0 bg-action transition-transform duration-(--duration-base) ease-standard group-hover/thumb:scale-x-100 group-focus-visible/thumb:scale-x-100"
|
|
4098
|
+
})]
|
|
4099
|
+
})
|
|
4100
|
+
});
|
|
4101
|
+
}
|
|
4102
|
+
//#endregion
|
|
3756
4103
|
//#region src/components/input/Input.tsx
|
|
3757
4104
|
/**
|
|
3758
4105
|
* 单行输入框。`className` 给外框,其余属性与 `ref` 给里面的 `<input>`。
|
|
@@ -4683,6 +5030,174 @@ function Navigator({ items, index, defaultIndex = 0, onIndexChange, loop = false
|
|
|
4683
5030
|
});
|
|
4684
5031
|
}
|
|
4685
5032
|
//#endregion
|
|
5033
|
+
//#region src/components/otp-input/OtpInput.tsx
|
|
5034
|
+
const slotSize = {
|
|
5035
|
+
sm: "h-8 w-8 text-base",
|
|
5036
|
+
md: "h-10 w-10 text-lg",
|
|
5037
|
+
lg: "h-14 w-14 text-2xl"
|
|
5038
|
+
};
|
|
5039
|
+
const slotFrame = {
|
|
5040
|
+
sunken: "border-b-2",
|
|
5041
|
+
outline: "border"
|
|
5042
|
+
};
|
|
5043
|
+
const slotFill = {
|
|
5044
|
+
sunken: "bg-surface-sunken",
|
|
5045
|
+
outline: "bg-surface"
|
|
5046
|
+
};
|
|
5047
|
+
const gapClass = {
|
|
5048
|
+
sm: "gap-1.5",
|
|
5049
|
+
md: "gap-2",
|
|
5050
|
+
lg: "gap-2"
|
|
5051
|
+
};
|
|
5052
|
+
const defaultSlotLabel = (position, length) => `第 ${position} 位,共 ${length} 位`;
|
|
5053
|
+
const toUpperCase = (value) => value.toUpperCase();
|
|
5054
|
+
/**
|
|
5055
|
+
* 验证码输入:一格一位的短码。每一格是一个真的输入框——打一位跳一格,
|
|
5056
|
+
* 粘贴一整串会分到各格,整组在 Tab 顺序里只停一次。
|
|
5057
|
+
*
|
|
5058
|
+
* 每个都要有标签:放进 `Field`,或者自己传 `aria-label`。
|
|
5059
|
+
* `className` 与 `ref` 给外面那一排。
|
|
5060
|
+
*/
|
|
5061
|
+
function OtpInput({ length = 6, value, defaultValue, onValueChange, onComplete, type = "numeric", uppercase = false, mask = false, groupSize, size = "md", variant = "sunken", invalid: invalidProp, id: idProp, disabled: disabledProp, required: requiredProp, readOnly = false, name, form, autoSubmit = false, autoFocus = false, slotLabel = defaultSlotLabel, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": describedByProp, className, ...props }) {
|
|
5062
|
+
const fieldContext = useFieldContext();
|
|
5063
|
+
const { id, disabled, required, invalid, ...field } = useFieldControl({
|
|
5064
|
+
id: idProp,
|
|
5065
|
+
disabled: disabledProp,
|
|
5066
|
+
required: requiredProp,
|
|
5067
|
+
invalid: invalidProp,
|
|
5068
|
+
"aria-describedby": describedByProp
|
|
5069
|
+
});
|
|
5070
|
+
const describedBy = field["aria-describedby"];
|
|
5071
|
+
const labelId = useId();
|
|
5072
|
+
const ownLabel = ariaLabelledBy ?? (ariaLabel ? labelId : void 0);
|
|
5073
|
+
const groupLabel = ownLabel ?? fieldContext?.labelId;
|
|
5074
|
+
const slotClass = cn("min-w-0 shrink text-center font-tech font-medium tabular-nums text-ink caret-ink", "transition-colors duration-(--duration-fast) ease-standard", focusRing, slotSize[size], slotFrame[variant], readOnly ? "border-transparent bg-transparent" : slotFill[variant], disabled ? "cursor-not-allowed border-line text-ink-disabled" : invalid ? "border-danger" : !readOnly && "border-line-strong hover:not-focus:border-ink-secondary focus:border-ink");
|
|
5075
|
+
const group = groupSize !== void 0 && groupSize > 0 ? groupSize : 0;
|
|
5076
|
+
return /* @__PURE__ */ jsxs(OTPField.Root, {
|
|
5077
|
+
...props,
|
|
5078
|
+
id,
|
|
5079
|
+
length,
|
|
5080
|
+
value,
|
|
5081
|
+
defaultValue,
|
|
5082
|
+
onValueChange: onValueChange && ((next) => onValueChange(next)),
|
|
5083
|
+
onValueComplete: onComplete && ((next) => onComplete(next)),
|
|
5084
|
+
validationType: type,
|
|
5085
|
+
normalizeValue: uppercase ? toUpperCase : void 0,
|
|
5086
|
+
mask,
|
|
5087
|
+
disabled,
|
|
5088
|
+
readOnly,
|
|
5089
|
+
required,
|
|
5090
|
+
name,
|
|
5091
|
+
form,
|
|
5092
|
+
autoSubmit,
|
|
5093
|
+
"aria-labelledby": groupLabel,
|
|
5094
|
+
"aria-describedby": describedBy,
|
|
5095
|
+
"data-size": size,
|
|
5096
|
+
"data-variant": variant,
|
|
5097
|
+
"data-invalid": invalid ? "" : void 0,
|
|
5098
|
+
className: cn("flex max-w-full min-w-0 items-center", gapClass[size], className),
|
|
5099
|
+
children: [ariaLabel && !ariaLabelledBy && /* @__PURE__ */ jsx("span", {
|
|
5100
|
+
id: labelId,
|
|
5101
|
+
hidden: true,
|
|
5102
|
+
children: ariaLabel
|
|
5103
|
+
}), Array.from({ length }, (_, index) => /* @__PURE__ */ jsxs(Fragment, { children: [group > 0 && index > 0 && index % group === 0 && /* @__PURE__ */ jsx("span", {
|
|
5104
|
+
"aria-hidden": "true",
|
|
5105
|
+
"data-separator": "",
|
|
5106
|
+
className: "h-0.5 w-2 shrink-0 bg-line-strong"
|
|
5107
|
+
}), /* @__PURE__ */ jsx(OTPField.Input, {
|
|
5108
|
+
...index === 0 ? {
|
|
5109
|
+
"aria-labelledby": ownLabel,
|
|
5110
|
+
"aria-describedby": describedBy
|
|
5111
|
+
} : { "aria-label": slotLabel(index + 1, length) },
|
|
5112
|
+
"aria-invalid": invalid || void 0,
|
|
5113
|
+
autoFocus: autoFocus && index === 0,
|
|
5114
|
+
autoCapitalize: uppercase ? "characters" : "none",
|
|
5115
|
+
className: slotClass
|
|
5116
|
+
})] }, index))]
|
|
5117
|
+
});
|
|
5118
|
+
}
|
|
5119
|
+
//#endregion
|
|
5120
|
+
//#region src/icons/ArrowLeft.tsx
|
|
5121
|
+
const ArrowLeft = createIcon("ArrowLeft", /* @__PURE__ */ jsx("path", { d: "M20 12H5M11 5l-7 7 7 7" }));
|
|
5122
|
+
//#endregion
|
|
5123
|
+
//#region src/components/page-header/PageHeader.tsx
|
|
5124
|
+
/**
|
|
5125
|
+
* 页头:这是哪一页、怎么回去、在这一页能做什么。一页一个。
|
|
5126
|
+
* 按自身的宽度响应:行动区放不下时折到标题下面。
|
|
5127
|
+
*/
|
|
5128
|
+
function PageHeader({ title, level = 1, meta, description, breadcrumb, back, actions, divider = false, sticky = false, className, children, ...props }) {
|
|
5129
|
+
const Heading = `h${level}`;
|
|
5130
|
+
return /* @__PURE__ */ jsxs("header", {
|
|
5131
|
+
...props,
|
|
5132
|
+
"data-sticky": sticky ? "" : void 0,
|
|
5133
|
+
className: cn("@container flex flex-col gap-4 text-ink", divider && !sticky && "border-b border-line pb-6", sticky && "sticky top-0 z-(--z-float) border-b border-line bg-surface py-4", className),
|
|
5134
|
+
children: [
|
|
5135
|
+
breadcrumb,
|
|
5136
|
+
/* @__PURE__ */ jsxs("div", {
|
|
5137
|
+
className: "flex flex-col gap-1",
|
|
5138
|
+
children: [meta && /* @__PURE__ */ jsx("p", {
|
|
5139
|
+
className: "font-tech text-xs text-ink-secondary",
|
|
5140
|
+
children: meta
|
|
5141
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
5142
|
+
className: "flex flex-wrap items-start justify-between gap-x-6 gap-y-4",
|
|
5143
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
5144
|
+
className: "flex min-w-0 flex-1 basis-64 items-start gap-4",
|
|
5145
|
+
children: [back && /* @__PURE__ */ jsx("div", {
|
|
5146
|
+
className: "flex h-10 shrink-0 items-center @md:h-12",
|
|
5147
|
+
children: back
|
|
5148
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
5149
|
+
className: "flex min-w-0 flex-col gap-1",
|
|
5150
|
+
children: [/* @__PURE__ */ jsx(Heading, {
|
|
5151
|
+
className: "min-w-0 text-2xl leading-10 font-bold wrap-anywhere @md:text-3xl @md:leading-12",
|
|
5152
|
+
children: title
|
|
5153
|
+
}), description && /* @__PURE__ */ jsx("p", {
|
|
5154
|
+
className: "max-w-prose text-sm text-ink-secondary",
|
|
5155
|
+
children: description
|
|
5156
|
+
})]
|
|
5157
|
+
})]
|
|
5158
|
+
}), actions && /* @__PURE__ */ jsx("div", {
|
|
5159
|
+
"data-actions": "",
|
|
5160
|
+
className: "flex min-h-10 flex-wrap items-center gap-2 @md:min-h-12",
|
|
5161
|
+
children: actions
|
|
5162
|
+
})]
|
|
5163
|
+
})]
|
|
5164
|
+
}),
|
|
5165
|
+
children
|
|
5166
|
+
]
|
|
5167
|
+
});
|
|
5168
|
+
}
|
|
5169
|
+
/**
|
|
5170
|
+
* 页头里的返回:40px 的深灰斜纹方块加一个左箭头,`back` 按钮收成的图标形态。
|
|
5171
|
+
* 放进 `PageHeader` 的 `back`。
|
|
5172
|
+
*/
|
|
5173
|
+
function PageHeaderBack({ "aria-label": label = "返回", href, target, rel, render, onClick, disabled = false, className, ...props }) {
|
|
5174
|
+
const classes = cn("relative inline-flex size-10 shrink-0 items-center justify-center", "transition-colors duration-(--duration-fast) ease-standard", focusRing, disabled ? "cursor-not-allowed bg-disabled text-on-disabled" : "hatch hatch-mid bg-control text-white shadow-xs hover:bg-neutral-600 active:bg-control-pressed", className);
|
|
5175
|
+
const icon = /* @__PURE__ */ jsx(ArrowLeft, { size: 20 });
|
|
5176
|
+
if (href !== void 0 || render !== void 0) return /* @__PURE__ */ jsx(LinkElement, {
|
|
5177
|
+
render,
|
|
5178
|
+
href: disabled ? void 0 : href,
|
|
5179
|
+
role: disabled ? "link" : void 0,
|
|
5180
|
+
"aria-disabled": disabled || void 0,
|
|
5181
|
+
"aria-label": label,
|
|
5182
|
+
target,
|
|
5183
|
+
rel,
|
|
5184
|
+
onClick: disabled ? (event) => event.preventDefault() : onClick,
|
|
5185
|
+
"data-page-header-back": "",
|
|
5186
|
+
className: classes,
|
|
5187
|
+
children: icon
|
|
5188
|
+
});
|
|
5189
|
+
return /* @__PURE__ */ jsx("button", {
|
|
5190
|
+
...props,
|
|
5191
|
+
type: "button",
|
|
5192
|
+
"aria-label": label,
|
|
5193
|
+
disabled,
|
|
5194
|
+
onClick,
|
|
5195
|
+
"data-page-header-back": "",
|
|
5196
|
+
className: classes,
|
|
5197
|
+
children: icon
|
|
5198
|
+
});
|
|
5199
|
+
}
|
|
5200
|
+
//#endregion
|
|
4686
5201
|
//#region src/components/pagination/Pagination.tsx
|
|
4687
5202
|
const barSize = {
|
|
4688
5203
|
md: "h-12",
|
|
@@ -4867,7 +5382,7 @@ function Popover({ trigger, title, description, side = "bottom", align = "start"
|
|
|
4867
5382
|
className: "z-(--z-overlay) outline-none",
|
|
4868
5383
|
children: /* @__PURE__ */ jsxs(Popover$1.Popup, {
|
|
4869
5384
|
...props,
|
|
4870
|
-
className: cn(
|
|
5385
|
+
className: cn(popoverPanel, className),
|
|
4871
5386
|
children: [(title || description) && /* @__PURE__ */ jsxs("div", {
|
|
4872
5387
|
className: "flex flex-col gap-1",
|
|
4873
5388
|
children: [title && /* @__PURE__ */ jsx(Popover$1.Title, {
|
|
@@ -5343,6 +5858,64 @@ function ScheduleItem({ start, end, title, type, variant = "banner", media, href
|
|
|
5343
5858
|
});
|
|
5344
5859
|
}
|
|
5345
5860
|
//#endregion
|
|
5861
|
+
//#region src/components/scroll-area/ScrollArea.tsx
|
|
5862
|
+
const barClass = {
|
|
5863
|
+
vertical: "w-3 before:absolute before:inset-y-0 before:left-1/2 before:w-px before:-translate-x-1/2 before:bg-line",
|
|
5864
|
+
horizontal: "h-3 before:absolute before:inset-x-0 before:top-1/2 before:h-px before:-translate-y-1/2 before:bg-line"
|
|
5865
|
+
};
|
|
5866
|
+
const thumbClass = {
|
|
5867
|
+
vertical: "w-full before:inset-y-0 before:left-1/2 before:w-[3px] before:-translate-x-1/2",
|
|
5868
|
+
horizontal: "h-full before:inset-x-0 before:top-1/2 before:h-[3px] before:-translate-y-1/2"
|
|
5869
|
+
};
|
|
5870
|
+
function Scrollbar({ orientation }) {
|
|
5871
|
+
return /* @__PURE__ */ jsx(ScrollArea$1.Scrollbar, {
|
|
5872
|
+
orientation,
|
|
5873
|
+
className: cn("group/bar flex", barClass[orientation]),
|
|
5874
|
+
children: /* @__PURE__ */ jsx(ScrollArea$1.Thumb, { className: cn("relative before:absolute before:bg-ink-secondary before:content-['']", "before:transition-colors before:duration-(--duration-fast) before:ease-standard", "group-hover/bar:before:bg-ink active:before:bg-ink", "forced-colors:before:bg-[CanvasText]", thumbClass[orientation]) })
|
|
5875
|
+
});
|
|
5876
|
+
}
|
|
5877
|
+
/**
|
|
5878
|
+
* 滚动区:一块自己能滚的区域,滚动条是画出来的。高度(或高度上限)由 `className` 给。
|
|
5879
|
+
* 只有真的溢出时才有滚动条,也才是一个能聚焦的区域。
|
|
5880
|
+
*
|
|
5881
|
+
* 整页的滚动留给浏览器,不要把页面主体包进来。
|
|
5882
|
+
* `className` 与 `ref` 给外框,其余属性(`onScroll` 等)给真正在滚的可视区。
|
|
5883
|
+
*/
|
|
5884
|
+
function ScrollArea({ "aria-label": label, orientation = "vertical", viewportRef, className, style, ref, children, ...props }) {
|
|
5885
|
+
const vertical = orientation !== "horizontal";
|
|
5886
|
+
const horizontal = orientation !== "vertical";
|
|
5887
|
+
return /* @__PURE__ */ jsxs(ScrollArea$1.Root, {
|
|
5888
|
+
ref,
|
|
5889
|
+
style,
|
|
5890
|
+
"data-scroll-area": "",
|
|
5891
|
+
"data-orientation": orientation,
|
|
5892
|
+
className: cn("flex min-h-0 min-w-0 flex-col text-ink", className),
|
|
5893
|
+
children: [
|
|
5894
|
+
/* @__PURE__ */ jsx(ScrollArea$1.Viewport, {
|
|
5895
|
+
...props,
|
|
5896
|
+
ref: viewportRef,
|
|
5897
|
+
style: orientation === "vertical" ? { overflowX: "hidden" } : orientation === "horizontal" ? { overflowY: "hidden" } : void 0,
|
|
5898
|
+
className: cn("min-h-0 flex-auto", focusRingInset, vertical && "data-has-overflow-y:mr-3", horizontal && "data-has-overflow-x:mb-3"),
|
|
5899
|
+
render: (viewportProps, state) => {
|
|
5900
|
+
const scrollable = vertical && state.hasOverflowY || horizontal && state.hasOverflowX;
|
|
5901
|
+
return /* @__PURE__ */ jsx("div", {
|
|
5902
|
+
...viewportProps,
|
|
5903
|
+
...scrollable && {
|
|
5904
|
+
role: "region",
|
|
5905
|
+
"aria-label": label
|
|
5906
|
+
},
|
|
5907
|
+
tabIndex: scrollable ? 0 : -1
|
|
5908
|
+
});
|
|
5909
|
+
},
|
|
5910
|
+
children: /* @__PURE__ */ jsx(ScrollArea$1.Content, { children })
|
|
5911
|
+
}),
|
|
5912
|
+
vertical && /* @__PURE__ */ jsx(Scrollbar, { orientation: "vertical" }),
|
|
5913
|
+
horizontal && /* @__PURE__ */ jsx(Scrollbar, { orientation: "horizontal" }),
|
|
5914
|
+
orientation === "both" && /* @__PURE__ */ jsx(ScrollArea$1.Corner, {})
|
|
5915
|
+
]
|
|
5916
|
+
});
|
|
5917
|
+
}
|
|
5918
|
+
//#endregion
|
|
5346
5919
|
//#region src/components/scroll-hint/ScrollHint.tsx
|
|
5347
5920
|
/**
|
|
5348
5921
|
* 滚动提示:只在确实还能往下滚、并且还没开始滚的时候出现,滚动开始后淡出。
|
|
@@ -8040,52 +8613,6 @@ function TopBar({ brand, tools, action, menu, className, children, ...props }) {
|
|
|
8040
8613
|
});
|
|
8041
8614
|
}
|
|
8042
8615
|
//#endregion
|
|
8043
|
-
//#region src/components/viewfinder/Viewfinder.tsx
|
|
8044
|
-
const armClass = {
|
|
8045
|
-
sm: "[--bracket-arm:16px]",
|
|
8046
|
-
md: "[--bracket-arm:24px]"
|
|
8047
|
-
};
|
|
8048
|
-
const readoutPosition = {
|
|
8049
|
-
topLeft: "top-5 left-5",
|
|
8050
|
-
topRight: "top-5 right-5 text-right",
|
|
8051
|
-
bottomLeft: "bottom-5 left-5",
|
|
8052
|
-
bottomRight: "right-5 bottom-5 text-right"
|
|
8053
|
-
};
|
|
8054
|
-
/**
|
|
8055
|
-
* 取景角:框住一张图、一片地图区域、一个焦点,标的是"范围",不是选中。
|
|
8056
|
-
* 里面放被框住的内容;压在深色画面上时给它加 `data-theme="dark"`。
|
|
8057
|
-
*
|
|
8058
|
-
* 只用在展示性的画面上,一屏一处。取景角与准星是装饰,读数是真实信息、读屏读得到。
|
|
8059
|
-
*/
|
|
8060
|
-
function Viewfinder({ size = "sm", crosshair = false, readouts, className, children, ...props }) {
|
|
8061
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
8062
|
-
...props,
|
|
8063
|
-
className: cn("relative text-ink", className),
|
|
8064
|
-
children: [
|
|
8065
|
-
children,
|
|
8066
|
-
/* @__PURE__ */ jsx("span", {
|
|
8067
|
-
"aria-hidden": "true",
|
|
8068
|
-
className: cn(decor, "corner-brackets absolute inset-3 [--bracket-color:currentColor] [--bracket-offset:0px] [--bracket-width:1.5px]", armClass[size])
|
|
8069
|
-
}),
|
|
8070
|
-
crosshair && /* @__PURE__ */ jsx("svg", {
|
|
8071
|
-
"aria-hidden": "true",
|
|
8072
|
-
focusable: "false",
|
|
8073
|
-
viewBox: "0 0 12 12",
|
|
8074
|
-
fill: "none",
|
|
8075
|
-
stroke: "currentColor",
|
|
8076
|
-
strokeWidth: 1.5,
|
|
8077
|
-
className: cn("pointer-events-none select-none forced-colors:hidden print:hidden", "absolute top-1/2 left-1/2 size-3 -translate-1/2 text-accent-ink"),
|
|
8078
|
-
children: /* @__PURE__ */ jsx("path", { d: "M6 0v12M0 6h12" })
|
|
8079
|
-
}),
|
|
8080
|
-
readouts && Object.keys(readoutPosition).map((corner) => readouts[corner] !== void 0 && /* @__PURE__ */ jsx("span", {
|
|
8081
|
-
"data-corner": corner,
|
|
8082
|
-
className: cn("pointer-events-none absolute font-tech text-xs leading-none text-ink-secondary tabular-nums", readoutPosition[corner]),
|
|
8083
|
-
children: readouts[corner]
|
|
8084
|
-
}, corner))
|
|
8085
|
-
]
|
|
8086
|
-
});
|
|
8087
|
-
}
|
|
8088
|
-
//#endregion
|
|
8089
8616
|
//#region src/hooks/useTheme.ts
|
|
8090
8617
|
const STORAGE_KEY = "ef-theme";
|
|
8091
8618
|
const DARK_QUERY = "(prefers-color-scheme: dark)";
|
|
@@ -8172,11 +8699,8 @@ function useTheme() {
|
|
|
8172
8699
|
};
|
|
8173
8700
|
}
|
|
8174
8701
|
//#endregion
|
|
8175
|
-
//#region src/icons/ArrowLeft.tsx
|
|
8176
|
-
const ArrowLeft = createIcon("ArrowLeft", /* @__PURE__ */ jsx("path", { d: "M20 12H5M11 5l-7 7 7 7" }));
|
|
8177
|
-
//#endregion
|
|
8178
8702
|
//#region src/icons/Menu.tsx
|
|
8179
8703
|
/** 三条横线:打开菜单 */
|
|
8180
8704
|
const Menu = createIcon("Menu", /* @__PURE__ */ jsx("path", { d: "M3 6h18M3 12h18M3 18h18" }));
|
|
8181
8705
|
//#endregion
|
|
8182
|
-
export { Accordion, AccordionItem, Alert, ArrowCorner, ArrowLeft, ArrowRight, Avatar, AvatarSwitcher, AvatarSwitcherItem, BackToTop, Badge, BracketTitle, Breadcrumb, BreadcrumbItem, Button, ButtonGroup, Calendar, CalendarIcon, Carousel, CarouselSlide, Check, Checkbox, ChevronDown, ChevronLeft, ChevronRight, Close, Combobox, CompletionBanner, ContextMenu, CornerBrackets, Countdown, Crosshair, DashIndicator, DataRow, DataRowList, DatePicker, DateRangePicker, Dialog, DialogClose, Drawer, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuGroup, DropdownMenuItem, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuSub, EmptyState, Field, FileItem, FileUpload, FilterChip, FlyoutBar, FlyoutBarItem, GhostText, Hatch, HazardStripe, IconButton, Input, ItemGrid, ItemSlot, Kbd, List, ListRow, Loader, Lock, Marquee, MediaCard, Menu, Minus, NavAction, NavMenu, NavMenuItem, Navigator, Pagination, Panel, PanelBody, PanelHeader, PanelRow, PanelRows, PlayButton, PlayMark, Plus, Popover, PopoverClose, Progress, ProgressRing, Radio, RadioGroup, RecIndicator, RegistrationStrip, ResourceChip, Schedule, ScheduleItem, ScheduleTrack, ScrollHint, SectionTitle, Segment, SegmentedControl, Select, SelectGroup, SelectItem, SelectSeparator, SideRail, SideRailGroup, SideRailItem, SideRailSub, SideRailSubItem, Skeleton, Slider, Sparkline, Spinner, Stat, StatusDanger, StatusInfo, StatusSuccess, StatusWarning, Step, Stepper, Steps, Switch, Tab, TabList, TabPanel, Table, TableBody, TableCell, TableExpander, TableHead, TableHeaderCell, TableRow, Tabs, Tag, TagInput, TagPair, Term, Textarea, Texture, TickRing, Timeline, TimelineItem, ToastProvider, Toc, TocItem, Toolbar, ToolbarButton, ToolbarGroup, ToolbarSeparator, ToolbarToggle, ToolbarToggleGroup, Tooltip, TooltipProvider, TopBar, TriangleRight, Upload, Viewfinder, cn, createIcon, useControllableState, useFieldContext, useFieldControl, useInView, usePortalScope, useTheme, useToast };
|
|
8706
|
+
export { Accordion, AccordionItem, Alert, ArrowCorner, ArrowLeft, ArrowRight, Avatar, AvatarSwitcher, AvatarSwitcherItem, BackToTop, Badge, BracketTitle, Breadcrumb, BreadcrumbItem, Button, ButtonGroup, Calendar, CalendarIcon, Carousel, CarouselSlide, Check, Checkbox, ChevronDown, ChevronLeft, ChevronRight, Close, Combobox, CompletionBanner, ContextMenu, CornerBrackets, Countdown, Crosshair, DashIndicator, DataRow, DataRowList, DatePicker, DateRangePicker, Dialog, DialogClose, Drawer, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuGroup, DropdownMenuItem, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuSub, EmptyState, Field, FileItem, FileUpload, FilterChip, FlyoutBar, FlyoutBarItem, GhostText, Hatch, HazardStripe, HoverCard, IconButton, ImageViewer, ImageViewerItem, Input, ItemGrid, ItemSlot, Kbd, List, ListRow, Loader, Lock, Marquee, MediaCard, Menu, Minus, NavAction, NavMenu, NavMenuItem, Navigator, OtpInput, PageHeader, PageHeaderBack, Pagination, Panel, PanelBody, PanelHeader, PanelRow, PanelRows, PlayButton, PlayMark, Plus, Popover, PopoverClose, Progress, ProgressRing, Radio, RadioGroup, RecIndicator, RegistrationStrip, ResourceChip, Schedule, ScheduleItem, ScheduleTrack, ScrollArea, ScrollHint, SectionTitle, Segment, SegmentedControl, Select, SelectGroup, SelectItem, SelectSeparator, SideRail, SideRailGroup, SideRailItem, SideRailSub, SideRailSubItem, Skeleton, Slider, Sparkline, Spinner, Stat, StatusDanger, StatusInfo, StatusSuccess, StatusWarning, Step, Stepper, Steps, Switch, Tab, TabList, TabPanel, Table, TableBody, TableCell, TableExpander, TableHead, TableHeaderCell, TableRow, Tabs, Tag, TagInput, TagPair, Term, Textarea, Texture, TickRing, Timeline, TimelineItem, ToastProvider, Toc, TocItem, Toolbar, ToolbarButton, ToolbarGroup, ToolbarSeparator, ToolbarToggle, ToolbarToggleGroup, Tooltip, TooltipProvider, TopBar, TriangleRight, Upload, Viewfinder, cn, createIcon, useControllableState, useFieldContext, useFieldControl, useInView, usePortalScope, useTheme, useToast };
|