banhaten 0.1.3 → 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/CHANGELOG.md +45 -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-token-consistency-audit.md +13 -3
- package/docs/design-system/components/README.md +3 -2
- package/docs/design-system/components/accordion.md +16 -0
- package/docs/design-system/components/activity-feed.md +15 -1
- 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 +15 -1
- package/docs/design-system/components/breadcrumbs.md +14 -0
- package/docs/design-system/components/button-group.md +34 -0
- package/docs/design-system/components/button.md +18 -0
- package/docs/design-system/components/card.md +18 -1
- package/docs/design-system/components/carousel.md +20 -3
- package/docs/design-system/components/catalog-components.md +15 -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 +14 -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 +15 -1
- package/docs/design-system/components/page-header.md +15 -1
- package/docs/design-system/components/pagination.md +30 -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 +15 -0
- 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 +15 -0
- package/docs/design-system/components/skeleton.md +18 -0
- package/docs/design-system/components/slideout.md +14 -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 +14 -0
- package/docs/design-system/components/table.md +15 -1
- 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 +14 -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 +44 -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 +166 -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 +29 -15
- package/registry/component-contract.json +40 -0
- package/registry/components/alert-dialog.tsx +1 -1
- package/registry/components/alert.tsx +86 -65
- package/registry/components/autocomplete.tsx +23 -9
- package/registry/components/avatar.tsx +7 -7
- package/registry/components/badge.tsx +1 -1
- package/registry/components/button-group.tsx +321 -4
- package/registry/components/button.tsx +38 -17
- package/registry/components/card.tsx +68 -59
- package/registry/components/carousel.tsx +53 -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 +1 -1
- package/registry/components/context-menu.tsx +1 -1
- package/registry/components/date-picker.tsx +245 -104
- package/registry/components/expanded/ActivityFeed.tsx +6 -15
- package/registry/components/expanded/Banner.tsx +3 -3
- package/registry/components/expanded/Breadcrumbs.tsx +5 -5
- package/registry/components/expanded/CatalogComponentsShowcase.tsx +13 -13
- package/registry/components/expanded/CatalogDivider.tsx +1 -1
- package/registry/components/expanded/EmptyState.tsx +1 -1
- package/registry/components/expanded/FileUpload.tsx +9 -2
- package/registry/components/expanded/OnboardingStepListItem.tsx +2 -4
- package/registry/components/expanded/PageHeader.tsx +13 -13
- package/registry/components/expanded/Slideout.tsx +15 -415
- package/registry/components/expanded/Steps.tsx +4 -4
- package/registry/components/expanded/Table.tsx +116 -69
- package/registry/components/expanded/Timeline.tsx +3 -3
- package/registry/components/expanded/activityFeed.css +1 -1
- package/registry/components/expanded/breadcrumbs.css +1 -1
- package/registry/components/expanded/divider.css +1 -1
- package/registry/components/expanded/emptyState.css +1 -1
- package/registry/components/expanded/fileUpload.css +6 -6
- package/registry/components/expanded/slideout.css +9 -685
- package/registry/components/expanded/steps.css +3 -3
- package/registry/components/expanded/table.css +2 -2
- package/registry/components/expanded/timeline.css +2 -2
- 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 +31 -245
- package/registry/components/menubar.tsx +1 -1
- package/registry/components/modal.tsx +142 -102
- package/registry/components/native-select.tsx +2 -2
- package/registry/components/pagination.tsx +80 -58
- package/registry/components/popover.tsx +162 -664
- package/registry/components/radio-card.tsx +116 -87
- package/registry/components/segmented-control.tsx +87 -0
- package/registry/components/select-content.tsx +2 -2
- package/registry/components/select.tsx +52 -16
- package/registry/components/sheet.tsx +1 -1
- package/registry/components/sidebar.tsx +1 -1
- package/registry/components/skeleton.css +0 -3
- package/registry/components/social-button.tsx +47 -25
- package/registry/components/spinner.tsx +18 -55
- package/registry/components/tabs.css +1 -1
- package/registry/components/tabs.tsx +8 -4
- package/registry/components/tag.tsx +2 -2
- package/registry/components/textarea.tsx +3 -3
- package/registry/components/toast.tsx +16 -11
- package/registry/components/toggle.tsx +2 -2
- package/registry/components/toolbar.tsx +89 -17
- package/registry/components/tooltip.tsx +1 -1
- 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 +7 -7
- 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/banner-demo.tsx +1 -1
- package/registry/examples/expanded/breadcrumbs-demo.tsx +1 -1
- 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 +6 -3
- 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 +1 -1
- 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 +5311 -377
- package/registry/styles/globals.css +2440 -4061
- package/registry.json +119 -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 +4312 -309
- 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
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
1
3
|
import * as React from "react"
|
|
2
4
|
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
5
|
|
|
@@ -9,6 +11,7 @@ const kbdVariants = cva(
|
|
|
9
11
|
"border border-[var(--bh-border-default)] bg-[var(--bh-interactive-soft-default)] text-[var(--bh-content-subtle)]",
|
|
10
12
|
"font-[var(--bh-font-family)] text-[length:var(--bh-text-body-xs-medium-font-size)] font-[var(--bh-text-body-xs-medium-font-weight)]",
|
|
11
13
|
"leading-[var(--bh-text-body-xs-medium-line-height)] tracking-[var(--bh-text-body-xs-medium-letter-spacing)] shadow-[var(--shadow-button-soft)]",
|
|
14
|
+
"data-[disabled=true]:border-[var(--bh-border-disabled)] data-[disabled=true]:bg-[var(--bh-interactive-soft-disabled)] data-[disabled=true]:text-[var(--bh-content-disabled)] data-[disabled=true]:shadow-none",
|
|
12
15
|
],
|
|
13
16
|
{
|
|
14
17
|
variants: {
|
|
@@ -24,14 +27,17 @@ const kbdVariants = cva(
|
|
|
24
27
|
)
|
|
25
28
|
|
|
26
29
|
type KbdProps = React.ComponentProps<"kbd"> &
|
|
27
|
-
VariantProps<typeof kbdVariants>
|
|
30
|
+
VariantProps<typeof kbdVariants> & {
|
|
31
|
+
disabled?: boolean
|
|
32
|
+
}
|
|
28
33
|
|
|
29
34
|
const Kbd = React.forwardRef<HTMLElement, KbdProps>(function Kbd(
|
|
30
|
-
{ className, size, ...props },
|
|
35
|
+
{ className, disabled = false, size, ...props },
|
|
31
36
|
ref
|
|
32
37
|
) {
|
|
33
38
|
return (
|
|
34
39
|
<kbd
|
|
40
|
+
data-disabled={disabled ? "true" : undefined}
|
|
35
41
|
data-slot="kbd"
|
|
36
42
|
ref={ref}
|
|
37
43
|
className={cn(kbdVariants({ size, className }))}
|
|
@@ -40,5 +46,106 @@ const Kbd = React.forwardRef<HTMLElement, KbdProps>(function Kbd(
|
|
|
40
46
|
)
|
|
41
47
|
})
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
type ShortcutPlatform = "auto" | "mac" | "windows" | "linux"
|
|
50
|
+
|
|
51
|
+
type KbdShortcutProps = Omit<React.ComponentProps<"span">, "children"> & {
|
|
52
|
+
disabled?: boolean
|
|
53
|
+
keys: readonly string[]
|
|
54
|
+
platform?: ShortcutPlatform
|
|
55
|
+
size?: KbdProps["size"]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const KbdShortcut = React.forwardRef<HTMLSpanElement, KbdShortcutProps>(
|
|
59
|
+
function KbdShortcut(
|
|
60
|
+
{
|
|
61
|
+
"aria-label": ariaLabel,
|
|
62
|
+
className,
|
|
63
|
+
disabled = false,
|
|
64
|
+
keys,
|
|
65
|
+
platform = "auto",
|
|
66
|
+
size = "sm",
|
|
67
|
+
...props
|
|
68
|
+
},
|
|
69
|
+
ref
|
|
70
|
+
) {
|
|
71
|
+
const [resolvedPlatform, setResolvedPlatform] = React.useState<
|
|
72
|
+
Exclude<ShortcutPlatform, "auto">
|
|
73
|
+
>(platform === "auto" ? "mac" : platform)
|
|
74
|
+
|
|
75
|
+
React.useEffect(() => {
|
|
76
|
+
if (platform !== "auto") {
|
|
77
|
+
setResolvedPlatform(platform)
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
setResolvedPlatform(detectShortcutPlatform())
|
|
82
|
+
}, [platform])
|
|
83
|
+
|
|
84
|
+
const resolvedKeys = keys.map((key) =>
|
|
85
|
+
resolveShortcutKey(key, resolvedPlatform)
|
|
86
|
+
)
|
|
87
|
+
const accessibleLabel =
|
|
88
|
+
ariaLabel ?? resolvedKeys.map(readableShortcutKey).join(" plus ")
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<span
|
|
92
|
+
aria-label={accessibleLabel}
|
|
93
|
+
className={cn(
|
|
94
|
+
"inline-flex shrink-0 items-center gap-[var(--bh-space-xs-4)]",
|
|
95
|
+
className
|
|
96
|
+
)}
|
|
97
|
+
data-disabled={disabled ? "true" : undefined}
|
|
98
|
+
data-platform={resolvedPlatform}
|
|
99
|
+
data-slot="kbd-shortcut"
|
|
100
|
+
dir="ltr"
|
|
101
|
+
ref={ref}
|
|
102
|
+
role="group"
|
|
103
|
+
{...props}
|
|
104
|
+
>
|
|
105
|
+
{resolvedKeys.map((key, index) => (
|
|
106
|
+
<Kbd
|
|
107
|
+
aria-hidden="true"
|
|
108
|
+
disabled={disabled}
|
|
109
|
+
key={`${key}-${index}`}
|
|
110
|
+
size={size}
|
|
111
|
+
>
|
|
112
|
+
{key}
|
|
113
|
+
</Kbd>
|
|
114
|
+
))}
|
|
115
|
+
</span>
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
function detectShortcutPlatform(): Exclude<ShortcutPlatform, "auto"> {
|
|
121
|
+
if (typeof navigator === "undefined") return "mac"
|
|
122
|
+
const platform = navigator.platform.toLowerCase()
|
|
123
|
+
if (
|
|
124
|
+
platform.includes("mac") ||
|
|
125
|
+
platform.includes("iphone") ||
|
|
126
|
+
platform.includes("ipad")
|
|
127
|
+
) {
|
|
128
|
+
return "mac"
|
|
129
|
+
}
|
|
130
|
+
if (platform.includes("linux")) return "linux"
|
|
131
|
+
return "windows"
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function resolveShortcutKey(
|
|
135
|
+
key: string,
|
|
136
|
+
platform: Exclude<ShortcutPlatform, "auto">
|
|
137
|
+
) {
|
|
138
|
+
if (key !== "Mod") return key
|
|
139
|
+
return platform === "mac" ? "⌘" : "Ctrl"
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function readableShortcutKey(key: string) {
|
|
143
|
+
if (key === "⌘") return "Command"
|
|
144
|
+
if (key === "⌥") return "Option"
|
|
145
|
+
if (key === "⇧") return "Shift"
|
|
146
|
+
if (key === "⌃") return "Control"
|
|
147
|
+
return key
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export { Kbd, KbdShortcut, kbdVariants }
|
|
151
|
+
export type { KbdProps, KbdShortcutProps, ShortcutPlatform }
|
|
@@ -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
|
}
|
|
@@ -907,7 +764,7 @@ function MenuProgressSpinner({ className }: { className?: string }) {
|
|
|
907
764
|
fill="none"
|
|
908
765
|
stroke="currentColor"
|
|
909
766
|
strokeLinecap="round"
|
|
910
|
-
strokeWidth="
|
|
767
|
+
strokeWidth="var(--bh-icon-stroke-300)"
|
|
911
768
|
/>
|
|
912
769
|
</svg>
|
|
913
770
|
)
|
|
@@ -1025,81 +882,6 @@ function MenuSeparator({
|
|
|
1025
882
|
return <DropdownMenuPrimitive.Separator asChild>{separator}</DropdownMenuPrimitive.Separator>
|
|
1026
883
|
}
|
|
1027
884
|
|
|
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
885
|
export {
|
|
1104
886
|
Menu,
|
|
1105
887
|
MenuCaption,
|
|
@@ -1117,12 +899,14 @@ export {
|
|
|
1117
899
|
MenuItemTitle,
|
|
1118
900
|
MenuLabel,
|
|
1119
901
|
MenuProgress,
|
|
902
|
+
MenuPreview,
|
|
1120
903
|
MenuPortal,
|
|
1121
904
|
MenuRoot,
|
|
1122
905
|
MenuSeparator,
|
|
1123
906
|
MenuSub,
|
|
1124
907
|
MenuSubContent,
|
|
1125
908
|
MenuSubTrigger,
|
|
909
|
+
MenuSurface,
|
|
1126
910
|
MenuTrigger,
|
|
1127
911
|
menuItemContentVariants,
|
|
1128
912
|
menuItemIconVariants,
|
|
@@ -1146,5 +930,7 @@ export type {
|
|
|
1146
930
|
MenuLabelProps,
|
|
1147
931
|
MenuProgressProps,
|
|
1148
932
|
MenuProps,
|
|
933
|
+
MenuPreviewProps,
|
|
934
|
+
MenuSurfaceProps,
|
|
1149
935
|
MenuWidth,
|
|
1150
936
|
}
|
|
@@ -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>,
|