banhaten 0.1.3 → 0.3.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/CHANGELOG.md +73 -0
- package/CONTRIBUTING.md +44 -0
- package/README.md +65 -13
- package/SECURITY.md +15 -0
- package/banhaten.config.example.json +0 -2
- package/docs/design-system/README.md +9 -0
- package/docs/design-system/adoption-quality-remediation.md +87 -0
- package/docs/design-system/agent-usage.md +13 -0
- package/docs/design-system/appearance-presets.md +6 -1
- package/docs/design-system/component-docs-pattern.md +81 -0
- package/docs/design-system/component-implementation-ownership-audit.md +244 -0
- package/docs/design-system/component-quality-audit-2026-08-20.md +223 -0
- package/docs/design-system/component-showcase-consistency-report.md +2 -0
- package/docs/design-system/component-token-consistency-audit.md +13 -3
- package/docs/design-system/components/README.md +4 -2
- package/docs/design-system/components/accordion.md +16 -0
- package/docs/design-system/components/activity-feed.md +17 -1
- package/docs/design-system/components/activity-heatmap.md +73 -0
- package/docs/design-system/components/alert-dialog.md +17 -0
- package/docs/design-system/components/alert.md +46 -14
- package/docs/design-system/components/aspect-ratio.md +15 -0
- package/docs/design-system/components/attribute.md +16 -0
- package/docs/design-system/components/autocomplete.md +19 -0
- package/docs/design-system/components/avatar.md +18 -0
- package/docs/design-system/components/badge.md +19 -0
- package/docs/design-system/components/banner.md +19 -1
- package/docs/design-system/components/breadcrumbs.md +17 -0
- package/docs/design-system/components/button-group.md +35 -0
- package/docs/design-system/components/button.md +18 -0
- package/docs/design-system/components/card.md +26 -1
- package/docs/design-system/components/carousel.md +20 -3
- package/docs/design-system/components/catalog-components.md +18 -1
- package/docs/design-system/components/chart.md +15 -0
- package/docs/design-system/components/checkbox.md +16 -0
- package/docs/design-system/components/collapsible.md +15 -0
- package/docs/design-system/components/command.md +15 -0
- package/docs/design-system/components/context-menu.md +15 -0
- package/docs/design-system/components/date-picker.md +21 -1
- package/docs/design-system/components/divider.md +16 -0
- package/docs/design-system/components/empty-state.md +17 -0
- package/docs/design-system/components/field.md +17 -0
- package/docs/design-system/components/file-upload.md +18 -0
- package/docs/design-system/components/hover-card.md +15 -0
- package/docs/design-system/components/input-group.md +17 -0
- package/docs/design-system/components/input-otp.md +18 -0
- package/docs/design-system/components/input.md +43 -7
- package/docs/design-system/components/item.md +72 -0
- package/docs/design-system/components/kbd.md +18 -0
- package/docs/design-system/components/label.md +16 -0
- package/docs/design-system/components/menu.md +17 -0
- package/docs/design-system/components/menubar.md +15 -0
- package/docs/design-system/components/modal.md +25 -4
- package/docs/design-system/components/native-select.md +18 -0
- package/docs/design-system/components/navigation-menu.md +15 -0
- package/docs/design-system/components/onboarding-step-list-item.md +16 -1
- package/docs/design-system/components/page-header.md +17 -1
- package/docs/design-system/components/pagination.md +31 -8
- package/docs/design-system/components/popover.md +19 -0
- package/docs/design-system/components/progress-slider.md +18 -0
- package/docs/design-system/components/progress.md +17 -0
- package/docs/design-system/components/radio-card.md +16 -0
- package/docs/design-system/components/radio-group.md +17 -0
- package/docs/design-system/components/resizable.md +15 -0
- package/docs/design-system/components/scroll-area.md +20 -1
- package/docs/design-system/components/segmented-control.md +71 -0
- package/docs/design-system/components/select.md +44 -7
- package/docs/design-system/components/sheet.md +17 -0
- package/docs/design-system/components/sidebar.md +17 -0
- package/docs/design-system/components/skeleton.md +18 -0
- package/docs/design-system/components/slideout.md +17 -0
- package/docs/design-system/components/slider.md +20 -0
- package/docs/design-system/components/social-button.md +21 -3
- package/docs/design-system/components/spinner.md +16 -0
- package/docs/design-system/components/steps.md +19 -0
- package/docs/design-system/components/table.md +36 -4
- package/docs/design-system/components/tabs.md +17 -0
- package/docs/design-system/components/tag.md +17 -0
- package/docs/design-system/components/textarea.md +16 -0
- package/docs/design-system/components/timeline.md +17 -0
- package/docs/design-system/components/toast.md +17 -0
- package/docs/design-system/components/toggle.md +17 -0
- package/docs/design-system/components/toolbar.md +21 -1
- package/docs/design-system/components/tooltip.md +18 -1
- package/docs/design-system/density-and-elevation.md +61 -0
- package/docs/design-system/direction-and-localization.md +23 -0
- package/docs/design-system/form-recipes.md +73 -0
- package/docs/design-system/gap-promotion-policy.md +21 -0
- package/docs/design-system/gap-promotion-report.md +100 -0
- package/docs/design-system/installation.md +68 -0
- package/docs/design-system/launch-comparison-shadcn.md +98 -0
- package/docs/design-system/registry-and-cli.md +30 -7
- package/docs/design-system/release-policy.md +30 -0
- package/docs/design-system/roadmap.md +14 -0
- package/docs/design-system/support-matrix.md +23 -0
- package/docs/design-system/tabs.md +1 -1
- package/docs/design-system/token-governance.md +23 -1
- package/package.json +30 -15
- package/registry/component-contract.json +40 -0
- package/registry/components/alert-dialog.tsx +2 -2
- package/registry/components/alert.tsx +101 -76
- package/registry/components/autocomplete.tsx +23 -9
- package/registry/components/avatar.tsx +14 -10
- package/registry/components/badge.tsx +18 -18
- package/registry/components/button-group.tsx +324 -5
- package/registry/components/button.tsx +38 -17
- package/registry/components/card.tsx +87 -62
- package/registry/components/carousel.tsx +67 -5
- package/registry/components/chart.tsx +36 -3
- package/registry/components/checkbox.tsx +3 -3
- package/registry/components/collapsible.tsx +1 -1
- package/registry/components/command.tsx +38 -4
- package/registry/components/context-menu.tsx +1 -1
- package/registry/components/date-picker.tsx +312 -135
- package/registry/components/expanded/ActivityFeed.tsx +9 -22
- package/registry/components/expanded/ActivityHeatmap.tsx +64 -0
- package/registry/components/expanded/Banner.tsx +3 -4
- package/registry/components/expanded/Breadcrumbs.tsx +8 -5
- package/registry/components/expanded/CatalogComponentsShowcase.tsx +16 -17
- package/registry/components/expanded/CatalogDivider.tsx +4 -3
- package/registry/components/expanded/EmptyState.tsx +1 -1
- package/registry/components/expanded/FileUpload.tsx +16 -7
- package/registry/components/expanded/OnboardingStepListItem.tsx +2 -6
- package/registry/components/expanded/PageHeader.tsx +47 -56
- package/registry/components/expanded/Slideout.tsx +29 -424
- package/registry/components/expanded/Steps.tsx +151 -13
- package/registry/components/expanded/Table.tsx +161 -82
- package/registry/components/expanded/Timeline.tsx +14 -27
- package/registry/components/expanded/activityFeed.css +9 -9
- package/registry/components/expanded/activityHeatmap.css +66 -0
- package/registry/components/expanded/banner.css +21 -11
- package/registry/components/expanded/breadcrumbs.css +2 -2
- package/registry/components/expanded/divider.css +1 -1
- package/registry/components/expanded/emptyState.css +1 -1
- package/registry/components/expanded/fileUpload.css +7 -7
- package/registry/components/expanded/onboardingStepListItem.css +2 -2
- package/registry/components/expanded/pageHeader.css +14 -11
- package/registry/components/expanded/slideout.css +10 -690
- package/registry/components/expanded/steps.css +78 -15
- package/registry/components/expanded/table.css +60 -69
- package/registry/components/expanded/timeline.css +19 -18
- package/registry/components/field.tsx +61 -8
- package/registry/components/input-content.ts +1 -0
- package/registry/components/input-otp.tsx +13 -3
- package/registry/components/input.tsx +68 -43
- package/registry/components/item.tsx +189 -0
- package/registry/components/kbd.tsx +111 -4
- package/registry/components/menu.tsx +87 -251
- package/registry/components/menubar.tsx +1 -1
- package/registry/components/modal.tsx +143 -103
- package/registry/components/native-select.tsx +2 -2
- package/registry/components/navigation-menu.tsx +1 -1
- package/registry/components/pagination.css +36 -0
- package/registry/components/pagination.tsx +88 -64
- package/registry/components/popover.tsx +162 -664
- package/registry/components/progress-slider.tsx +1 -1
- package/registry/components/radio-card.tsx +116 -87
- package/registry/components/resizable.tsx +36 -2
- package/registry/components/scroll-area.tsx +30 -6
- package/registry/components/segmented-control.tsx +87 -0
- package/registry/components/select-content.tsx +2 -2
- package/registry/components/select.tsx +86 -27
- package/registry/components/sheet.tsx +1 -1
- package/registry/components/sidebar.tsx +12 -6
- package/registry/components/skeleton.css +0 -3
- package/registry/components/social-button.tsx +59 -30
- package/registry/components/spinner.tsx +18 -55
- package/registry/components/table-elements.tsx +128 -0
- package/registry/components/tabs.css +1 -1
- package/registry/components/tabs.tsx +13 -14
- package/registry/components/tag.tsx +3 -3
- package/registry/components/textarea.tsx +3 -3
- package/registry/components/toast.tsx +16 -11
- package/registry/components/toggle.tsx +4 -4
- package/registry/components/toolbar.tsx +94 -22
- package/registry/components/tooltip.tsx +2 -2
- package/registry/examples/alert-demo.tsx +25 -5
- package/registry/examples/attribute-demo.tsx +1 -1
- package/registry/examples/autocomplete-demo.tsx +3 -3
- package/registry/examples/avatar-demo.tsx +14 -11
- package/registry/examples/badge-demo.tsx +1 -1
- package/registry/examples/button-demo.tsx +6 -6
- package/registry/examples/button-group-demo.tsx +23 -2
- package/registry/examples/card-demo.tsx +46 -8
- package/registry/examples/carousel-demo.tsx +20 -2
- package/registry/examples/chart-demo.tsx +1 -1
- package/registry/examples/checkbox-demo.tsx +5 -5
- package/registry/examples/collection-filter-bar-demo.tsx +135 -0
- package/registry/examples/command-demo.tsx +3 -3
- package/registry/examples/{expanded/command-bar-demo.tsx → command-palette-demo.tsx} +5 -15
- package/registry/examples/context-menu-demo.tsx +1 -1
- package/registry/examples/control-density-contract-demo.tsx +61 -0
- package/registry/examples/date-picker-demo.tsx +12 -12
- package/registry/examples/divider-demo.tsx +3 -3
- package/registry/examples/expanded/activity-heatmap-demo.tsx +29 -0
- package/registry/examples/expanded/banner-demo.tsx +1 -1
- package/registry/examples/expanded/breadcrumbs-demo.tsx +4 -4
- package/registry/examples/expanded/empty-state-demo.tsx +1 -1
- package/registry/examples/expanded/file-upload-demo.tsx +1 -1
- package/registry/examples/expanded/onboarding-step-demo.tsx +1 -1
- package/registry/examples/expanded/slideout-demo.tsx +2 -2
- package/registry/examples/expanded/steps-demo.tsx +1 -1
- package/registry/examples/expanded/tabs-demo.tsx +1 -1
- package/registry/examples/expanded/timeline-demo.tsx +1 -1
- package/registry/examples/hover-card-demo.tsx +2 -2
- package/registry/examples/input-demo.tsx +6 -6
- package/registry/examples/input-otp-demo.tsx +4 -4
- package/registry/examples/item-demo.tsx +46 -0
- package/registry/examples/kbd-demo.tsx +1 -1
- package/registry/examples/label-demo.tsx +1 -1
- package/registry/examples/menu-demo.tsx +1 -1
- package/registry/examples/modal-demo.tsx +67 -9
- package/registry/examples/navigation-menu-demo.tsx +1 -1
- package/registry/examples/pagination-demo.tsx +10 -7
- package/registry/examples/popover-demo.tsx +19 -19
- package/registry/examples/progress-demo.tsx +1 -1
- package/registry/examples/progress-slider-demo.tsx +1 -1
- package/registry/examples/radio-card-demo.tsx +4 -4
- package/registry/examples/radio-group-demo.tsx +2 -2
- package/registry/examples/resizable-demo.tsx +1 -1
- package/registry/examples/results-toolbar-demo.tsx +44 -0
- package/registry/examples/scroll-area-demo.tsx +3 -3
- package/registry/examples/segmented-control-demo.tsx +39 -0
- package/registry/examples/select-demo.tsx +1 -1
- package/registry/examples/sidebar-demo.tsx +5 -3
- package/registry/examples/slider-demo.tsx +3 -3
- package/registry/examples/social-button-demo.tsx +18 -8
- package/registry/examples/spinner-demo.tsx +13 -13
- package/registry/examples/tabs-demo.tsx +1 -1
- package/registry/examples/tag-demo.tsx +1 -1
- package/registry/examples/textarea-demo.tsx +4 -4
- package/registry/examples/toast-demo.tsx +1 -1
- package/registry/examples/toggle-demo.tsx +6 -6
- package/registry/examples/tooltip-demo.tsx +4 -4
- package/registry/guidance/USAGE.md +27 -0
- package/registry/hooks/use-direction.ts +193 -13
- package/registry/index.json +5668 -375
- package/registry/styles/globals.css +2404 -3999
- package/registry.json +156 -149
- package/schema/config.schema.json +11 -8
- package/schema/registry.schema.json +150 -45
- package/schema/tokens.schema.json +134 -34
- package/src/cli/command-line.js +194 -0
- package/src/cli/config-editors.js +9 -0
- package/src/cli/doctor.js +399 -0
- package/src/cli/file-transaction.js +295 -0
- package/src/cli/global-css.js +153 -0
- package/src/cli/index.js +567 -1613
- package/src/cli/install-plan.js +592 -0
- package/src/cli/install-receipt.js +171 -0
- package/src/cli/jsonc.js +203 -0
- package/src/cli/package-install-state.js +254 -0
- package/src/cli/package-manager.js +238 -0
- package/src/cli/package-plan.js +185 -0
- package/src/cli/package-version.js +97 -0
- package/src/cli/registry-query.js +207 -0
- package/src/cli/reporting.js +55 -0
- package/src/cli/schema-validation.js +19 -0
- package/src/cli/source-scanner.js +686 -0
- package/src/cli/vite-config.js +478 -0
- package/tokens/banhaten.tokens.json +4444 -331
- package/docs/design-system/components/command-bar.md +0 -57
- package/registry/components/expanded/BannerBoard.tsx +0 -225
- package/registry/components/expanded/CatalogTag.tsx +0 -85
- package/registry/components/expanded/CommandBar.tsx +0 -386
- package/registry/components/expanded/IconExplorer.tsx +0 -612
- package/registry/components/expanded/TimelineShowcase.tsx +0 -158
- package/registry/components/expanded/commandBar.css +0 -470
- package/registry/components/expanded/iconExplorer.css +0 -764
- package/registry/components/expanded/iconPacks.ts +0 -866
- package/registry/components/expanded/tag.css +0 -148
- package/registry/examples/expanded/icons-demo.tsx +0 -5
- package/tokens/Color mode.zip +0 -0
- package/tokens/Numbers.zip +0 -0
- package/tokens/Radius.zip +0 -0
- package/tokens/Theme.zip +0 -0
|
@@ -4,39 +4,21 @@ import { Slot } from "@radix-ui/react-slot"
|
|
|
4
4
|
import { cva, type VariantProps } from "class-variance-authority"
|
|
5
5
|
|
|
6
6
|
import { cn } from "@/lib/utils"
|
|
7
|
+
import { useResolvedDirection } from "@/hooks/use-direction"
|
|
7
8
|
|
|
8
9
|
type MenuWidth = "menu" | "default" | "multiline" | "action" | "auto"
|
|
9
10
|
type MenuItemKind = "default" | "multiline" | "action" | "progress" | "button"
|
|
10
11
|
type MenuItemState = "default" | "disabled"
|
|
11
12
|
type MenuItemIconPosition = "leading" | "trailing"
|
|
12
13
|
type MenuItemAvatarSize = "sm" | "lg"
|
|
13
|
-
type MenuCollisionShift = {
|
|
14
|
-
x: number
|
|
15
|
-
y: number
|
|
16
|
-
}
|
|
17
14
|
type MenuCollisionPadding = NonNullable<
|
|
18
15
|
React.ComponentProps<typeof DropdownMenuPrimitive.Content>["collisionPadding"]
|
|
19
16
|
>
|
|
20
|
-
type MenuCollisionPaddingSides = {
|
|
21
|
-
bottom: number
|
|
22
|
-
left: number
|
|
23
|
-
right: number
|
|
24
|
-
top: number
|
|
25
|
-
}
|
|
26
|
-
type MenuResizeObserverConstructor = new (
|
|
27
|
-
callback: () => void
|
|
28
|
-
) => {
|
|
29
|
-
disconnect: () => void
|
|
30
|
-
observe: (target: Element) => void
|
|
31
|
-
}
|
|
32
17
|
|
|
33
18
|
// Default Radix collisionPadding mirrors --bh-space-md-8.
|
|
34
19
|
const MENU_COLLISION_PADDING_PX = 8
|
|
35
20
|
// Radix sideOffset is numeric; this mirrors --bh-space-xs-4.
|
|
36
21
|
const MENU_SUBCONTENT_SIDE_OFFSET_PX = 4
|
|
37
|
-
const zeroMenuCollisionShift: MenuCollisionShift = { x: 0, y: 0 }
|
|
38
|
-
const useSafeLayoutEffect =
|
|
39
|
-
typeof window === "undefined" ? React.useEffect : React.useLayoutEffect
|
|
40
22
|
|
|
41
23
|
const menuVariants = cva(
|
|
42
24
|
"flex flex-col items-start gap-[var(--bh-menu-gap)] overflow-hidden rounded-[var(--bh-menu-radius)] bg-[var(--bh-menu-bg)] py-[var(--bh-menu-padding-y)] shadow-[var(--shadow-menu)]",
|
|
@@ -119,14 +101,7 @@ const menuItemIconVariants = cva(
|
|
|
119
101
|
}
|
|
120
102
|
)
|
|
121
103
|
|
|
122
|
-
type MenuProps = React.ComponentProps<
|
|
123
|
-
VariantProps<typeof menuVariants> & {
|
|
124
|
-
asChild?: boolean
|
|
125
|
-
defaultOpen?: boolean
|
|
126
|
-
modal?: boolean
|
|
127
|
-
onOpenChange?: (open: boolean) => void
|
|
128
|
-
open?: boolean
|
|
129
|
-
}
|
|
104
|
+
type MenuProps = React.ComponentProps<typeof DropdownMenuPrimitive.Root>
|
|
130
105
|
|
|
131
106
|
type MenuContentProps = React.ComponentProps<typeof DropdownMenuPrimitive.Content> &
|
|
132
107
|
VariantProps<typeof menuVariants> & {
|
|
@@ -143,147 +118,38 @@ const MenuSub = DropdownMenuPrimitive.Sub
|
|
|
143
118
|
const MenuTrigger = DropdownMenuPrimitive.Trigger
|
|
144
119
|
const MenuStaticContext = React.createContext(false)
|
|
145
120
|
|
|
121
|
+
function Menu({ dir, ...props }: MenuProps) {
|
|
122
|
+
const directionBoundaryRef = React.useRef<HTMLSpanElement | null>(null)
|
|
123
|
+
const resolvedDirection = useResolvedDirection(dir, directionBoundaryRef)
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<span className="contents" data-slot="menu-root-boundary" ref={directionBoundaryRef}>
|
|
127
|
+
<MenuRoot dir={resolvedDirection} {...props} />
|
|
128
|
+
</span>
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
|
|
146
132
|
type MenuSurfaceProps = React.ComponentProps<"div"> &
|
|
147
133
|
VariantProps<typeof menuVariants> & {
|
|
148
134
|
asChild?: boolean
|
|
149
|
-
avoidViewportNudge?: boolean
|
|
150
|
-
collisionPadding?: MenuCollisionPadding
|
|
151
135
|
}
|
|
152
136
|
|
|
153
137
|
const MenuSurface = React.forwardRef<HTMLDivElement, MenuSurfaceProps>(function MenuSurface({
|
|
154
138
|
asChild = false,
|
|
155
|
-
avoidViewportNudge = true,
|
|
156
139
|
children,
|
|
157
140
|
className,
|
|
158
|
-
collisionPadding = MENU_COLLISION_PADDING_PX,
|
|
159
141
|
role = "menu",
|
|
160
|
-
style,
|
|
161
142
|
width,
|
|
162
143
|
...props
|
|
163
144
|
}, ref) {
|
|
164
145
|
const Comp = asChild ? Slot : "div"
|
|
165
|
-
const surfaceRef = React.useRef<HTMLDivElement | null>(null)
|
|
166
|
-
const collisionShiftRef = React.useRef(zeroMenuCollisionShift)
|
|
167
|
-
const [collisionShift, setCollisionShift] = React.useState(
|
|
168
|
-
zeroMenuCollisionShift
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
function updateCollisionShift(nextShift: MenuCollisionShift) {
|
|
172
|
-
collisionShiftRef.current = nextShift
|
|
173
|
-
setCollisionShift((currentShift) =>
|
|
174
|
-
isSameMenuCollisionShift(currentShift, nextShift)
|
|
175
|
-
? currentShift
|
|
176
|
-
: nextShift
|
|
177
|
-
)
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function setSurfaceRef(node: HTMLDivElement | null) {
|
|
181
|
-
surfaceRef.current = node
|
|
182
|
-
|
|
183
|
-
if (typeof ref === "function") {
|
|
184
|
-
ref(node)
|
|
185
|
-
} else if (ref) {
|
|
186
|
-
ref.current = node
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
useSafeLayoutEffect(() => {
|
|
191
|
-
if (!avoidViewportNudge) {
|
|
192
|
-
updateCollisionShift(zeroMenuCollisionShift)
|
|
193
|
-
return
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const node = surfaceRef.current
|
|
197
|
-
if (!node) return
|
|
198
|
-
const surfaceNode: HTMLDivElement = node
|
|
199
|
-
|
|
200
|
-
const ownerWindow = surfaceNode.ownerDocument.defaultView
|
|
201
|
-
if (!ownerWindow) return
|
|
202
|
-
const surfaceWindow: Window = ownerWindow
|
|
203
|
-
|
|
204
|
-
let animationFrame = 0
|
|
205
|
-
const resolvedCollisionPadding = resolveMenuCollisionPadding(collisionPadding)
|
|
206
|
-
const requestFrame =
|
|
207
|
-
typeof surfaceWindow.requestAnimationFrame === "function"
|
|
208
|
-
? surfaceWindow.requestAnimationFrame.bind(surfaceWindow)
|
|
209
|
-
: (callback: FrameRequestCallback) =>
|
|
210
|
-
surfaceWindow.setTimeout(
|
|
211
|
-
() => callback(surfaceWindow.performance.now()),
|
|
212
|
-
0
|
|
213
|
-
)
|
|
214
|
-
const cancelFrame =
|
|
215
|
-
typeof surfaceWindow.cancelAnimationFrame === "function"
|
|
216
|
-
? surfaceWindow.cancelAnimationFrame.bind(surfaceWindow)
|
|
217
|
-
: surfaceWindow.clearTimeout.bind(surfaceWindow)
|
|
218
|
-
|
|
219
|
-
function measureCollisionShift() {
|
|
220
|
-
animationFrame = 0
|
|
221
|
-
|
|
222
|
-
const rect = surfaceNode.getBoundingClientRect()
|
|
223
|
-
const currentShift = collisionShiftRef.current
|
|
224
|
-
const unshiftedRect = {
|
|
225
|
-
bottom: rect.bottom - currentShift.y,
|
|
226
|
-
left: rect.left - currentShift.x,
|
|
227
|
-
right: rect.right - currentShift.x,
|
|
228
|
-
top: rect.top - currentShift.y,
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
updateCollisionShift(
|
|
232
|
-
getMenuCollisionShift({
|
|
233
|
-
collisionPadding: resolvedCollisionPadding,
|
|
234
|
-
rect: unshiftedRect,
|
|
235
|
-
viewportHeight: surfaceWindow.innerHeight,
|
|
236
|
-
viewportWidth: surfaceWindow.innerWidth,
|
|
237
|
-
})
|
|
238
|
-
)
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function queueCollisionShiftMeasure() {
|
|
242
|
-
if (animationFrame) {
|
|
243
|
-
cancelFrame(animationFrame)
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
animationFrame = requestFrame(measureCollisionShift)
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
queueCollisionShiftMeasure()
|
|
250
|
-
surfaceWindow.addEventListener("resize", queueCollisionShiftMeasure)
|
|
251
|
-
surfaceWindow.addEventListener("scroll", queueCollisionShiftMeasure, true)
|
|
252
|
-
|
|
253
|
-
const ResizeObserver = (
|
|
254
|
-
surfaceWindow as Window & {
|
|
255
|
-
ResizeObserver?: MenuResizeObserverConstructor
|
|
256
|
-
}
|
|
257
|
-
).ResizeObserver
|
|
258
|
-
const resizeObserver = ResizeObserver
|
|
259
|
-
? new ResizeObserver(queueCollisionShiftMeasure)
|
|
260
|
-
: undefined
|
|
261
|
-
resizeObserver?.observe(surfaceNode)
|
|
262
|
-
|
|
263
|
-
return () => {
|
|
264
|
-
if (animationFrame) {
|
|
265
|
-
cancelFrame(animationFrame)
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
surfaceWindow.removeEventListener("resize", queueCollisionShiftMeasure)
|
|
269
|
-
surfaceWindow.removeEventListener("scroll", queueCollisionShiftMeasure, true)
|
|
270
|
-
resizeObserver?.disconnect()
|
|
271
|
-
}
|
|
272
|
-
}, [avoidViewportNudge, collisionPadding, width])
|
|
273
146
|
|
|
274
147
|
return (
|
|
275
148
|
<Comp
|
|
276
149
|
data-slot="menu"
|
|
277
150
|
data-width={width ?? "menu"}
|
|
278
|
-
ref={
|
|
151
|
+
ref={ref}
|
|
279
152
|
role={role}
|
|
280
|
-
style={{
|
|
281
|
-
...style,
|
|
282
|
-
transform: mergeMenuTransforms(
|
|
283
|
-
getMenuCollisionTransform(collisionShift),
|
|
284
|
-
style?.transform
|
|
285
|
-
),
|
|
286
|
-
}}
|
|
287
153
|
className={cn(
|
|
288
154
|
menuVariants({ width }),
|
|
289
155
|
"max-h-[var(--radix-dropdown-menu-content-available-height)] overflow-y-auto",
|
|
@@ -314,7 +180,6 @@ const MenuContent = React.forwardRef<HTMLDivElement, MenuContentProps>(function
|
|
|
314
180
|
<MenuSurface
|
|
315
181
|
asChild={asChild}
|
|
316
182
|
className={className}
|
|
317
|
-
collisionPadding={collisionPadding}
|
|
318
183
|
ref={ref}
|
|
319
184
|
role={role}
|
|
320
185
|
width={width}
|
|
@@ -345,7 +210,6 @@ const MenuSubContent = React.forwardRef<HTMLDivElement, MenuSubContentProps>(fun
|
|
|
345
210
|
<MenuSurface
|
|
346
211
|
asChild={asChild}
|
|
347
212
|
className={className}
|
|
348
|
-
collisionPadding={collisionPadding}
|
|
349
213
|
ref={ref}
|
|
350
214
|
role={role}
|
|
351
215
|
width={width}
|
|
@@ -356,37 +220,30 @@ const MenuSubContent = React.forwardRef<HTMLDivElement, MenuSubContentProps>(fun
|
|
|
356
220
|
)
|
|
357
221
|
})
|
|
358
222
|
|
|
359
|
-
|
|
223
|
+
type MenuPreviewProps = React.ComponentProps<"div"> &
|
|
224
|
+
VariantProps<typeof menuVariants> & {
|
|
225
|
+
asChild?: boolean
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function MenuPreview({
|
|
360
229
|
children,
|
|
361
230
|
className,
|
|
362
|
-
defaultOpen = true,
|
|
363
|
-
modal = false,
|
|
364
|
-
onOpenChange,
|
|
365
|
-
open,
|
|
366
231
|
role = "menu",
|
|
367
232
|
width,
|
|
368
233
|
asChild = false,
|
|
369
234
|
...props
|
|
370
|
-
}:
|
|
371
|
-
const Comp = asChild ? Slot : "div"
|
|
372
|
-
const isOpen = open ?? defaultOpen
|
|
373
|
-
|
|
374
|
-
void modal
|
|
375
|
-
void onOpenChange
|
|
376
|
-
|
|
377
|
-
if (!isOpen) return null
|
|
378
|
-
|
|
235
|
+
}: MenuPreviewProps) {
|
|
379
236
|
return (
|
|
380
237
|
<MenuStaticContext.Provider value>
|
|
381
|
-
<
|
|
382
|
-
|
|
383
|
-
|
|
238
|
+
<MenuSurface
|
|
239
|
+
asChild={asChild}
|
|
240
|
+
className={className}
|
|
384
241
|
role={role}
|
|
385
|
-
|
|
242
|
+
width={width}
|
|
386
243
|
{...props}
|
|
387
244
|
>
|
|
388
245
|
{children}
|
|
389
|
-
</
|
|
246
|
+
</MenuSurface>
|
|
390
247
|
</MenuStaticContext.Provider>
|
|
391
248
|
)
|
|
392
249
|
}
|
|
@@ -426,11 +283,12 @@ type MenuSubTriggerProps = React.ComponentProps<"div"> &
|
|
|
426
283
|
}
|
|
427
284
|
|
|
428
285
|
const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(function MenuItem({
|
|
286
|
+
"aria-checked": ariaChecked,
|
|
429
287
|
className,
|
|
430
288
|
kind = "default",
|
|
431
289
|
disabled = false,
|
|
432
290
|
asChild = false,
|
|
433
|
-
role
|
|
291
|
+
role,
|
|
434
292
|
tabIndex,
|
|
435
293
|
children,
|
|
436
294
|
onSelect,
|
|
@@ -441,15 +299,23 @@ const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(function MenuIt
|
|
|
441
299
|
const isStaticMenu = React.useContext(MenuStaticContext)
|
|
442
300
|
const selectedKind: MenuItemKind = kind ?? "default"
|
|
443
301
|
const isDisabled = disabled
|
|
302
|
+
const switchState = findMenuItemSwitchState(children)
|
|
303
|
+
const resolvedRole = role ??
|
|
304
|
+
(switchState === undefined ? "menuitem" : "menuitemcheckbox")
|
|
305
|
+
const resolvedChecked =
|
|
306
|
+
resolvedRole === "menuitemcheckbox"
|
|
307
|
+
? (ariaChecked ?? switchState ?? false)
|
|
308
|
+
: undefined
|
|
444
309
|
|
|
445
310
|
const item = (
|
|
446
311
|
<Comp
|
|
312
|
+
aria-checked={resolvedChecked}
|
|
447
313
|
aria-disabled={isDisabled ? true : undefined}
|
|
448
314
|
data-disabled={isDisabled ? "true" : "false"}
|
|
449
315
|
data-kind={selectedKind}
|
|
450
316
|
data-slot="menu-item"
|
|
451
317
|
ref={ref}
|
|
452
|
-
role={
|
|
318
|
+
role={resolvedRole}
|
|
453
319
|
tabIndex={isDisabled ? -1 : (tabIndex ?? 0)}
|
|
454
320
|
className={cn(menuItemVariants({ kind: selectedKind, className }))}
|
|
455
321
|
{...props}
|
|
@@ -462,6 +328,20 @@ const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(function MenuIt
|
|
|
462
328
|
|
|
463
329
|
if (isStaticMenu) return item
|
|
464
330
|
|
|
331
|
+
if (switchState !== undefined) {
|
|
332
|
+
return (
|
|
333
|
+
<DropdownMenuPrimitive.CheckboxItem
|
|
334
|
+
asChild
|
|
335
|
+
checked={switchState}
|
|
336
|
+
disabled={isDisabled}
|
|
337
|
+
onSelect={onSelect}
|
|
338
|
+
textValue={textValue ?? getMenuItemTextValue(children)}
|
|
339
|
+
>
|
|
340
|
+
{item}
|
|
341
|
+
</DropdownMenuPrimitive.CheckboxItem>
|
|
342
|
+
)
|
|
343
|
+
}
|
|
344
|
+
|
|
465
345
|
return (
|
|
466
346
|
<DropdownMenuPrimitive.Item
|
|
467
347
|
asChild
|
|
@@ -476,6 +356,27 @@ const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(function MenuIt
|
|
|
476
356
|
|
|
477
357
|
MenuItem.displayName = "MenuItem"
|
|
478
358
|
|
|
359
|
+
function findMenuItemSwitchState(children: React.ReactNode): boolean | undefined {
|
|
360
|
+
let state: boolean | undefined
|
|
361
|
+
|
|
362
|
+
React.Children.forEach(children, (child) => {
|
|
363
|
+
if (state !== undefined || !React.isValidElement(child)) return
|
|
364
|
+
|
|
365
|
+
const element = child as React.ReactElement<{
|
|
366
|
+
active?: boolean
|
|
367
|
+
children?: React.ReactNode
|
|
368
|
+
}>
|
|
369
|
+
if (element.type === MenuItemSwitch) {
|
|
370
|
+
state = Boolean(element.props.active)
|
|
371
|
+
return
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
state = findMenuItemSwitchState(element.props.children)
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
return state
|
|
378
|
+
}
|
|
379
|
+
|
|
479
380
|
const MenuSubTrigger = React.forwardRef<HTMLDivElement, MenuSubTriggerProps>(function MenuSubTrigger({
|
|
480
381
|
className,
|
|
481
382
|
kind = "default",
|
|
@@ -719,7 +620,10 @@ function MenuItemBadge({
|
|
|
719
620
|
)
|
|
720
621
|
}
|
|
721
622
|
|
|
722
|
-
type MenuItemSwitchProps = Omit<
|
|
623
|
+
type MenuItemSwitchProps = Omit<
|
|
624
|
+
React.ComponentProps<"span">,
|
|
625
|
+
"aria-checked" | "aria-label" | "aria-labelledby" | "role"
|
|
626
|
+
> & {
|
|
723
627
|
active?: boolean
|
|
724
628
|
disabled?: boolean
|
|
725
629
|
}
|
|
@@ -732,12 +636,10 @@ function MenuItemSwitch({
|
|
|
732
636
|
}: MenuItemSwitchProps) {
|
|
733
637
|
return (
|
|
734
638
|
<span
|
|
735
|
-
aria-
|
|
736
|
-
aria-disabled={disabled || undefined}
|
|
639
|
+
aria-hidden="true"
|
|
737
640
|
data-active={active ? "true" : "false"}
|
|
738
641
|
data-disabled={disabled ? "true" : "false"}
|
|
739
642
|
data-slot="menu-item-switch"
|
|
740
|
-
role="switch"
|
|
741
643
|
className={cn(
|
|
742
644
|
"flex h-[var(--bh-menu-switch-height)] w-[var(--bh-menu-switch-width)] shrink-0 items-center rounded-[var(--bh-radius-full)] p-[var(--bh-menu-switch-padding)] group-data-[disabled=true]/menu-item:bg-[var(--bh-interactive-switch-disabled)]",
|
|
743
645
|
active
|
|
@@ -820,6 +722,8 @@ function MenuProgress({
|
|
|
820
722
|
...props
|
|
821
723
|
}: MenuProgressProps) {
|
|
822
724
|
const clampedValue = Math.min(100, Math.max(0, value))
|
|
725
|
+
const labelId = React.useId()
|
|
726
|
+
const hasLabel = hasRenderableContent(label)
|
|
823
727
|
|
|
824
728
|
return (
|
|
825
729
|
<div
|
|
@@ -831,6 +735,8 @@ function MenuProgress({
|
|
|
831
735
|
{...props}
|
|
832
736
|
>
|
|
833
737
|
<div
|
|
738
|
+
aria-label={hasLabel ? undefined : "Progress"}
|
|
739
|
+
aria-labelledby={hasLabel ? labelId : undefined}
|
|
834
740
|
aria-valuemax={100}
|
|
835
741
|
aria-valuemin={0}
|
|
836
742
|
aria-valuenow={clampedValue}
|
|
@@ -860,6 +766,7 @@ function MenuProgress({
|
|
|
860
766
|
>
|
|
861
767
|
{hasRenderableContent(label) ? (
|
|
862
768
|
<span
|
|
769
|
+
id={labelId}
|
|
863
770
|
data-slot="menu-progress-label-text"
|
|
864
771
|
dir="auto"
|
|
865
772
|
className="truncate text-[length:var(--bh-text-body-xs-medium-font-size)] font-[var(--bh-text-body-xs-medium-font-weight)] leading-[var(--bh-text-body-xs-medium-line-height)] tracking-[var(--bh-text-body-xs-medium-letter-spacing)] text-[var(--bh-content-default)]"
|
|
@@ -907,7 +814,7 @@ function MenuProgressSpinner({ className }: { className?: string }) {
|
|
|
907
814
|
fill="none"
|
|
908
815
|
stroke="currentColor"
|
|
909
816
|
strokeLinecap="round"
|
|
910
|
-
strokeWidth="
|
|
817
|
+
strokeWidth="var(--bh-icon-stroke-300)"
|
|
911
818
|
/>
|
|
912
819
|
</svg>
|
|
913
820
|
)
|
|
@@ -1025,81 +932,6 @@ function MenuSeparator({
|
|
|
1025
932
|
return <DropdownMenuPrimitive.Separator asChild>{separator}</DropdownMenuPrimitive.Separator>
|
|
1026
933
|
}
|
|
1027
934
|
|
|
1028
|
-
function getMenuCollisionShift({
|
|
1029
|
-
collisionPadding,
|
|
1030
|
-
rect,
|
|
1031
|
-
viewportHeight,
|
|
1032
|
-
viewportWidth,
|
|
1033
|
-
}: {
|
|
1034
|
-
collisionPadding: MenuCollisionPaddingSides
|
|
1035
|
-
rect: Pick<DOMRect, "bottom" | "left" | "right" | "top">
|
|
1036
|
-
viewportHeight: number
|
|
1037
|
-
viewportWidth: number
|
|
1038
|
-
}): MenuCollisionShift {
|
|
1039
|
-
let x = 0
|
|
1040
|
-
let y = 0
|
|
1041
|
-
const minX = collisionPadding.left
|
|
1042
|
-
const minY = collisionPadding.top
|
|
1043
|
-
const maxX = viewportWidth - collisionPadding.right
|
|
1044
|
-
const maxY = viewportHeight - collisionPadding.bottom
|
|
1045
|
-
|
|
1046
|
-
if (rect.left < minX) {
|
|
1047
|
-
x = minX - rect.left
|
|
1048
|
-
} else if (rect.right > maxX) {
|
|
1049
|
-
x = maxX - rect.right
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
if (rect.top < minY) {
|
|
1053
|
-
y = minY - rect.top
|
|
1054
|
-
} else if (rect.bottom > maxY) {
|
|
1055
|
-
y = maxY - rect.bottom
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
return { x: Math.round(x), y: Math.round(y) }
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
function resolveMenuCollisionPadding(
|
|
1062
|
-
collisionPadding: MenuCollisionPadding
|
|
1063
|
-
): MenuCollisionPaddingSides {
|
|
1064
|
-
if (typeof collisionPadding === "number") {
|
|
1065
|
-
const padding = Math.max(0, collisionPadding)
|
|
1066
|
-
|
|
1067
|
-
return {
|
|
1068
|
-
bottom: padding,
|
|
1069
|
-
left: padding,
|
|
1070
|
-
right: padding,
|
|
1071
|
-
top: padding,
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
return {
|
|
1076
|
-
bottom: Math.max(0, collisionPadding.bottom ?? 0),
|
|
1077
|
-
left: Math.max(0, collisionPadding.left ?? 0),
|
|
1078
|
-
right: Math.max(0, collisionPadding.right ?? 0),
|
|
1079
|
-
top: Math.max(0, collisionPadding.top ?? 0),
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
function getMenuCollisionTransform(shift: MenuCollisionShift) {
|
|
1084
|
-
return `translate(${Math.round(shift.x)}px, ${Math.round(shift.y)}px)`
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
function mergeMenuTransforms(
|
|
1088
|
-
collisionTransform: string,
|
|
1089
|
-
customTransform: string | undefined
|
|
1090
|
-
) {
|
|
1091
|
-
return customTransform
|
|
1092
|
-
? `${collisionTransform} ${customTransform}`
|
|
1093
|
-
: collisionTransform
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
function isSameMenuCollisionShift(
|
|
1097
|
-
currentShift: MenuCollisionShift,
|
|
1098
|
-
nextShift: MenuCollisionShift
|
|
1099
|
-
) {
|
|
1100
|
-
return currentShift.x === nextShift.x && currentShift.y === nextShift.y
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
935
|
export {
|
|
1104
936
|
Menu,
|
|
1105
937
|
MenuCaption,
|
|
@@ -1117,12 +949,14 @@ export {
|
|
|
1117
949
|
MenuItemTitle,
|
|
1118
950
|
MenuLabel,
|
|
1119
951
|
MenuProgress,
|
|
952
|
+
MenuPreview,
|
|
1120
953
|
MenuPortal,
|
|
1121
954
|
MenuRoot,
|
|
1122
955
|
MenuSeparator,
|
|
1123
956
|
MenuSub,
|
|
1124
957
|
MenuSubContent,
|
|
1125
958
|
MenuSubTrigger,
|
|
959
|
+
MenuSurface,
|
|
1126
960
|
MenuTrigger,
|
|
1127
961
|
menuItemContentVariants,
|
|
1128
962
|
menuItemIconVariants,
|
|
@@ -1146,5 +980,7 @@ export type {
|
|
|
1146
980
|
MenuLabelProps,
|
|
1147
981
|
MenuProgressProps,
|
|
1148
982
|
MenuProps,
|
|
983
|
+
MenuPreviewProps,
|
|
984
|
+
MenuSurfaceProps,
|
|
1149
985
|
MenuWidth,
|
|
1150
986
|
}
|
|
@@ -78,7 +78,7 @@ const menuContentClassName =
|
|
|
78
78
|
"z-[var(--bh-z-popover)] min-w-[var(--bh-menu-width)] overflow-hidden rounded-[var(--bh-radius-xl-10)] bg-[var(--bh-bg-raised)] p-[var(--bh-space-xs-4)] text-[var(--bh-content-default)] shadow-[var(--shadow-menu)]"
|
|
79
79
|
|
|
80
80
|
const menuItemClassName =
|
|
81
|
-
"relative flex min-h-[var(--bh-menu-item-
|
|
81
|
+
"relative flex min-h-[var(--bh-menu-item-default-height)] cursor-default select-none items-center gap-[var(--bh-space-md-8)] rounded-[var(--bh-radius-md-6)] px-[var(--bh-space-md-8)] py-[var(--bh-space-sm-6)] text-start text-[length:var(--bh-text-body-sm-regular-font-size)] font-[var(--bh-text-body-sm-regular-font-weight)] leading-[var(--bh-text-body-sm-regular-line-height)] tracking-[var(--bh-text-body-sm-regular-letter-spacing)] text-[var(--bh-content-default)] outline-none data-[disabled]:pointer-events-none data-[disabled]:text-[var(--bh-content-disabled)] data-[highlighted]:bg-[var(--bh-menu-item-hover-bg)]"
|
|
82
82
|
|
|
83
83
|
const MenubarContent = React.forwardRef<
|
|
84
84
|
React.ElementRef<typeof MenubarPrimitive.Content>,
|