@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,213 @@
|
|
|
1
|
+
import { Drawer as BaseDrawer } from "@base-ui/react/drawer";
|
|
2
|
+
import {
|
|
3
|
+
useRef,
|
|
4
|
+
type ComponentProps,
|
|
5
|
+
type ReactElement,
|
|
6
|
+
type ReactNode,
|
|
7
|
+
} from "react";
|
|
8
|
+
import { usePortalScope } from "../../hooks/usePortalScope";
|
|
9
|
+
import { Close } from "../../icons/Close";
|
|
10
|
+
import { cn } from "../../lib/cn";
|
|
11
|
+
import { firstFocusableIn } from "../dialog/initial-focus";
|
|
12
|
+
import {
|
|
13
|
+
overlayBackdrop,
|
|
14
|
+
overlayClose,
|
|
15
|
+
overlayHeader,
|
|
16
|
+
overlaySurface,
|
|
17
|
+
} from "../dialog/overlay-style";
|
|
18
|
+
|
|
19
|
+
export type DrawerSide = "right" | "left" | "bottom";
|
|
20
|
+
export type DrawerSize = "sm" | "md" | "lg";
|
|
21
|
+
|
|
22
|
+
type PopupProps = ComponentProps<typeof BaseDrawer.Popup>;
|
|
23
|
+
|
|
24
|
+
export type DrawerProps = Omit<
|
|
25
|
+
ComponentProps<"div">,
|
|
26
|
+
"title" | "children" | "className" | "style"
|
|
27
|
+
> & {
|
|
28
|
+
/** 触发按钮。不传就用 `open` / `onOpenChange` 自己控制 */
|
|
29
|
+
trigger?: ReactElement<Record<string, unknown>>;
|
|
30
|
+
open?: boolean;
|
|
31
|
+
defaultOpen?: boolean;
|
|
32
|
+
onOpenChange?: (open: boolean) => void;
|
|
33
|
+
/** 标题,在深色标题带里左对齐。它也是抽屉的可访问名称 */
|
|
34
|
+
title: ReactNode;
|
|
35
|
+
/** 标题下的一段说明,会关联给读屏 */
|
|
36
|
+
description?: ReactNode;
|
|
37
|
+
/** 底部的行动区,不随内容滚动 */
|
|
38
|
+
footer?: ReactNode;
|
|
39
|
+
/**
|
|
40
|
+
* 从哪一边滑入:
|
|
41
|
+
* - `right` 详情、设置,默认;
|
|
42
|
+
* - `left` 导航;
|
|
43
|
+
* - `bottom` 移动端的操作菜单,带拖动把手。
|
|
44
|
+
* 都可以朝来的方向划走。
|
|
45
|
+
*/
|
|
46
|
+
side?: DrawerSide;
|
|
47
|
+
/** 侧边抽屉的宽度 320 / 400 / 480px,窄屏上通宽。默认 `md`。底部抽屉不用 */
|
|
48
|
+
size?: DrawerSize;
|
|
49
|
+
/** 靠页面内容的一侧加一条行动色的细条 */
|
|
50
|
+
accent?: boolean;
|
|
51
|
+
/** 关闭图标的可访问名称 */
|
|
52
|
+
closeLabel?: string;
|
|
53
|
+
/** 打开时焦点落在哪。默认是内容区里第一个可聚焦的元素 */
|
|
54
|
+
initialFocus?: PopupProps["initialFocus"];
|
|
55
|
+
/** 关闭后焦点回到哪。默认是触发按钮 */
|
|
56
|
+
finalFocus?: PopupProps["finalFocus"];
|
|
57
|
+
/** 给抽屉本身 */
|
|
58
|
+
className?: string;
|
|
59
|
+
children?: ReactNode;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const swipeDirection = {
|
|
63
|
+
right: "right",
|
|
64
|
+
left: "left",
|
|
65
|
+
bottom: "down",
|
|
66
|
+
} as const;
|
|
67
|
+
|
|
68
|
+
const viewportClass: Record<DrawerSide, string> = {
|
|
69
|
+
right: "justify-end",
|
|
70
|
+
left: "justify-start",
|
|
71
|
+
bottom: "items-end",
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const sizeClass: Record<DrawerSize, string> = {
|
|
75
|
+
sm: "max-w-80",
|
|
76
|
+
md: "max-w-100",
|
|
77
|
+
lg: "max-w-120",
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/*
|
|
81
|
+
* 位移跟着手指走(--drawer-swipe-movement-*),进出场时整块移到屏幕外。
|
|
82
|
+
* 拖动中不要过渡,否则抽屉会慢半拍。
|
|
83
|
+
*/
|
|
84
|
+
const motionClass: Record<DrawerSide, string> = {
|
|
85
|
+
right:
|
|
86
|
+
"translate-x-[var(--drawer-swipe-movement-x,0px)] data-ending-style:translate-x-full data-starting-style:translate-x-full",
|
|
87
|
+
left: "translate-x-[var(--drawer-swipe-movement-x,0px)] data-ending-style:-translate-x-full data-starting-style:-translate-x-full",
|
|
88
|
+
bottom:
|
|
89
|
+
"translate-y-[var(--drawer-swipe-movement-y,0px)] data-ending-style:translate-y-full data-starting-style:translate-y-full",
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const accentClass: Record<DrawerSide, string> = {
|
|
93
|
+
right: "border-l-4 border-l-action",
|
|
94
|
+
left: "border-r-4 border-r-action",
|
|
95
|
+
bottom: "border-t-4 border-t-action",
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 抽屉:从屏幕一侧滑入的面板。在不离开当前页面的前提下查看、编辑一个条目,
|
|
100
|
+
* 或者在窄屏上替代侧栏。焦点与背景的处理和弹窗相同。
|
|
101
|
+
*/
|
|
102
|
+
export function Drawer({
|
|
103
|
+
trigger,
|
|
104
|
+
open,
|
|
105
|
+
defaultOpen,
|
|
106
|
+
onOpenChange,
|
|
107
|
+
title,
|
|
108
|
+
description,
|
|
109
|
+
footer,
|
|
110
|
+
side = "right",
|
|
111
|
+
size = "md",
|
|
112
|
+
accent = false,
|
|
113
|
+
closeLabel = "关闭",
|
|
114
|
+
initialFocus,
|
|
115
|
+
finalFocus,
|
|
116
|
+
className,
|
|
117
|
+
children,
|
|
118
|
+
...props
|
|
119
|
+
}: DrawerProps) {
|
|
120
|
+
const { anchorRef, portalRef } = usePortalScope();
|
|
121
|
+
const contentRef = useRef<HTMLDivElement>(null);
|
|
122
|
+
const sheet = side === "bottom";
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<BaseDrawer.Root
|
|
126
|
+
open={open}
|
|
127
|
+
defaultOpen={defaultOpen}
|
|
128
|
+
onOpenChange={onOpenChange && ((next) => onOpenChange(next))}
|
|
129
|
+
swipeDirection={swipeDirection[side]}
|
|
130
|
+
>
|
|
131
|
+
{trigger && <BaseDrawer.Trigger ref={anchorRef} render={trigger} />}
|
|
132
|
+
<BaseDrawer.Portal ref={portalRef}>
|
|
133
|
+
<BaseDrawer.Backdrop
|
|
134
|
+
className={cn(
|
|
135
|
+
overlayBackdrop,
|
|
136
|
+
// 往外划的时候遮罩跟着变淡
|
|
137
|
+
"opacity-[calc(1-var(--drawer-swipe-progress,0))] data-swiping:duration-0",
|
|
138
|
+
)}
|
|
139
|
+
/>
|
|
140
|
+
<BaseDrawer.Viewport
|
|
141
|
+
className={cn(
|
|
142
|
+
"fixed inset-0 z-(--z-overlay) flex overflow-hidden",
|
|
143
|
+
viewportClass[side],
|
|
144
|
+
)}
|
|
145
|
+
>
|
|
146
|
+
<BaseDrawer.Popup
|
|
147
|
+
{...props}
|
|
148
|
+
initialFocus={initialFocus ?? firstFocusableIn(contentRef)}
|
|
149
|
+
finalFocus={finalFocus}
|
|
150
|
+
data-side={side}
|
|
151
|
+
className={cn(
|
|
152
|
+
overlaySurface,
|
|
153
|
+
"flex min-h-0 w-full flex-col",
|
|
154
|
+
sheet ? "max-h-[85dvh]" : ["h-full", sizeClass[size]],
|
|
155
|
+
"transition-[translate] duration-(--duration-base) ease-exit data-swiping:duration-0 data-swiping:select-none",
|
|
156
|
+
motionClass[side],
|
|
157
|
+
accent && accentClass[side],
|
|
158
|
+
className,
|
|
159
|
+
)}
|
|
160
|
+
>
|
|
161
|
+
<div
|
|
162
|
+
data-theme="dark"
|
|
163
|
+
className={cn(
|
|
164
|
+
overlayHeader,
|
|
165
|
+
"min-h-12 pr-12 pl-4",
|
|
166
|
+
sheet && "pt-2",
|
|
167
|
+
)}
|
|
168
|
+
>
|
|
169
|
+
{sheet && (
|
|
170
|
+
// 拖动把手
|
|
171
|
+
<span
|
|
172
|
+
aria-hidden="true"
|
|
173
|
+
className="absolute top-1.5 left-1/2 h-1 w-8 -translate-x-1/2 bg-line-strong"
|
|
174
|
+
/>
|
|
175
|
+
)}
|
|
176
|
+
<BaseDrawer.Title className="py-2.5 text-lg font-normal wrap-anywhere">
|
|
177
|
+
{title}
|
|
178
|
+
</BaseDrawer.Title>
|
|
179
|
+
<BaseDrawer.Close
|
|
180
|
+
aria-label={closeLabel}
|
|
181
|
+
className={overlayClose}
|
|
182
|
+
>
|
|
183
|
+
<Close size={24} />
|
|
184
|
+
</BaseDrawer.Close>
|
|
185
|
+
</div>
|
|
186
|
+
|
|
187
|
+
<div ref={contentRef} className="flex min-h-0 flex-auto flex-col">
|
|
188
|
+
<BaseDrawer.Content className="min-h-0 flex-auto overflow-y-auto overscroll-contain p-4 wrap-anywhere">
|
|
189
|
+
{description && (
|
|
190
|
+
<BaseDrawer.Description
|
|
191
|
+
className={cn(
|
|
192
|
+
"text-ink-secondary",
|
|
193
|
+
children !== undefined && "mb-4",
|
|
194
|
+
)}
|
|
195
|
+
>
|
|
196
|
+
{description}
|
|
197
|
+
</BaseDrawer.Description>
|
|
198
|
+
)}
|
|
199
|
+
{children}
|
|
200
|
+
</BaseDrawer.Content>
|
|
201
|
+
|
|
202
|
+
{footer && (
|
|
203
|
+
<div className="flex shrink-0 flex-wrap justify-end gap-3 border-t border-line px-4 py-3">
|
|
204
|
+
{footer}
|
|
205
|
+
</div>
|
|
206
|
+
)}
|
|
207
|
+
</div>
|
|
208
|
+
</BaseDrawer.Popup>
|
|
209
|
+
</BaseDrawer.Viewport>
|
|
210
|
+
</BaseDrawer.Portal>
|
|
211
|
+
</BaseDrawer.Root>
|
|
212
|
+
);
|
|
213
|
+
}
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import { Menu as BaseMenu } from "@base-ui/react/menu";
|
|
2
|
+
import {
|
|
3
|
+
useContext,
|
|
4
|
+
type ComponentProps,
|
|
5
|
+
type MouseEvent,
|
|
6
|
+
type ReactElement,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
} from "react";
|
|
9
|
+
import { usePortalScope } from "../../hooks/usePortalScope";
|
|
10
|
+
import { TriangleRight } from "../../icons/TriangleRight";
|
|
11
|
+
import { cn } from "../../lib/cn";
|
|
12
|
+
import { defined } from "../../lib/defined";
|
|
13
|
+
import type { LinkRender } from "../link-element/LinkElement";
|
|
14
|
+
import { MenuCheck } from "./MenuCheck";
|
|
15
|
+
import {
|
|
16
|
+
MenuVariantContext,
|
|
17
|
+
menuGroupLabel,
|
|
18
|
+
menuItem,
|
|
19
|
+
menuItemCurrent,
|
|
20
|
+
menuItemHover,
|
|
21
|
+
menuPanel,
|
|
22
|
+
menuSeparator,
|
|
23
|
+
type MenuVariant,
|
|
24
|
+
} from "./menu-style";
|
|
25
|
+
|
|
26
|
+
export type DropdownMenuVariant = MenuVariant;
|
|
27
|
+
export type DropdownMenuSide = "top" | "bottom" | "left" | "right";
|
|
28
|
+
export type DropdownMenuAlign = "start" | "center" | "end";
|
|
29
|
+
|
|
30
|
+
export type DropdownMenuProps = {
|
|
31
|
+
/** 触发按钮 */
|
|
32
|
+
trigger: ReactElement<Record<string, unknown>>;
|
|
33
|
+
/**
|
|
34
|
+
* - `plain` 跟随主题的面板,默认;
|
|
35
|
+
* - `strong` 固定的深色面板,当前项整行黄底墨字。
|
|
36
|
+
*/
|
|
37
|
+
variant?: DropdownMenuVariant;
|
|
38
|
+
/** 面板出现在触发按钮的哪一侧,放不下时自动翻到对面。默认 `bottom` */
|
|
39
|
+
side?: DropdownMenuSide;
|
|
40
|
+
/** 和触发按钮的哪一边对齐。默认 `start` */
|
|
41
|
+
align?: DropdownMenuAlign;
|
|
42
|
+
open?: boolean;
|
|
43
|
+
defaultOpen?: boolean;
|
|
44
|
+
onOpenChange?: (open: boolean) => void;
|
|
45
|
+
/** 给面板 */
|
|
46
|
+
className?: string;
|
|
47
|
+
/**
|
|
48
|
+
* `DropdownMenuItem`、`DropdownMenuCheckboxItem`、`DropdownMenuGroup`、
|
|
49
|
+
* `DropdownMenuSeparator`、`DropdownMenuRadioGroup`、`DropdownMenuSub`
|
|
50
|
+
*/
|
|
51
|
+
children: ReactNode;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 点击触发的一组操作或选项。方向键移动、回车选择、`Esc` 关闭、按首字母跳转。
|
|
56
|
+
* 危险项放在最后并和其他项隔开。
|
|
57
|
+
*/
|
|
58
|
+
export function DropdownMenu({
|
|
59
|
+
trigger,
|
|
60
|
+
variant = "plain",
|
|
61
|
+
side = "bottom",
|
|
62
|
+
align = "start",
|
|
63
|
+
open,
|
|
64
|
+
defaultOpen,
|
|
65
|
+
onOpenChange,
|
|
66
|
+
className,
|
|
67
|
+
children,
|
|
68
|
+
}: DropdownMenuProps) {
|
|
69
|
+
const { anchorRef, portalRef } = usePortalScope();
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<BaseMenu.Root
|
|
73
|
+
open={open}
|
|
74
|
+
defaultOpen={defaultOpen}
|
|
75
|
+
onOpenChange={onOpenChange && ((next) => onOpenChange(next))}
|
|
76
|
+
>
|
|
77
|
+
<BaseMenu.Trigger ref={anchorRef} render={trigger} />
|
|
78
|
+
<BaseMenu.Portal ref={portalRef}>
|
|
79
|
+
<BaseMenu.Positioner
|
|
80
|
+
side={side}
|
|
81
|
+
align={align}
|
|
82
|
+
sideOffset={4}
|
|
83
|
+
className="z-(--z-overlay) outline-none"
|
|
84
|
+
>
|
|
85
|
+
<BaseMenu.Popup
|
|
86
|
+
data-theme={variant === "strong" ? "dark" : undefined}
|
|
87
|
+
data-variant={variant}
|
|
88
|
+
className={cn(
|
|
89
|
+
menuPanel,
|
|
90
|
+
"max-h-(--available-height) overflow-y-auto overscroll-contain",
|
|
91
|
+
className,
|
|
92
|
+
)}
|
|
93
|
+
>
|
|
94
|
+
<MenuVariantContext value={variant}>{children}</MenuVariantContext>
|
|
95
|
+
</BaseMenu.Popup>
|
|
96
|
+
</BaseMenu.Positioner>
|
|
97
|
+
</BaseMenu.Portal>
|
|
98
|
+
</BaseMenu.Root>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export type DropdownMenuItemTone = "neutral" | "danger";
|
|
103
|
+
|
|
104
|
+
type ItemOwnProps = {
|
|
105
|
+
/** `danger` 用于删除一类的操作:文字变红,放在最后并和其他项隔开 */
|
|
106
|
+
tone?: DropdownMenuItemTone;
|
|
107
|
+
/** 文字左侧的图标 */
|
|
108
|
+
iconStart?: ReactNode;
|
|
109
|
+
/** 行尾的补充:快捷键、计数 */
|
|
110
|
+
end?: ReactNode;
|
|
111
|
+
disabled?: boolean;
|
|
112
|
+
/** 点了之后是否关掉菜单。操作项默认关,链接项默认不关 */
|
|
113
|
+
closeOnClick?: boolean;
|
|
114
|
+
/** 传了就渲染成链接 */
|
|
115
|
+
href?: string;
|
|
116
|
+
target?: string;
|
|
117
|
+
rel?: string;
|
|
118
|
+
/** 用这个元素代替 `<a>`(路由库的链接组件)。传了就按链接处理 */
|
|
119
|
+
render?: LinkRender;
|
|
120
|
+
onClick?: (event: MouseEvent<HTMLElement>) => void;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export type DropdownMenuItemProps = ItemOwnProps &
|
|
124
|
+
Omit<ComponentProps<"div">, keyof ItemOwnProps | "id" | "style">;
|
|
125
|
+
|
|
126
|
+
function itemContent(
|
|
127
|
+
iconStart: ReactNode,
|
|
128
|
+
children: ReactNode,
|
|
129
|
+
end: ReactNode,
|
|
130
|
+
) {
|
|
131
|
+
return (
|
|
132
|
+
<>
|
|
133
|
+
{iconStart && (
|
|
134
|
+
<span
|
|
135
|
+
aria-hidden="true"
|
|
136
|
+
className="flex shrink-0 items-center [&_svg]:size-4"
|
|
137
|
+
>
|
|
138
|
+
{iconStart}
|
|
139
|
+
</span>
|
|
140
|
+
)}
|
|
141
|
+
<span className="min-w-0 flex-1 truncate">{children}</span>
|
|
142
|
+
{end && (
|
|
143
|
+
<span className="ml-4 flex shrink-0 items-center text-xs text-ink-secondary">
|
|
144
|
+
{end}
|
|
145
|
+
</span>
|
|
146
|
+
)}
|
|
147
|
+
</>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** 菜单里的一个操作;传 `href` 就是一个链接。 */
|
|
152
|
+
export function DropdownMenuItem({
|
|
153
|
+
tone = "neutral",
|
|
154
|
+
iconStart,
|
|
155
|
+
end,
|
|
156
|
+
disabled,
|
|
157
|
+
closeOnClick,
|
|
158
|
+
href,
|
|
159
|
+
target,
|
|
160
|
+
rel,
|
|
161
|
+
render,
|
|
162
|
+
onClick,
|
|
163
|
+
className,
|
|
164
|
+
children,
|
|
165
|
+
...props
|
|
166
|
+
}: DropdownMenuItemProps) {
|
|
167
|
+
const classes = cn(
|
|
168
|
+
menuItem,
|
|
169
|
+
menuItemHover,
|
|
170
|
+
tone === "danger" && "text-danger",
|
|
171
|
+
className,
|
|
172
|
+
);
|
|
173
|
+
const content = itemContent(iconStart, children, end);
|
|
174
|
+
|
|
175
|
+
if (href !== undefined || render !== undefined) {
|
|
176
|
+
return (
|
|
177
|
+
<BaseMenu.LinkItem
|
|
178
|
+
render={render}
|
|
179
|
+
{...defined({ href, target, rel })}
|
|
180
|
+
closeOnClick={closeOnClick}
|
|
181
|
+
onClick={onClick}
|
|
182
|
+
data-tone={tone}
|
|
183
|
+
className={classes}
|
|
184
|
+
>
|
|
185
|
+
{content}
|
|
186
|
+
</BaseMenu.LinkItem>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<BaseMenu.Item
|
|
192
|
+
{...props}
|
|
193
|
+
disabled={disabled}
|
|
194
|
+
closeOnClick={closeOnClick}
|
|
195
|
+
onClick={onClick}
|
|
196
|
+
data-tone={tone}
|
|
197
|
+
className={classes}
|
|
198
|
+
>
|
|
199
|
+
{content}
|
|
200
|
+
</BaseMenu.Item>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export type DropdownMenuGroupProps = Omit<ComponentProps<"div">, "style"> & {
|
|
205
|
+
/** 分组的小标题 */
|
|
206
|
+
label?: ReactNode;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
export function DropdownMenuGroup({
|
|
210
|
+
label,
|
|
211
|
+
children,
|
|
212
|
+
...props
|
|
213
|
+
}: DropdownMenuGroupProps) {
|
|
214
|
+
return (
|
|
215
|
+
<BaseMenu.Group {...props}>
|
|
216
|
+
{label && (
|
|
217
|
+
<BaseMenu.GroupLabel className={menuGroupLabel}>
|
|
218
|
+
{label}
|
|
219
|
+
</BaseMenu.GroupLabel>
|
|
220
|
+
)}
|
|
221
|
+
{children}
|
|
222
|
+
</BaseMenu.Group>
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export type DropdownMenuSeparatorProps = Omit<ComponentProps<"div">, "style">;
|
|
227
|
+
|
|
228
|
+
export function DropdownMenuSeparator({
|
|
229
|
+
className,
|
|
230
|
+
...props
|
|
231
|
+
}: DropdownMenuSeparatorProps) {
|
|
232
|
+
return (
|
|
233
|
+
<BaseMenu.Separator {...props} className={cn(menuSeparator, className)} />
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export type DropdownMenuRadioGroupProps = {
|
|
238
|
+
value?: string;
|
|
239
|
+
defaultValue?: string;
|
|
240
|
+
onValueChange?: (value: string) => void;
|
|
241
|
+
/** 这一组的小标题 */
|
|
242
|
+
label?: ReactNode;
|
|
243
|
+
disabled?: boolean;
|
|
244
|
+
children: ReactNode;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
/** 一组里只有一个"当前项"的选项,比如语言、排序方式。 */
|
|
248
|
+
export function DropdownMenuRadioGroup({
|
|
249
|
+
value,
|
|
250
|
+
defaultValue,
|
|
251
|
+
onValueChange,
|
|
252
|
+
label,
|
|
253
|
+
disabled,
|
|
254
|
+
children,
|
|
255
|
+
}: DropdownMenuRadioGroupProps) {
|
|
256
|
+
const group = (
|
|
257
|
+
<BaseMenu.RadioGroup
|
|
258
|
+
value={value}
|
|
259
|
+
defaultValue={defaultValue}
|
|
260
|
+
onValueChange={onValueChange && ((next) => onValueChange(String(next)))}
|
|
261
|
+
disabled={disabled}
|
|
262
|
+
>
|
|
263
|
+
{children}
|
|
264
|
+
</BaseMenu.RadioGroup>
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
if (!label) return group;
|
|
268
|
+
return (
|
|
269
|
+
<BaseMenu.Group>
|
|
270
|
+
<BaseMenu.GroupLabel className={menuGroupLabel}>
|
|
271
|
+
{label}
|
|
272
|
+
</BaseMenu.GroupLabel>
|
|
273
|
+
{group}
|
|
274
|
+
</BaseMenu.Group>
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export type DropdownMenuRadioItemProps = Omit<
|
|
279
|
+
ComponentProps<"div">,
|
|
280
|
+
"id" | "style" | "onClick"
|
|
281
|
+
> & {
|
|
282
|
+
value: string;
|
|
283
|
+
disabled?: boolean;
|
|
284
|
+
/** 行尾的补充 */
|
|
285
|
+
end?: ReactNode;
|
|
286
|
+
/** 选了之后是否关掉菜单,默认关 */
|
|
287
|
+
closeOnClick?: boolean;
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
export function DropdownMenuRadioItem({
|
|
291
|
+
value,
|
|
292
|
+
disabled,
|
|
293
|
+
end,
|
|
294
|
+
closeOnClick = true,
|
|
295
|
+
className,
|
|
296
|
+
children,
|
|
297
|
+
...props
|
|
298
|
+
}: DropdownMenuRadioItemProps) {
|
|
299
|
+
const variant = useContext(MenuVariantContext);
|
|
300
|
+
|
|
301
|
+
return (
|
|
302
|
+
<BaseMenu.RadioItem
|
|
303
|
+
{...props}
|
|
304
|
+
value={value}
|
|
305
|
+
disabled={disabled}
|
|
306
|
+
closeOnClick={closeOnClick}
|
|
307
|
+
className={(state) =>
|
|
308
|
+
cn(
|
|
309
|
+
menuItem,
|
|
310
|
+
state.checked ? menuItemCurrent[variant] : menuItemHover,
|
|
311
|
+
className,
|
|
312
|
+
)
|
|
313
|
+
}
|
|
314
|
+
>
|
|
315
|
+
{itemContent(null, children, end)}
|
|
316
|
+
</BaseMenu.RadioItem>
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export type DropdownMenuCheckboxItemProps = Omit<
|
|
321
|
+
ComponentProps<"div">,
|
|
322
|
+
"id" | "style" | "onClick"
|
|
323
|
+
> & {
|
|
324
|
+
checked?: boolean;
|
|
325
|
+
defaultChecked?: boolean;
|
|
326
|
+
onCheckedChange?: (checked: boolean) => void;
|
|
327
|
+
disabled?: boolean;
|
|
328
|
+
/** 行尾的补充 */
|
|
329
|
+
end?: ReactNode;
|
|
330
|
+
/** 勾了之后是否关掉菜单。默认不关:可以连着改几项 */
|
|
331
|
+
closeOnClick?: boolean;
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
/** 能开能关的一项设置,比如"显示缩略图"。行首是一个小方格,没勾的时候也看得见。 */
|
|
335
|
+
export function DropdownMenuCheckboxItem({
|
|
336
|
+
checked,
|
|
337
|
+
defaultChecked,
|
|
338
|
+
onCheckedChange,
|
|
339
|
+
disabled,
|
|
340
|
+
end,
|
|
341
|
+
closeOnClick = false,
|
|
342
|
+
className,
|
|
343
|
+
children,
|
|
344
|
+
...props
|
|
345
|
+
}: DropdownMenuCheckboxItemProps) {
|
|
346
|
+
return (
|
|
347
|
+
<BaseMenu.CheckboxItem
|
|
348
|
+
{...props}
|
|
349
|
+
checked={checked}
|
|
350
|
+
defaultChecked={defaultChecked}
|
|
351
|
+
onCheckedChange={onCheckedChange && ((next) => onCheckedChange(next))}
|
|
352
|
+
disabled={disabled}
|
|
353
|
+
closeOnClick={closeOnClick}
|
|
354
|
+
className={cn(menuItem, menuItemHover, className)}
|
|
355
|
+
render={(itemProps, state) => (
|
|
356
|
+
<div {...itemProps}>
|
|
357
|
+
<MenuCheck checked={state.checked} disabled={state.disabled} />
|
|
358
|
+
{itemContent(null, children, end)}
|
|
359
|
+
</div>
|
|
360
|
+
)}
|
|
361
|
+
/>
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export type DropdownMenuSubProps = {
|
|
366
|
+
/** 这一行的文字 */
|
|
367
|
+
label: ReactNode;
|
|
368
|
+
/** 文字左侧的图标 */
|
|
369
|
+
iconStart?: ReactNode;
|
|
370
|
+
disabled?: boolean;
|
|
371
|
+
/** 给子面板 */
|
|
372
|
+
className?: string;
|
|
373
|
+
/** 子面板里的选项 */
|
|
374
|
+
children: ReactNode;
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* 子菜单:菜单里的一行,停留或按 `→` 时在侧边展开另一块面板。
|
|
379
|
+
* 只做一层,不要在子菜单里再套子菜单。
|
|
380
|
+
*/
|
|
381
|
+
export function DropdownMenuSub({
|
|
382
|
+
label,
|
|
383
|
+
iconStart,
|
|
384
|
+
disabled,
|
|
385
|
+
className,
|
|
386
|
+
children,
|
|
387
|
+
}: DropdownMenuSubProps) {
|
|
388
|
+
const variant = useContext(MenuVariantContext);
|
|
389
|
+
// 子面板也挂在 <body> 下:从这一行往上找,能找到主面板带着的主题
|
|
390
|
+
const { anchorRef, portalRef } = usePortalScope();
|
|
391
|
+
|
|
392
|
+
return (
|
|
393
|
+
<BaseMenu.SubmenuRoot disabled={disabled}>
|
|
394
|
+
<BaseMenu.SubmenuTrigger
|
|
395
|
+
ref={anchorRef}
|
|
396
|
+
className={cn(
|
|
397
|
+
menuItem,
|
|
398
|
+
menuItemHover,
|
|
399
|
+
// 子面板开着的时候这一行保持高亮:看得出面板是从哪一行出来的
|
|
400
|
+
"data-popup-open:bg-ink/5",
|
|
401
|
+
)}
|
|
402
|
+
>
|
|
403
|
+
{itemContent(iconStart, label, <TriangleRight size={10} />)}
|
|
404
|
+
</BaseMenu.SubmenuTrigger>
|
|
405
|
+
<BaseMenu.Portal ref={portalRef}>
|
|
406
|
+
<BaseMenu.Positioner
|
|
407
|
+
sideOffset={2}
|
|
408
|
+
// 面板有 1px 的边线和 4px 的上内边距:往上提这么多,第一项正好和这一行对齐
|
|
409
|
+
alignOffset={-5}
|
|
410
|
+
className="z-(--z-overlay) outline-none"
|
|
411
|
+
>
|
|
412
|
+
<BaseMenu.Popup
|
|
413
|
+
data-theme={variant === "strong" ? "dark" : undefined}
|
|
414
|
+
data-variant={variant}
|
|
415
|
+
className={cn(
|
|
416
|
+
menuPanel,
|
|
417
|
+
"max-h-(--available-height) overflow-y-auto overscroll-contain",
|
|
418
|
+
className,
|
|
419
|
+
)}
|
|
420
|
+
>
|
|
421
|
+
{children}
|
|
422
|
+
</BaseMenu.Popup>
|
|
423
|
+
</BaseMenu.Positioner>
|
|
424
|
+
</BaseMenu.Portal>
|
|
425
|
+
</BaseMenu.SubmenuRoot>
|
|
426
|
+
);
|
|
427
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Check } from "../../icons/Check";
|
|
2
|
+
import { cn } from "../../lib/cn";
|
|
3
|
+
|
|
4
|
+
export type MenuCheckProps = {
|
|
5
|
+
checked: boolean;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 菜单复选项、多选选项行首的小方格。画法同复选框(`checkbox/choice-style.ts`),
|
|
11
|
+
* 缩到 16px:未选是凹陷底 + 描边,已选是反转块 + 对勾;祖先上有
|
|
12
|
+
* `data-choice="diamond"` 时换成菱形,已选是实心的、不画对勾。
|
|
13
|
+
*
|
|
14
|
+
* 纯展示:勾没勾由所在的选项用 `aria-checked` / `aria-selected` 告诉读屏。
|
|
15
|
+
*/
|
|
16
|
+
export function MenuCheck({ checked, disabled = false }: MenuCheckProps) {
|
|
17
|
+
return (
|
|
18
|
+
// 外面这一层是定位盒:菱形比它小一圈,转 45° 后对角线正好还是 16px
|
|
19
|
+
<span
|
|
20
|
+
aria-hidden="true"
|
|
21
|
+
data-checked={checked ? "" : undefined}
|
|
22
|
+
className="flex size-4 shrink-0 items-center justify-center"
|
|
23
|
+
>
|
|
24
|
+
<span
|
|
25
|
+
className={cn(
|
|
26
|
+
"flex size-4 items-center justify-center border-2 transition-colors duration-(--duration-fast) ease-standard choice-diamond:size-3 choice-diamond:rotate-45",
|
|
27
|
+
disabled
|
|
28
|
+
? [
|
|
29
|
+
"border-ink-disabled bg-surface-sunken text-ink-disabled",
|
|
30
|
+
// 菱形方案里"已选"只靠实心表达,禁用了也要保持实心
|
|
31
|
+
checked && "choice-diamond:bg-ink-disabled",
|
|
32
|
+
]
|
|
33
|
+
: checked
|
|
34
|
+
? "border-surface-inverse bg-surface-inverse text-accent-ink-inverse"
|
|
35
|
+
: "border-line-strong bg-surface-sunken",
|
|
36
|
+
)}
|
|
37
|
+
>
|
|
38
|
+
{checked && (
|
|
39
|
+
<Check size={10} strokeWidth={4} className="choice-diamond:hidden" />
|
|
40
|
+
)}
|
|
41
|
+
</span>
|
|
42
|
+
</span>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export {
|
|
2
|
+
DropdownMenu,
|
|
3
|
+
DropdownMenuCheckboxItem,
|
|
4
|
+
DropdownMenuGroup,
|
|
5
|
+
DropdownMenuItem,
|
|
6
|
+
DropdownMenuRadioGroup,
|
|
7
|
+
DropdownMenuRadioItem,
|
|
8
|
+
DropdownMenuSeparator,
|
|
9
|
+
DropdownMenuSub,
|
|
10
|
+
type DropdownMenuAlign,
|
|
11
|
+
type DropdownMenuCheckboxItemProps,
|
|
12
|
+
type DropdownMenuGroupProps,
|
|
13
|
+
type DropdownMenuItemProps,
|
|
14
|
+
type DropdownMenuItemTone,
|
|
15
|
+
type DropdownMenuProps,
|
|
16
|
+
type DropdownMenuRadioGroupProps,
|
|
17
|
+
type DropdownMenuRadioItemProps,
|
|
18
|
+
type DropdownMenuSeparatorProps,
|
|
19
|
+
type DropdownMenuSide,
|
|
20
|
+
type DropdownMenuSubProps,
|
|
21
|
+
type DropdownMenuVariant,
|
|
22
|
+
} from "./DropdownMenu";
|