@endfield-ui/react 0.0.0-stage → 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 -0
- package/NOTICE.md +27 -0
- package/README.md +818 -2
- package/dist/index.d.ts +3445 -0
- package/dist/index.js +8706 -0
- package/dist/styles.css +2 -0
- package/package.json +83 -3
- package/src/components/README.md +179 -0
- package/src/components/accordion/Accordion.tsx +168 -0
- package/src/components/accordion/index.ts +8 -0
- package/src/components/alert/Alert.tsx +106 -0
- package/src/components/alert/index.ts +1 -0
- package/src/components/avatar/Avatar.tsx +92 -0
- package/src/components/avatar/AvatarSwitcher.tsx +261 -0
- package/src/components/avatar/index.ts +8 -0
- package/src/components/back-to-top/BackToTop.tsx +124 -0
- package/src/components/back-to-top/index.ts +5 -0
- package/src/components/badge/Badge.tsx +75 -0
- package/src/components/badge/index.ts +1 -0
- package/src/components/bracket-title/BracketTitle.tsx +40 -0
- package/src/components/bracket-title/index.ts +1 -0
- package/src/components/breadcrumb/Breadcrumb.tsx +161 -0
- package/src/components/breadcrumb/index.ts +6 -0
- package/src/components/button/Button.tsx +357 -0
- package/src/components/button/ButtonGroup.tsx +55 -0
- package/src/components/button/index.ts +13 -0
- package/src/components/calendar/Calendar.tsx +443 -0
- package/src/components/calendar/index.ts +7 -0
- package/src/components/carousel/Carousel.tsx +252 -0
- package/src/components/carousel/index.ts +7 -0
- package/src/components/carousel/snap-track.ts +74 -0
- package/src/components/checkbox/Checkbox.tsx +94 -0
- package/src/components/checkbox/choice-style.ts +30 -0
- package/src/components/checkbox/index.ts +1 -0
- package/src/components/chip/FilterChip.tsx +68 -0
- package/src/components/chip/ResourceChip.tsx +63 -0
- package/src/components/chip/capsule-style.ts +22 -0
- package/src/components/chip/index.ts +6 -0
- package/src/components/combobox/Combobox.tsx +450 -0
- package/src/components/combobox/index.ts +10 -0
- package/src/components/completion-banner/CompletionBanner.tsx +72 -0
- package/src/components/completion-banner/index.ts +4 -0
- package/src/components/context-menu/ContextMenu.tsx +70 -0
- package/src/components/context-menu/index.ts +5 -0
- package/src/components/corner-brackets/CornerBrackets.tsx +43 -0
- package/src/components/corner-brackets/index.ts +5 -0
- package/src/components/countdown/Countdown.tsx +145 -0
- package/src/components/countdown/index.ts +6 -0
- package/src/components/dash-indicator/DashIndicator.tsx +44 -0
- package/src/components/dash-indicator/index.ts +1 -0
- package/src/components/data-row/DataRow.tsx +282 -0
- package/src/components/data-row/index.ts +8 -0
- package/src/components/date-picker/DateField.tsx +159 -0
- package/src/components/date-picker/DatePicker.tsx +182 -0
- package/src/components/date-picker/DateRangePicker.tsx +203 -0
- package/src/components/date-picker/index.ts +7 -0
- package/src/components/dialog/Dialog.tsx +203 -0
- package/src/components/dialog/index.ts +7 -0
- package/src/components/dialog/initial-focus.ts +19 -0
- package/src/components/dialog/overlay-style.ts +28 -0
- package/src/components/drawer/Drawer.tsx +213 -0
- package/src/components/drawer/index.ts +6 -0
- package/src/components/dropdown-menu/DropdownMenu.tsx +427 -0
- package/src/components/dropdown-menu/MenuCheck.tsx +44 -0
- package/src/components/dropdown-menu/index.ts +22 -0
- package/src/components/dropdown-menu/menu-style.ts +47 -0
- package/src/components/empty-state/EmptyState.tsx +79 -0
- package/src/components/empty-state/index.ts +1 -0
- package/src/components/field/Field.tsx +203 -0
- package/src/components/field/index.ts +6 -0
- package/src/components/file-upload/FileItem.tsx +201 -0
- package/src/components/file-upload/FileUpload.tsx +425 -0
- package/src/components/file-upload/index.ts +9 -0
- package/src/components/flyout-bar/FlyoutBar.tsx +158 -0
- package/src/components/flyout-bar/index.ts +7 -0
- package/src/components/ghost-text/GhostText.tsx +47 -0
- package/src/components/ghost-text/index.ts +5 -0
- package/src/components/hatch/Hatch.tsx +35 -0
- package/src/components/hatch/index.ts +1 -0
- package/src/components/hazard-stripe/HazardStripe.tsx +35 -0
- package/src/components/hazard-stripe/index.ts +5 -0
- package/src/components/hover-card/HoverCard.tsx +101 -0
- package/src/components/hover-card/index.ts +6 -0
- package/src/components/icon-button/IconButton.tsx +95 -0
- package/src/components/icon-button/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/input/Input.tsx +109 -0
- package/src/components/input/control-box.ts +71 -0
- package/src/components/input/index.ts +6 -0
- package/src/components/input/value-chip.ts +34 -0
- package/src/components/item-slot/ItemGrid.tsx +207 -0
- package/src/components/item-slot/ItemSlot.tsx +284 -0
- package/src/components/item-slot/index.ts +7 -0
- package/src/components/kbd/Kbd.tsx +33 -0
- package/src/components/kbd/index.ts +1 -0
- package/src/components/link-element/LinkElement.tsx +25 -0
- package/src/components/list/List.tsx +229 -0
- package/src/components/list/band-style.ts +18 -0
- package/src/components/list/index.ts +8 -0
- package/src/components/loader/Loader.tsx +154 -0
- package/src/components/loader/index.ts +1 -0
- package/src/components/marquee/Marquee.tsx +96 -0
- package/src/components/marquee/index.ts +1 -0
- package/src/components/media-card/MediaCard.tsx +139 -0
- package/src/components/media-card/index.ts +5 -0
- package/src/components/nav-action/NavAction.tsx +142 -0
- package/src/components/nav-action/index.ts +5 -0
- package/src/components/nav-menu/NavMenu.tsx +288 -0
- package/src/components/nav-menu/index.ts +6 -0
- package/src/components/navigator/Navigator.tsx +136 -0
- package/src/components/navigator/index.ts +5 -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/pagination/Pagination.tsx +181 -0
- package/src/components/pagination/index.ts +5 -0
- package/src/components/panel/Panel.tsx +114 -0
- package/src/components/panel/index.ts +13 -0
- package/src/components/play-button/PlayButton.tsx +92 -0
- package/src/components/play-button/index.ts +7 -0
- package/src/components/popover/Popover.tsx +97 -0
- package/src/components/popover/index.ts +8 -0
- package/src/components/popover/panel-style.ts +15 -0
- package/src/components/progress/Progress.tsx +133 -0
- package/src/components/progress/ProgressRing.tsx +123 -0
- package/src/components/progress/index.ts +2 -0
- package/src/components/radio/Radio.tsx +182 -0
- package/src/components/radio/index.ts +7 -0
- package/src/components/rec-indicator/RecIndicator.tsx +47 -0
- package/src/components/rec-indicator/index.ts +1 -0
- package/src/components/registration-strip/RegistrationStrip.tsx +51 -0
- package/src/components/registration-strip/index.ts +5 -0
- package/src/components/schedule/Schedule.tsx +457 -0
- package/src/components/schedule/index.ts +9 -0
- package/src/components/scroll-area/ScrollArea.tsx +137 -0
- package/src/components/scroll-area/index.ts +5 -0
- package/src/components/scroll-hint/ScrollHint.tsx +78 -0
- package/src/components/scroll-hint/index.ts +5 -0
- package/src/components/section-title/SectionTitle.tsx +202 -0
- package/src/components/section-title/index.ts +5 -0
- package/src/components/segmented-control/SegmentedControl.tsx +220 -0
- package/src/components/segmented-control/index.ts +8 -0
- package/src/components/select/Select.tsx +390 -0
- package/src/components/select/index.ts +15 -0
- package/src/components/side-rail/SideRail.tsx +317 -0
- package/src/components/side-rail/SideRailSub.tsx +298 -0
- package/src/components/side-rail/index.ts +14 -0
- package/src/components/skeleton/Skeleton.tsx +70 -0
- package/src/components/skeleton/index.ts +1 -0
- package/src/components/slider/Slider.tsx +219 -0
- package/src/components/slider/index.ts +8 -0
- package/src/components/sparkline/Sparkline.tsx +122 -0
- package/src/components/sparkline/index.ts +6 -0
- package/src/components/spinner/Spinner.tsx +64 -0
- package/src/components/spinner/index.ts +1 -0
- package/src/components/stat/Stat.tsx +79 -0
- package/src/components/stat/index.ts +1 -0
- package/src/components/stepper/Stepper.tsx +316 -0
- package/src/components/stepper/index.ts +1 -0
- package/src/components/steps/Steps.tsx +302 -0
- package/src/components/steps/index.ts +10 -0
- package/src/components/switch/Switch.tsx +120 -0
- package/src/components/switch/index.ts +1 -0
- package/src/components/table/Table.tsx +560 -0
- package/src/components/table/index.ts +20 -0
- package/src/components/tabs/Tabs.tsx +313 -0
- package/src/components/tabs/index.ts +12 -0
- package/src/components/tag/Tag.tsx +66 -0
- package/src/components/tag/TagPair.tsx +34 -0
- package/src/components/tag/index.ts +2 -0
- package/src/components/tag-input/TagInput.tsx +502 -0
- package/src/components/tag-input/index.ts +8 -0
- package/src/components/term/Term.tsx +53 -0
- package/src/components/term/index.ts +1 -0
- package/src/components/textarea/Textarea.tsx +99 -0
- package/src/components/textarea/index.ts +1 -0
- package/src/components/texture/Texture.tsx +45 -0
- package/src/components/texture/index.ts +1 -0
- package/src/components/tick-ring/TickRing.tsx +77 -0
- package/src/components/tick-ring/index.ts +1 -0
- package/src/components/timeline/Timeline.tsx +84 -0
- package/src/components/timeline/index.ts +7 -0
- package/src/components/toast/Toast.tsx +257 -0
- package/src/components/toast/index.ts +10 -0
- package/src/components/toc/Toc.tsx +263 -0
- package/src/components/toc/index.ts +7 -0
- package/src/components/toolbar/Toolbar.tsx +389 -0
- package/src/components/toolbar/index.ts +17 -0
- package/src/components/tooltip/Tooltip.tsx +175 -0
- package/src/components/tooltip/index.ts +8 -0
- package/src/components/top-bar/TopBar.tsx +47 -0
- package/src/components/top-bar/index.ts +1 -0
- package/src/components/viewfinder/Viewfinder.tsx +97 -0
- package/src/components/viewfinder/index.ts +6 -0
- package/src/hooks/README.md +14 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/useControllableState.ts +30 -0
- package/src/hooks/useInView.ts +44 -0
- package/src/hooks/useOverflowing.ts +28 -0
- package/src/hooks/usePortalScope.ts +68 -0
- package/src/hooks/useScrollPosition.ts +57 -0
- package/src/hooks/useTheme.ts +102 -0
- package/src/icons/ArrowCorner.tsx +7 -0
- package/src/icons/ArrowLeft.tsx +6 -0
- package/src/icons/ArrowRight.tsx +6 -0
- package/src/icons/CalendarIcon.tsx +10 -0
- package/src/icons/Check.tsx +4 -0
- package/src/icons/ChevronDown.tsx +3 -0
- package/src/icons/ChevronLeft.tsx +6 -0
- package/src/icons/ChevronRight.tsx +6 -0
- package/src/icons/Close.tsx +3 -0
- package/src/icons/Crosshair.tsx +7 -0
- package/src/icons/Lock.tsx +6 -0
- package/src/icons/Menu.tsx +4 -0
- package/src/icons/Minus.tsx +4 -0
- package/src/icons/Plus.tsx +3 -0
- package/src/icons/README.md +31 -0
- package/src/icons/StatusDanger.tsx +7 -0
- package/src/icons/StatusInfo.tsx +7 -0
- package/src/icons/StatusSuccess.tsx +10 -0
- package/src/icons/StatusWarning.tsx +7 -0
- package/src/icons/TriangleRight.tsx +7 -0
- package/src/icons/Upload.tsx +7 -0
- package/src/icons/createIcon.tsx +34 -0
- package/src/icons/index.ts +21 -0
- package/src/index.ts +87 -0
- package/src/lib/README.md +17 -0
- package/src/lib/cn.ts +34 -0
- package/src/lib/date.ts +125 -0
- package/src/lib/decor.ts +6 -0
- package/src/lib/defined.ts +9 -0
- package/src/lib/file.ts +61 -0
- package/src/lib/focus-ring.ts +11 -0
- package/src/lib/merge-refs.ts +11 -0
- package/src/styles/README.md +70 -0
- package/src/styles/index.css +25 -0
- package/src/styles/theme.css +568 -0
- package/src/styles/utilities.css +241 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
createContext,
|
|
4
|
+
useCallback,
|
|
5
|
+
useContext,
|
|
6
|
+
useEffect,
|
|
7
|
+
useId,
|
|
8
|
+
useMemo,
|
|
9
|
+
useRef,
|
|
10
|
+
useState,
|
|
11
|
+
type ComponentProps,
|
|
12
|
+
type MouseEvent,
|
|
13
|
+
type ReactNode,
|
|
14
|
+
type RefObject,
|
|
15
|
+
} from "react";
|
|
16
|
+
import {
|
|
17
|
+
useScrollPosition,
|
|
18
|
+
type ScrollMetrics,
|
|
19
|
+
} from "../../hooks/useScrollPosition";
|
|
20
|
+
import { cn } from "../../lib/cn";
|
|
21
|
+
import { focusRingInset } from "../../lib/focus-ring";
|
|
22
|
+
import { mergeRefs } from "../../lib/merge-refs";
|
|
23
|
+
import { LinkElement, type LinkRender } from "../link-element/LinkElement";
|
|
24
|
+
|
|
25
|
+
export type TocLevel = 1 | 2 | 3;
|
|
26
|
+
|
|
27
|
+
const TocContext = createContext<{
|
|
28
|
+
active: string | null;
|
|
29
|
+
pick: (section: string) => void;
|
|
30
|
+
}>({ active: null, pick: () => {} });
|
|
31
|
+
|
|
32
|
+
type TocOwnProps = {
|
|
33
|
+
/** 列表上面的一行小标。有它时目录以它命名 */
|
|
34
|
+
title?: ReactNode;
|
|
35
|
+
/**
|
|
36
|
+
* 视口上沿往下让多少像素才算"到了"。页面有吸顶的页头时传页头的高度,
|
|
37
|
+
* 并给各节的标题加同样大小的 `scroll-mt-*`
|
|
38
|
+
*/
|
|
39
|
+
offset?: number;
|
|
40
|
+
/** 内容在哪个滚动容器里。不传就是整个页面 */
|
|
41
|
+
target?: RefObject<HTMLElement | null>;
|
|
42
|
+
/** 当前的小节变了:拿到它的 `id`,一节都还没到时是 `null` */
|
|
43
|
+
onActiveChange?: (section: string | null) => void;
|
|
44
|
+
/** `TocItem` */
|
|
45
|
+
children: ReactNode;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type TocProps = TocOwnProps &
|
|
49
|
+
Omit<ComponentProps<"nav">, keyof TocOwnProps>;
|
|
50
|
+
|
|
51
|
+
/** 点了一项之后,多久没有滚动事件算这一次滚动停下了 */
|
|
52
|
+
const SETTLE_MS = 150;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 页内目录:这一页有哪几节、现在看到了哪一节。里面放 `TocItem`。
|
|
56
|
+
*
|
|
57
|
+
* 位置由使用方给,通常是 `className="sticky top-6"`。点一项就是点一个锚点链接:
|
|
58
|
+
* 滚不滚、平不平滑是浏览器和页面 CSS 的事,它不接管滚动。
|
|
59
|
+
*/
|
|
60
|
+
export function Toc({
|
|
61
|
+
title,
|
|
62
|
+
offset = 0,
|
|
63
|
+
target,
|
|
64
|
+
onActiveChange,
|
|
65
|
+
"aria-label": ariaLabel,
|
|
66
|
+
className,
|
|
67
|
+
ref,
|
|
68
|
+
children,
|
|
69
|
+
...props
|
|
70
|
+
}: TocProps) {
|
|
71
|
+
const navRef = useRef<HTMLElement>(null);
|
|
72
|
+
const titleId = useId();
|
|
73
|
+
// 目录的项数变了(内容是后来才到的)要重新算一次,不等下一次滚动
|
|
74
|
+
const count = Children.count(children);
|
|
75
|
+
|
|
76
|
+
// 哪一节是当前的:最后一个顶边已经到了上沿的;滚到底时是最后一节
|
|
77
|
+
const read = useCallback(
|
|
78
|
+
({ scrolled, room }: ScrollMetrics) => {
|
|
79
|
+
const nav = navRef.current;
|
|
80
|
+
if (!nav) return null;
|
|
81
|
+
const container = target?.current;
|
|
82
|
+
const top = container ? container.getBoundingClientRect().top : 0;
|
|
83
|
+
|
|
84
|
+
let active: string | null = null;
|
|
85
|
+
let last: string | null = null;
|
|
86
|
+
for (const link of nav.querySelectorAll<HTMLElement>(
|
|
87
|
+
"[data-toc-section]",
|
|
88
|
+
)) {
|
|
89
|
+
const id = link.dataset.tocSection!;
|
|
90
|
+
const section = document.getElementById(id);
|
|
91
|
+
if (!section) continue;
|
|
92
|
+
last = id;
|
|
93
|
+
if (section.getBoundingClientRect().top - top <= offset + 1) {
|
|
94
|
+
active = id;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// 最后一节往往很短,顶边永远到不了上沿
|
|
98
|
+
if (room > 0 && scrolled >= room - 1) return last;
|
|
99
|
+
return active;
|
|
100
|
+
},
|
|
101
|
+
// count 没在里面用到:它变了这个函数就换一个,滚动的监听跟着重新接、重新算一次
|
|
102
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
103
|
+
[target, offset, count],
|
|
104
|
+
);
|
|
105
|
+
const spied = useScrollPosition<string | null>(target, read, null);
|
|
106
|
+
|
|
107
|
+
// 点了一项:它立刻是当前项,保持到这一次滚动停下、使用者自己再滚为止。
|
|
108
|
+
// 否则点倒数第二节、它到不了上沿,亮起来的会是别的
|
|
109
|
+
const [pinned, setPinned] = useState<string | null>(null);
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (pinned === null) return;
|
|
112
|
+
const source = target?.current ?? window;
|
|
113
|
+
let settled = false;
|
|
114
|
+
let timer = setTimeout(() => {
|
|
115
|
+
settled = true;
|
|
116
|
+
}, SETTLE_MS);
|
|
117
|
+
const onScroll = () => {
|
|
118
|
+
if (settled) {
|
|
119
|
+
setPinned(null);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
clearTimeout(timer);
|
|
123
|
+
timer = setTimeout(() => {
|
|
124
|
+
settled = true;
|
|
125
|
+
}, SETTLE_MS);
|
|
126
|
+
};
|
|
127
|
+
source.addEventListener("scroll", onScroll, { passive: true });
|
|
128
|
+
return () => {
|
|
129
|
+
clearTimeout(timer);
|
|
130
|
+
source.removeEventListener("scroll", onScroll);
|
|
131
|
+
};
|
|
132
|
+
}, [pinned, target]);
|
|
133
|
+
|
|
134
|
+
const active = pinned ?? spied;
|
|
135
|
+
|
|
136
|
+
const report = useRef(onActiveChange);
|
|
137
|
+
report.current = onActiveChange;
|
|
138
|
+
const reported = useRef<string | null>(null);
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
if (reported.current === active) return;
|
|
141
|
+
reported.current = active;
|
|
142
|
+
report.current?.(active);
|
|
143
|
+
}, [active]);
|
|
144
|
+
|
|
145
|
+
// 目录自己能滚的时候(使用方给了高度上限),当前项保持在看得见的范围里。
|
|
146
|
+
// 只动目录自己的滚动位置,不碰页面
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
const nav = navRef.current;
|
|
149
|
+
if (!nav || active === null) return;
|
|
150
|
+
if (nav.scrollHeight <= nav.clientHeight + 1) return;
|
|
151
|
+
const link = nav.querySelector<HTMLElement>("[aria-current]");
|
|
152
|
+
if (!link) return;
|
|
153
|
+
const box = nav.getBoundingClientRect();
|
|
154
|
+
const rect = link.getBoundingClientRect();
|
|
155
|
+
if (rect.top < box.top) nav.scrollTop -= box.top - rect.top + 8;
|
|
156
|
+
else if (rect.bottom > box.bottom) {
|
|
157
|
+
nav.scrollTop += rect.bottom - box.bottom + 8;
|
|
158
|
+
}
|
|
159
|
+
}, [active]);
|
|
160
|
+
|
|
161
|
+
const context = useMemo(() => ({ active, pick: setPinned }), [active]);
|
|
162
|
+
const hasTitle = title !== undefined && title !== null && title !== false;
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<nav
|
|
166
|
+
{...props}
|
|
167
|
+
ref={mergeRefs(navRef, ref)}
|
|
168
|
+
aria-label={ariaLabel ?? (hasTitle ? undefined : "本页目录")}
|
|
169
|
+
aria-labelledby={
|
|
170
|
+
ariaLabel === undefined && hasTitle ? titleId : undefined
|
|
171
|
+
}
|
|
172
|
+
className={cn("text-sm text-ink", className)}
|
|
173
|
+
>
|
|
174
|
+
{hasTitle && (
|
|
175
|
+
<p
|
|
176
|
+
id={titleId}
|
|
177
|
+
className="mb-2 pl-4 font-tech text-xs text-ink-secondary"
|
|
178
|
+
>
|
|
179
|
+
{title}
|
|
180
|
+
</p>
|
|
181
|
+
)}
|
|
182
|
+
{/* 引线:从头贯到尾的一条细线,当前项那一段由那一项自己换成粗条 */}
|
|
183
|
+
<ul className="relative before:absolute before:inset-y-0 before:left-0 before:w-px before:bg-line before:content-['']">
|
|
184
|
+
<TocContext value={context}>{children}</TocContext>
|
|
185
|
+
</ul>
|
|
186
|
+
</nav>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
type TocItemOwnProps = {
|
|
191
|
+
/** `#小节的 id`。小节就是按 `#` 后面这一段找的 */
|
|
192
|
+
href?: string;
|
|
193
|
+
/** 小节的 `id`。地址在 `render` 那个元素手里时用它 */
|
|
194
|
+
section?: string;
|
|
195
|
+
/** 第几级,只影响缩进。默认 1 */
|
|
196
|
+
level?: TocLevel;
|
|
197
|
+
/** 用这个元素代替 `<a>`(路由库的链接组件) */
|
|
198
|
+
render?: LinkRender;
|
|
199
|
+
onClick?: (event: MouseEvent<HTMLElement>) => void;
|
|
200
|
+
children: ReactNode;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
export type TocItemProps = TocItemOwnProps &
|
|
204
|
+
Omit<ComponentProps<"li">, keyof TocItemOwnProps>;
|
|
205
|
+
|
|
206
|
+
const levelPadding: Record<TocLevel, string> = {
|
|
207
|
+
1: "pl-4",
|
|
208
|
+
2: "pl-7",
|
|
209
|
+
3: "pl-10",
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const sectionOf = (href: string | undefined) => {
|
|
213
|
+
const hash = href?.split("#")[1];
|
|
214
|
+
if (!hash) return undefined;
|
|
215
|
+
try {
|
|
216
|
+
return decodeURIComponent(hash);
|
|
217
|
+
} catch {
|
|
218
|
+
return hash;
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
/** 目录里的一项:一个指向小节的锚点链接。 */
|
|
223
|
+
export function TocItem({
|
|
224
|
+
href,
|
|
225
|
+
section = sectionOf(href),
|
|
226
|
+
level = 1,
|
|
227
|
+
render,
|
|
228
|
+
onClick,
|
|
229
|
+
children,
|
|
230
|
+
...props
|
|
231
|
+
}: TocItemProps) {
|
|
232
|
+
const { active, pick } = useContext(TocContext);
|
|
233
|
+
const current = section !== undefined && section === active;
|
|
234
|
+
|
|
235
|
+
return (
|
|
236
|
+
<li {...props}>
|
|
237
|
+
<LinkElement
|
|
238
|
+
render={render}
|
|
239
|
+
href={href}
|
|
240
|
+
data-toc-section={section}
|
|
241
|
+
data-level={level}
|
|
242
|
+
aria-current={current ? "location" : undefined}
|
|
243
|
+
onClick={(event) => {
|
|
244
|
+
onClick?.(event);
|
|
245
|
+
if (!event.defaultPrevented && section !== undefined) pick(section);
|
|
246
|
+
}}
|
|
247
|
+
className={cn(
|
|
248
|
+
"relative block py-2 pr-2 leading-snug wrap-anywhere",
|
|
249
|
+
"transition-colors duration-(--duration-fast) ease-standard",
|
|
250
|
+
// 目录自己可能在一个会滚动的容器里:环画在里面
|
|
251
|
+
focusRingInset,
|
|
252
|
+
levelPadding[level],
|
|
253
|
+
current
|
|
254
|
+
? // 不加粗:加粗会让折行变掉,整列目录跟着跳。靠的是引线上的这一段粗条
|
|
255
|
+
"text-ink before:absolute before:inset-y-1 before:left-0 before:w-[3px] before:bg-ink before:content-['']"
|
|
256
|
+
: "text-ink-secondary hover:text-ink",
|
|
257
|
+
)}
|
|
258
|
+
>
|
|
259
|
+
{children}
|
|
260
|
+
</LinkElement>
|
|
261
|
+
</li>
|
|
262
|
+
);
|
|
263
|
+
}
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import { Toggle as BaseToggle } from "@base-ui/react/toggle";
|
|
2
|
+
import { ToggleGroup as BaseToggleGroup } from "@base-ui/react/toggle-group";
|
|
3
|
+
import { Toolbar as BaseToolbar } from "@base-ui/react/toolbar";
|
|
4
|
+
import {
|
|
5
|
+
createContext,
|
|
6
|
+
useContext,
|
|
7
|
+
useMemo,
|
|
8
|
+
type ComponentProps,
|
|
9
|
+
type KeyboardEvent,
|
|
10
|
+
type MouseEvent,
|
|
11
|
+
type ReactNode,
|
|
12
|
+
} from "react";
|
|
13
|
+
import { cn } from "../../lib/cn";
|
|
14
|
+
import { focusRing } from "../../lib/focus-ring";
|
|
15
|
+
import type { LinkRender } from "../link-element/LinkElement";
|
|
16
|
+
|
|
17
|
+
export type ToolbarSize = "sm" | "md";
|
|
18
|
+
export type ToolbarVariant = "sunken" | "outline";
|
|
19
|
+
export type ToolbarOrientation = "horizontal" | "vertical";
|
|
20
|
+
|
|
21
|
+
const ToolbarContext = createContext<{
|
|
22
|
+
size: ToolbarSize;
|
|
23
|
+
orientation: ToolbarOrientation;
|
|
24
|
+
}>({ size: "md", orientation: "horizontal" });
|
|
25
|
+
|
|
26
|
+
export type ToolbarProps = Omit<
|
|
27
|
+
ComponentProps<"div">,
|
|
28
|
+
"aria-label" | "style"
|
|
29
|
+
> & {
|
|
30
|
+
/** 这条工具栏管的是什么:"表格工具" */
|
|
31
|
+
"aria-label": string;
|
|
32
|
+
/** 带子高 32 / 40px(里面的钮 28 / 36px),默认 `md`。触屏上用 `md` */
|
|
33
|
+
size?: ToolbarSize;
|
|
34
|
+
/**
|
|
35
|
+
* - `sunken` 凹陷的底,默认;
|
|
36
|
+
* - `outline` 四边描边,放在凹陷底色的区域里时用。
|
|
37
|
+
*/
|
|
38
|
+
variant?: ToolbarVariant;
|
|
39
|
+
/** 竖排时方向键换成上下 */
|
|
40
|
+
orientation?: ToolbarOrientation;
|
|
41
|
+
/** 整条禁用 */
|
|
42
|
+
disabled?: boolean;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/* 带子的总高和输入框同档:2px 的内边距(描边时是 1px 的边 + 1px)加里面的钮 */
|
|
46
|
+
const barVariant: Record<ToolbarVariant, string> = {
|
|
47
|
+
sunken: "bg-surface-sunken p-0.5",
|
|
48
|
+
outline: "border border-line-strong bg-surface p-px",
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 工具栏:一排作用于同一个对象的小工具,收在一条带子里。整条只占一个 Tab 停靠点,
|
|
53
|
+
* 方向键在各项之间走。里面放 `ToolbarButton`、`ToolbarToggle`、`ToolbarToggleGroup`、
|
|
54
|
+
* `ToolbarGroup`、`ToolbarSeparator`。
|
|
55
|
+
*
|
|
56
|
+
* 不在里面放输入框:左右方向键在输入框里是移光标。
|
|
57
|
+
*/
|
|
58
|
+
export function Toolbar({
|
|
59
|
+
size = "md",
|
|
60
|
+
variant = "sunken",
|
|
61
|
+
orientation = "horizontal",
|
|
62
|
+
disabled = false,
|
|
63
|
+
className,
|
|
64
|
+
onKeyDown,
|
|
65
|
+
...props
|
|
66
|
+
}: ToolbarProps) {
|
|
67
|
+
const context = useMemo(() => ({ size, orientation }), [size, orientation]);
|
|
68
|
+
|
|
69
|
+
// 基元只管方向键,Home / End 在这里补上:到第一个、最后一个
|
|
70
|
+
const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
|
|
71
|
+
// 禁用的钮会把按键拦下(除了 Tab),到这里已经是"被拦下的"了:
|
|
72
|
+
// 只认使用方在这一层自己拦的
|
|
73
|
+
const already = event.defaultPrevented;
|
|
74
|
+
onKeyDown?.(event);
|
|
75
|
+
if (!already && event.defaultPrevented) return;
|
|
76
|
+
if (event.key !== "Home" && event.key !== "End") return;
|
|
77
|
+
// 工具栏里的每一项都带 tabindex(当前那个是 0,其余是 -1)
|
|
78
|
+
const items = event.currentTarget.querySelectorAll<HTMLElement>(
|
|
79
|
+
"button[tabindex], a[tabindex]",
|
|
80
|
+
);
|
|
81
|
+
const target = event.key === "Home" ? items[0] : items[items.length - 1];
|
|
82
|
+
if (!target) return;
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
target.focus();
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<ToolbarContext value={context}>
|
|
89
|
+
<BaseToolbar.Root
|
|
90
|
+
{...props}
|
|
91
|
+
orientation={orientation}
|
|
92
|
+
disabled={disabled}
|
|
93
|
+
onKeyDown={handleKeyDown}
|
|
94
|
+
data-size={size}
|
|
95
|
+
data-variant={variant}
|
|
96
|
+
className={cn(
|
|
97
|
+
"inline-flex max-w-full gap-0.5 text-ink",
|
|
98
|
+
// 放不下就换行,不出横向滚动条
|
|
99
|
+
orientation === "vertical" ? "flex-col" : "flex-wrap items-center",
|
|
100
|
+
barVariant[variant],
|
|
101
|
+
className,
|
|
102
|
+
)}
|
|
103
|
+
/>
|
|
104
|
+
</ToolbarContext>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/*
|
|
109
|
+
* 钮的点击区用伪元素补出去。`md` 四周各 2px,正好盖住钮之间的缝和带子的内边距,
|
|
110
|
+
* 到 40 × 40px;`sm` 横向只能补到缝为止(再多就盖到旁边的钮上了),是 32 × 40px。
|
|
111
|
+
*/
|
|
112
|
+
const itemSize: Record<ToolbarSize, string> = {
|
|
113
|
+
sm: "h-7 min-w-7 after:absolute after:-inset-x-0.5 after:-inset-y-1.5 after:content-['']",
|
|
114
|
+
md: "h-9 min-w-9 after:absolute after:-inset-0.5 after:content-['']",
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const itemPadding: Record<ToolbarSize, string> = {
|
|
118
|
+
sm: "px-2",
|
|
119
|
+
md: "px-2.5",
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/* 只有图标时图标跟着档位走;旁边有字时都是 16px,和 `text-sm` 的字配 */
|
|
123
|
+
const iconOnlySize: Record<ToolbarSize, string> = {
|
|
124
|
+
sm: "[&_svg]:size-4",
|
|
125
|
+
md: "[&_svg]:size-5",
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const itemBase = [
|
|
129
|
+
"group/item relative inline-flex shrink-0 items-center justify-center gap-1.5",
|
|
130
|
+
"text-sm leading-none font-medium whitespace-nowrap text-ink-secondary select-none",
|
|
131
|
+
"transition-colors duration-(--duration-fast) ease-standard",
|
|
132
|
+
"hover:bg-ink/5 hover:text-ink active:bg-ink/10",
|
|
133
|
+
// 按下的开关钮、还开着的菜单的触发钮:填充反转。字是反转的墨色,图标是黄记号(见 itemIcon)
|
|
134
|
+
"data-pressed:bg-surface-inverse data-pressed:text-ink-inverse",
|
|
135
|
+
"data-popup-open:bg-surface-inverse data-popup-open:text-ink-inverse",
|
|
136
|
+
"data-disabled:cursor-not-allowed data-disabled:bg-transparent data-disabled:text-ink-disabled",
|
|
137
|
+
"data-disabled:data-pressed:bg-disabled data-disabled:data-pressed:text-on-disabled",
|
|
138
|
+
// 焦点环压在旁边的钮上面
|
|
139
|
+
"focus-visible:z-1",
|
|
140
|
+
focusRing,
|
|
141
|
+
].join(" ");
|
|
142
|
+
|
|
143
|
+
/* 墨底黄记号,和 inverse 图标钮同一个样子;按下又禁用时跟着字一起降下去 */
|
|
144
|
+
const itemIcon = [
|
|
145
|
+
"flex shrink-0",
|
|
146
|
+
"group-data-pressed/item:text-accent-ink-inverse",
|
|
147
|
+
"group-data-popup-open/item:text-accent-ink-inverse",
|
|
148
|
+
"group-data-disabled/item:group-data-pressed/item:text-on-disabled",
|
|
149
|
+
].join(" ");
|
|
150
|
+
|
|
151
|
+
function useItemClass(iconOnly: boolean, className: string | undefined) {
|
|
152
|
+
const { size } = useContext(ToolbarContext);
|
|
153
|
+
return cn(
|
|
154
|
+
itemBase,
|
|
155
|
+
itemSize[size],
|
|
156
|
+
iconOnly ? iconOnlySize[size] : [itemPadding[size], "[&_svg]:size-4"],
|
|
157
|
+
className,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function ItemContent({
|
|
162
|
+
icon,
|
|
163
|
+
children,
|
|
164
|
+
}: {
|
|
165
|
+
icon: ReactNode;
|
|
166
|
+
children: ReactNode;
|
|
167
|
+
}) {
|
|
168
|
+
return (
|
|
169
|
+
<>
|
|
170
|
+
{icon && <span className={itemIcon}>{icon}</span>}
|
|
171
|
+
{children}
|
|
172
|
+
</>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** 有字的钮名称就是字;只有图标的必须另给名称 */
|
|
177
|
+
type ItemContentProps =
|
|
178
|
+
| { children: ReactNode; "aria-label"?: string }
|
|
179
|
+
| { children?: undefined; "aria-label": string };
|
|
180
|
+
|
|
181
|
+
const hasText = (children: ReactNode) =>
|
|
182
|
+
children !== undefined && children !== null && children !== false;
|
|
183
|
+
|
|
184
|
+
type ButtonOwnProps = {
|
|
185
|
+
/** 图标,在文字左边 */
|
|
186
|
+
icon?: ReactNode;
|
|
187
|
+
disabled?: boolean;
|
|
188
|
+
/** 传了就渲染成链接 */
|
|
189
|
+
href?: string;
|
|
190
|
+
target?: string;
|
|
191
|
+
rel?: string;
|
|
192
|
+
/** 用这个元素代替 `<a>`(路由库的链接组件)。传了就按链接处理 */
|
|
193
|
+
render?: LinkRender;
|
|
194
|
+
onClick?: (event: MouseEvent<HTMLElement>) => void;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
export type ToolbarButtonProps = ButtonOwnProps &
|
|
198
|
+
ItemContentProps &
|
|
199
|
+
Omit<
|
|
200
|
+
ComponentProps<"button">,
|
|
201
|
+
keyof ButtonOwnProps | "children" | "aria-label" | "style"
|
|
202
|
+
>;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* 工具栏里的一个钮。只有图标时是方的,必须有 `aria-label`。
|
|
206
|
+
* 可以交给 `Menu` / `Popover` 的 `trigger`,也可以用 `Tooltip` 包住。
|
|
207
|
+
*/
|
|
208
|
+
export function ToolbarButton({
|
|
209
|
+
icon,
|
|
210
|
+
disabled = false,
|
|
211
|
+
href,
|
|
212
|
+
target,
|
|
213
|
+
rel,
|
|
214
|
+
render,
|
|
215
|
+
onClick,
|
|
216
|
+
type = "button",
|
|
217
|
+
className,
|
|
218
|
+
children,
|
|
219
|
+
...props
|
|
220
|
+
}: ToolbarButtonProps) {
|
|
221
|
+
const classes = useItemClass(!hasText(children), className);
|
|
222
|
+
const content = <ItemContent icon={icon}>{children}</ItemContent>;
|
|
223
|
+
|
|
224
|
+
if (href !== undefined || render !== undefined) {
|
|
225
|
+
return (
|
|
226
|
+
<BaseToolbar.Link
|
|
227
|
+
{...(props as ComponentProps<"a">)}
|
|
228
|
+
render={render}
|
|
229
|
+
// 禁用的链接去掉地址;用了 render 时地址在那个元素手里,只能拦下点击
|
|
230
|
+
href={disabled ? undefined : href}
|
|
231
|
+
target={target}
|
|
232
|
+
rel={rel}
|
|
233
|
+
role={disabled && render === undefined ? "link" : undefined}
|
|
234
|
+
aria-disabled={disabled || undefined}
|
|
235
|
+
data-disabled={disabled ? "" : undefined}
|
|
236
|
+
onClick={disabled ? (event) => event.preventDefault() : onClick}
|
|
237
|
+
className={classes}
|
|
238
|
+
>
|
|
239
|
+
{content}
|
|
240
|
+
</BaseToolbar.Link>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return (
|
|
245
|
+
<BaseToolbar.Button
|
|
246
|
+
{...props}
|
|
247
|
+
type={type}
|
|
248
|
+
disabled={disabled}
|
|
249
|
+
onClick={onClick}
|
|
250
|
+
className={classes}
|
|
251
|
+
>
|
|
252
|
+
{content}
|
|
253
|
+
</BaseToolbar.Button>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
type ToggleOwnProps = {
|
|
258
|
+
/** 图标,在文字左边 */
|
|
259
|
+
icon?: ReactNode;
|
|
260
|
+
/** 受控的按下状态。放在 `ToolbarToggleGroup` 里时不用它,用 `value` */
|
|
261
|
+
pressed?: boolean;
|
|
262
|
+
/** 非受控时一开始是不是按下的 */
|
|
263
|
+
defaultPressed?: boolean;
|
|
264
|
+
onPressedChange?: (pressed: boolean) => void;
|
|
265
|
+
/** 在 `ToolbarToggleGroup` 里是这一个钮的标识:组的 `value` 里放的就是它 */
|
|
266
|
+
value?: string;
|
|
267
|
+
disabled?: boolean;
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
export type ToolbarToggleProps = ToggleOwnProps &
|
|
271
|
+
ItemContentProps &
|
|
272
|
+
Omit<
|
|
273
|
+
ComponentProps<"button">,
|
|
274
|
+
keyof ToggleOwnProps | "children" | "aria-label" | "aria-pressed" | "style"
|
|
275
|
+
>;
|
|
276
|
+
|
|
277
|
+
/** 开关钮:按下是墨底黄记号。单独用,或者几个放进 `ToolbarToggleGroup`。 */
|
|
278
|
+
export function ToolbarToggle({
|
|
279
|
+
icon,
|
|
280
|
+
pressed,
|
|
281
|
+
defaultPressed,
|
|
282
|
+
onPressedChange,
|
|
283
|
+
value,
|
|
284
|
+
disabled = false,
|
|
285
|
+
type = "button",
|
|
286
|
+
className,
|
|
287
|
+
children,
|
|
288
|
+
...props
|
|
289
|
+
}: ToolbarToggleProps) {
|
|
290
|
+
const classes = useItemClass(!hasText(children), className);
|
|
291
|
+
|
|
292
|
+
return (
|
|
293
|
+
<BaseToolbar.Button
|
|
294
|
+
{...props}
|
|
295
|
+
type={type}
|
|
296
|
+
disabled={disabled}
|
|
297
|
+
render={
|
|
298
|
+
<BaseToggle
|
|
299
|
+
pressed={pressed}
|
|
300
|
+
defaultPressed={defaultPressed}
|
|
301
|
+
onPressedChange={onPressedChange && ((next) => onPressedChange(next))}
|
|
302
|
+
value={value}
|
|
303
|
+
/>
|
|
304
|
+
}
|
|
305
|
+
className={classes}
|
|
306
|
+
>
|
|
307
|
+
<ItemContent icon={icon}>{children}</ItemContent>
|
|
308
|
+
</BaseToolbar.Button>
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const groupClass =
|
|
313
|
+
"flex gap-0.5 data-[orientation=horizontal]:flex-wrap data-[orientation=horizontal]:items-center data-[orientation=vertical]:flex-col";
|
|
314
|
+
|
|
315
|
+
export type ToolbarToggleGroupProps = Omit<
|
|
316
|
+
ComponentProps<"div">,
|
|
317
|
+
"aria-label" | "defaultValue" | "onChange" | "style"
|
|
318
|
+
> & {
|
|
319
|
+
/** 这一组管的是什么:"行高" */
|
|
320
|
+
"aria-label": string;
|
|
321
|
+
/** 按下的那几个钮的 `value` */
|
|
322
|
+
value?: string[];
|
|
323
|
+
defaultValue?: string[];
|
|
324
|
+
onValueChange?: (value: string[]) => void;
|
|
325
|
+
/** 可以同时按下几个。默认按下这个,那个就弹起来;一个都不按也可以 */
|
|
326
|
+
multiple?: boolean;
|
|
327
|
+
disabled?: boolean;
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
/** 一组开关钮。值是数组:没有按下的就是空的。 */
|
|
331
|
+
export function ToolbarToggleGroup({
|
|
332
|
+
value,
|
|
333
|
+
defaultValue,
|
|
334
|
+
onValueChange,
|
|
335
|
+
multiple = false,
|
|
336
|
+
disabled,
|
|
337
|
+
className,
|
|
338
|
+
...props
|
|
339
|
+
}: ToolbarToggleGroupProps) {
|
|
340
|
+
const { orientation } = useContext(ToolbarContext);
|
|
341
|
+
|
|
342
|
+
return (
|
|
343
|
+
<BaseToggleGroup
|
|
344
|
+
{...props}
|
|
345
|
+
value={value}
|
|
346
|
+
defaultValue={defaultValue}
|
|
347
|
+
onValueChange={onValueChange && ((next) => onValueChange(next))}
|
|
348
|
+
multiple={multiple}
|
|
349
|
+
disabled={disabled}
|
|
350
|
+
orientation={orientation}
|
|
351
|
+
className={cn(groupClass, className)}
|
|
352
|
+
/>
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export type ToolbarGroupProps = Omit<
|
|
357
|
+
ComponentProps<"div">,
|
|
358
|
+
"aria-label" | "style"
|
|
359
|
+
> & {
|
|
360
|
+
/** 这一组管的是什么 */
|
|
361
|
+
"aria-label": string;
|
|
362
|
+
/** 整组禁用 */
|
|
363
|
+
disabled?: boolean;
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
/** 把几个钮在读屏里算成一组。看上去没有区别;要分开看,中间放 `ToolbarSeparator`。 */
|
|
367
|
+
export function ToolbarGroup({ className, ...props }: ToolbarGroupProps) {
|
|
368
|
+
return <BaseToolbar.Group {...props} className={cn(groupClass, className)} />;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export type ToolbarSeparatorProps = Omit<ComponentProps<"div">, "style">;
|
|
372
|
+
|
|
373
|
+
/** 分隔:一条短线,方向和工具栏垂直。 */
|
|
374
|
+
export function ToolbarSeparator({
|
|
375
|
+
className,
|
|
376
|
+
...props
|
|
377
|
+
}: ToolbarSeparatorProps) {
|
|
378
|
+
return (
|
|
379
|
+
<BaseToolbar.Separator
|
|
380
|
+
{...props}
|
|
381
|
+
className={cn(
|
|
382
|
+
"shrink-0 self-center bg-line-strong",
|
|
383
|
+
"data-[orientation=vertical]:mx-1 data-[orientation=vertical]:h-4 data-[orientation=vertical]:w-px",
|
|
384
|
+
"data-[orientation=horizontal]:my-1 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-4",
|
|
385
|
+
className,
|
|
386
|
+
)}
|
|
387
|
+
/>
|
|
388
|
+
);
|
|
389
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export {
|
|
2
|
+
Toolbar,
|
|
3
|
+
ToolbarButton,
|
|
4
|
+
ToolbarGroup,
|
|
5
|
+
ToolbarSeparator,
|
|
6
|
+
ToolbarToggle,
|
|
7
|
+
ToolbarToggleGroup,
|
|
8
|
+
type ToolbarButtonProps,
|
|
9
|
+
type ToolbarGroupProps,
|
|
10
|
+
type ToolbarOrientation,
|
|
11
|
+
type ToolbarProps,
|
|
12
|
+
type ToolbarSeparatorProps,
|
|
13
|
+
type ToolbarSize,
|
|
14
|
+
type ToolbarToggleGroupProps,
|
|
15
|
+
type ToolbarToggleProps,
|
|
16
|
+
type ToolbarVariant,
|
|
17
|
+
} from "./Toolbar";
|