@bug-on/md3-react 2.0.3 → 3.0.1
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/.turbo/turbo-build.log +42 -0
- package/CHANGELOG.md +69 -0
- package/dist/index.css +178 -0
- package/dist/index.css.d.ts +2 -0
- package/dist/index.d.mts +6135 -0
- package/dist/index.d.ts +6135 -71
- package/dist/index.js +1688 -631
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1600 -564
- package/dist/index.mjs.map +1 -1
- package/dist/material-symbols-cdn.css.d.ts +2 -0
- package/dist/material-symbols-self-hosted.css.d.ts +2 -0
- package/dist/plugin.d.mts +1 -0
- package/dist/plugin.d.ts +1 -0
- package/dist/plugin.js +13 -0
- package/dist/plugin.js.map +1 -0
- package/dist/plugin.mjs +3 -0
- package/dist/plugin.mjs.map +1 -0
- package/dist/typography.css.d.ts +2 -0
- package/package.json +28 -19
- package/scripts/copy-assets.js +115 -0
- package/src/assets/fonts/GoogleSansFlex-VariableFont.woff2 +0 -0
- package/src/assets/fonts/MaterialSymbolsOutlined-VariableFont_FILL,GRAD,opsz,wght.ttf +0 -0
- package/src/assets/fonts/MaterialSymbolsRounded-VariableFont_FILL,GRAD,opsz,wght.ttf +0 -0
- package/src/assets/fonts/MaterialSymbolsSharp-VariableFont_FILL,GRAD,opsz,wght.ttf +0 -0
- package/src/assets/loading-indicator.svg +19 -0
- package/src/assets/material-symbols-cdn.css +65 -0
- package/src/assets/material-symbols-self-hosted.css +90 -0
- package/src/css.d.ts +20 -0
- package/src/hooks/useClickOutside.ts +37 -0
- package/src/hooks/useMediaQuery.ts +28 -0
- package/src/hooks/useRipple.ts +88 -0
- package/src/index.css +23 -0
- package/src/index.ts +349 -0
- package/src/lib/material-symbols-preconnect.tsx +82 -0
- package/src/lib/theme-utils.ts +195 -0
- package/src/lib/utils.ts +6 -0
- package/src/plugin.ts +12 -0
- package/src/test/button.test.tsx +59 -0
- package/src/test/icon.test.tsx +91 -0
- package/src/test/loading-indicator.test.tsx +128 -0
- package/src/test/progress-indicator.test.tsx +306 -0
- package/src/test/setup.ts +80 -0
- package/src/test/typography.test.tsx +206 -0
- package/src/types/index.ts +7 -0
- package/src/types/md3.ts +31 -0
- package/src/ui/Text.tsx +60 -0
- package/src/ui/__snapshots__/divider.test.tsx.snap +63 -0
- package/src/ui/app-bar/app-bar-column.tsx +99 -0
- package/src/ui/app-bar/app-bar-item-button.tsx +71 -0
- package/src/ui/app-bar/app-bar-items.test.tsx +89 -0
- package/src/ui/app-bar/app-bar-overflow-indicator.tsx +108 -0
- package/src/ui/app-bar/app-bar-row.tsx +104 -0
- package/src/ui/app-bar/app-bar.test.tsx +87 -0
- package/src/ui/app-bar/app-bar.tokens.ts +223 -0
- package/src/ui/app-bar/app-bar.types.ts +441 -0
- package/src/ui/app-bar/bottom-app-bar.test.tsx +42 -0
- package/src/ui/app-bar/bottom-app-bar.tsx +84 -0
- package/src/ui/app-bar/docked-toolbar.test.tsx +34 -0
- package/src/ui/app-bar/docked-toolbar.tsx +54 -0
- package/src/ui/app-bar/flexible-app-bar.test.tsx +75 -0
- package/src/ui/app-bar/hooks/use-app-bar-scroll.ts +110 -0
- package/src/ui/app-bar/hooks/use-flexible-app-bar.ts +123 -0
- package/{dist/ui/app-bar/index.d.ts → src/ui/app-bar/index.ts} +35 -2
- package/src/ui/app-bar/large-flexible-app-bar.tsx +165 -0
- package/src/ui/app-bar/medium-flexible-app-bar.tsx +167 -0
- package/src/ui/app-bar/search-app-bar.test.tsx +49 -0
- package/src/ui/app-bar/search-app-bar.tsx +176 -0
- package/src/ui/app-bar/search-view.tsx +227 -0
- package/src/ui/app-bar/small-app-bar.test.tsx +48 -0
- package/src/ui/app-bar/small-app-bar.tsx +203 -0
- package/src/ui/badge.test.tsx +345 -0
- package/src/ui/badge.tsx +282 -0
- package/src/ui/button-group.test.tsx +71 -0
- package/src/ui/button-group.tsx +350 -0
- package/src/ui/button.test.tsx +306 -0
- package/src/ui/button.tsx +665 -0
- package/src/ui/card.test.tsx +187 -0
- package/src/ui/card.tsx +259 -0
- package/src/ui/checkbox.test.tsx +423 -0
- package/src/ui/checkbox.tsx +525 -0
- package/src/ui/chip.test.tsx +292 -0
- package/src/ui/chip.tsx +548 -0
- package/src/ui/code-block.tsx +219 -0
- package/src/ui/dialog.test.tsx +300 -0
- package/src/ui/dialog.tsx +384 -0
- package/src/ui/divider.test.tsx +314 -0
- package/src/ui/divider.tsx +412 -0
- package/src/ui/drawer.tsx +240 -0
- package/src/ui/fab-menu.test.tsx +494 -0
- package/src/ui/fab-menu.tsx +739 -0
- package/src/ui/fab.test.tsx +232 -0
- package/src/ui/fab.tsx +505 -0
- package/src/ui/icon-button.test.tsx +515 -0
- package/src/ui/icon-button.tsx +525 -0
- package/src/ui/icon.test.tsx +197 -0
- package/src/ui/icon.tsx +179 -0
- package/src/ui/loading-indicator.test.tsx +73 -0
- package/src/ui/loading-indicator.tsx +312 -0
- package/src/ui/menu/context-menu.tsx +275 -0
- package/src/ui/menu/index.ts +77 -0
- package/src/ui/menu/menu-animations.ts +102 -0
- package/src/ui/menu/menu-context.tsx +99 -0
- package/src/ui/menu/menu-divider.tsx +47 -0
- package/src/ui/menu/menu-group.tsx +200 -0
- package/src/ui/menu/menu-item.tsx +294 -0
- package/src/ui/menu/menu-tokens.ts +208 -0
- package/src/ui/menu/menu-types.ts +313 -0
- package/src/ui/menu/menu.test.tsx +624 -0
- package/src/ui/menu/menu.tsx +289 -0
- package/src/ui/menu/sub-menu.tsx +223 -0
- package/src/ui/menu/vertical-menu.tsx +382 -0
- package/src/ui/navigation-rail.test.tsx +404 -0
- package/src/ui/navigation-rail.tsx +607 -0
- package/src/ui/progress-indicator/circular.tsx +248 -0
- package/src/ui/progress-indicator/hooks.ts +51 -0
- package/{dist/ui/progress-indicator/index.d.ts → src/ui/progress-indicator/index.tsx} +20 -2
- package/src/ui/progress-indicator/linear-flat.tsx +83 -0
- package/src/ui/progress-indicator/linear-wavy.tsx +243 -0
- package/src/ui/progress-indicator/linear.tsx +143 -0
- package/src/ui/progress-indicator/types.ts +158 -0
- package/src/ui/progress-indicator/utils.ts +73 -0
- package/src/ui/radio-button.test.tsx +407 -0
- package/src/ui/radio-button.tsx +551 -0
- package/src/ui/ripple.test.tsx +72 -0
- package/src/ui/ripple.tsx +234 -0
- package/src/ui/scroll-area.test.tsx +58 -0
- package/src/ui/scroll-area.tsx +139 -0
- package/src/ui/search/animated-placeholder.tsx +145 -0
- package/src/ui/search/hooks/use-search-keyboard.test.ts +202 -0
- package/src/ui/search/hooks/use-search-keyboard.ts +104 -0
- package/src/ui/search/hooks/use-search-view-focus.test.ts +96 -0
- package/src/ui/search/hooks/use-search-view-focus.ts +24 -0
- package/src/ui/search/index.ts +44 -0
- package/src/ui/search/search-bar.tsx +220 -0
- package/src/ui/search/search-context.tsx +42 -0
- package/src/ui/search/search-view-docked.tsx +194 -0
- package/src/ui/search/search-view-fullscreen.tsx +247 -0
- package/src/ui/search/search.test.tsx +233 -0
- package/src/ui/search/search.tokens.ts +134 -0
- package/src/ui/search/search.tsx +131 -0
- package/src/ui/search/search.types.ts +154 -0
- package/src/ui/search/trailing-action.tsx +49 -0
- package/src/ui/shared/constants.ts +135 -0
- package/{dist/ui/shared/touch-target.d.ts → src/ui/shared/touch-target.tsx} +13 -1
- package/src/ui/slider/hooks/useSliderMath.ts +195 -0
- package/{dist/ui/slider/index.d.ts → src/ui/slider/index.ts} +12 -1
- package/src/ui/slider/range-slider.tsx +561 -0
- package/src/ui/slider/slider-thumb.tsx +379 -0
- package/src/ui/slider/slider-track.tsx +912 -0
- package/src/ui/slider/slider.tokens.ts +189 -0
- package/src/ui/slider/slider.tsx +259 -0
- package/src/ui/slider/slider.types.ts +288 -0
- package/src/ui/snackbar/index.ts +20 -0
- package/src/ui/snackbar/snackbar.test.tsx +338 -0
- package/src/ui/snackbar/snackbar.tsx +476 -0
- package/{dist/ui/switch/index.d.ts → src/ui/switch/index.ts} +1 -0
- package/src/ui/switch/switch.stories.tsx +309 -0
- package/src/ui/switch/switch.test.tsx +243 -0
- package/src/ui/switch/switch.tokens.ts +89 -0
- package/src/ui/switch/switch.tsx +504 -0
- package/src/ui/switch/switch.types.ts +62 -0
- package/{dist/ui/tabs/index.d.ts → src/ui/tabs/index.ts} +8 -1
- package/src/ui/tabs/tab.tsx +407 -0
- package/src/ui/tabs/tabs-content.tsx +89 -0
- package/src/ui/tabs/tabs-list.tsx +146 -0
- package/src/ui/tabs/tabs.test.tsx +290 -0
- package/src/ui/tabs/tabs.tokens.ts +121 -0
- package/src/ui/tabs/tabs.tsx +229 -0
- package/src/ui/tabs/tabs.types.ts +185 -0
- package/{dist/ui/text-field/index.d.ts → src/ui/text-field/index.ts} +8 -1
- package/src/ui/text-field/subcomponents/active-indicator.tsx +67 -0
- package/src/ui/text-field/subcomponents/floating-label.tsx +161 -0
- package/src/ui/text-field/subcomponents/leading-icon.tsx +46 -0
- package/src/ui/text-field/subcomponents/outline-container.tsx +170 -0
- package/src/ui/text-field/subcomponents/prefix-suffix.tsx +59 -0
- package/src/ui/text-field/subcomponents/supporting-text.tsx +145 -0
- package/src/ui/text-field/subcomponents/trailing-icon.tsx +199 -0
- package/src/ui/text-field/text-field.test.tsx +454 -0
- package/src/ui/text-field/text-field.tokens.ts +104 -0
- package/src/ui/text-field/text-field.tsx +548 -0
- package/src/ui/text-field/text-field.types.ts +180 -0
- package/src/ui/theme-provider/index.tsx +215 -0
- package/src/ui/toc.test.tsx +108 -0
- package/src/ui/toc.tsx +172 -0
- package/src/ui/tooltip/plain-tooltip.tsx +63 -0
- package/src/ui/tooltip/rich-tooltip.tsx +94 -0
- package/src/ui/tooltip/tooltip-box.tsx +266 -0
- package/src/ui/tooltip/tooltip-caret-shape.tsx +68 -0
- package/src/ui/tooltip/tooltip.tokens.ts +26 -0
- package/src/ui/tooltip/tooltip.types.ts +70 -0
- package/src/ui/tooltip/use-tooltip-position.ts +208 -0
- package/src/ui/tooltip/use-tooltip-state.ts +41 -0
- package/src/ui/typography/__tests__/typography.test.tsx +170 -0
- package/{dist/ui/typography/index.d.ts → src/ui/typography/index.ts} +21 -3
- package/src/ui/typography/type-scale-tokens.ts +205 -0
- package/src/ui/typography/typography-key-tokens.ts +43 -0
- package/src/ui/typography/typography-tokens.ts +360 -0
- package/src/ui/typography/typography.css +22 -0
- package/src/ui/typography/typography.tsx +559 -0
- package/test-render.tsx +4 -0
- package/test-shadow.html +26 -0
- package/test_output.txt +164 -0
- package/test_output_v2.txt +5 -0
- package/tsconfig.build.json +10 -0
- package/tsconfig.json +18 -0
- package/tsup.config.ts +20 -0
- package/vitest.config.ts +11 -0
- package/dist/hooks/useClickOutside.d.ts +0 -8
- package/dist/hooks/useMediaQuery.d.ts +0 -11
- package/dist/hooks/useRipple.d.ts +0 -26
- package/dist/lib/material-symbols-preconnect.d.ts +0 -42
- package/dist/lib/theme-utils.d.ts +0 -63
- package/dist/lib/utils.d.ts +0 -2
- package/dist/types/index.d.ts +0 -1
- package/dist/types/md3.d.ts +0 -14
- package/dist/ui/app-bar/app-bar-column.d.ts +0 -28
- package/dist/ui/app-bar/app-bar-item-button.d.ts +0 -16
- package/dist/ui/app-bar/app-bar-overflow-indicator.d.ts +0 -18
- package/dist/ui/app-bar/app-bar-row.d.ts +0 -36
- package/dist/ui/app-bar/app-bar.tokens.d.ts +0 -184
- package/dist/ui/app-bar/app-bar.types.d.ts +0 -392
- package/dist/ui/app-bar/bottom-app-bar.d.ts +0 -31
- package/dist/ui/app-bar/docked-toolbar.d.ts +0 -25
- package/dist/ui/app-bar/hooks/use-app-bar-scroll.d.ts +0 -42
- package/dist/ui/app-bar/hooks/use-flexible-app-bar.d.ts +0 -37
- package/dist/ui/app-bar/large-flexible-app-bar.d.ts +0 -26
- package/dist/ui/app-bar/medium-flexible-app-bar.d.ts +0 -28
- package/dist/ui/app-bar/search-app-bar.d.ts +0 -43
- package/dist/ui/app-bar/search-view.d.ts +0 -54
- package/dist/ui/app-bar/small-app-bar.d.ts +0 -37
- package/dist/ui/badge.d.ts +0 -125
- package/dist/ui/button-group.d.ts +0 -59
- package/dist/ui/button.d.ts +0 -148
- package/dist/ui/card.d.ts +0 -62
- package/dist/ui/checkbox.d.ts +0 -82
- package/dist/ui/chip.d.ts +0 -110
- package/dist/ui/code-block.d.ts +0 -14
- package/dist/ui/dialog.d.ts +0 -111
- package/dist/ui/divider.d.ts +0 -164
- package/dist/ui/drawer.d.ts +0 -39
- package/dist/ui/dropdown.d.ts +0 -29
- package/dist/ui/fab-menu.d.ts +0 -204
- package/dist/ui/fab.d.ts +0 -162
- package/dist/ui/icon-button.d.ts +0 -131
- package/dist/ui/icon.d.ts +0 -88
- package/dist/ui/loading-indicator.d.ts +0 -42
- package/dist/ui/navigation-rail.d.ts +0 -29
- package/dist/ui/progress-indicator/circular.d.ts +0 -3
- package/dist/ui/progress-indicator/hooks.d.ts +0 -3
- package/dist/ui/progress-indicator/linear-flat.d.ts +0 -10
- package/dist/ui/progress-indicator/linear-wavy.d.ts +0 -18
- package/dist/ui/progress-indicator/linear.d.ts +0 -3
- package/dist/ui/progress-indicator/types.d.ts +0 -151
- package/dist/ui/progress-indicator/utils.d.ts +0 -3
- package/dist/ui/radio-button.d.ts +0 -106
- package/dist/ui/ripple.d.ts +0 -126
- package/dist/ui/scroll-area.d.ts +0 -27
- package/dist/ui/search/animated-placeholder.d.ts +0 -54
- package/dist/ui/search/hooks/use-search-keyboard.d.ts +0 -32
- package/dist/ui/search/hooks/use-search-view-focus.d.ts +0 -6
- package/dist/ui/search/index.d.ts +0 -27
- package/dist/ui/search/search-bar.d.ts +0 -32
- package/dist/ui/search/search-context.d.ts +0 -24
- package/dist/ui/search/search-view-docked.d.ts +0 -25
- package/dist/ui/search/search-view-fullscreen.d.ts +0 -36
- package/dist/ui/search/search.d.ts +0 -50
- package/dist/ui/search/search.tokens.d.ts +0 -112
- package/dist/ui/search/search.types.d.ts +0 -131
- package/dist/ui/search/trailing-action.d.ts +0 -9
- package/dist/ui/shared/constants.d.ts +0 -86
- package/dist/ui/slider/hooks/useSliderMath.d.ts +0 -101
- package/dist/ui/slider/range-slider.d.ts +0 -47
- package/dist/ui/slider/slider-thumb.d.ts +0 -33
- package/dist/ui/slider/slider-track.d.ts +0 -25
- package/dist/ui/slider/slider.d.ts +0 -60
- package/dist/ui/slider/slider.tokens.d.ts +0 -151
- package/dist/ui/slider/slider.types.d.ts +0 -259
- package/dist/ui/snackbar/index.d.ts +0 -6
- package/dist/ui/snackbar/snackbar.d.ts +0 -197
- package/dist/ui/switch/switch.d.ts +0 -30
- package/dist/ui/switch/switch.stories.d.ts +0 -48
- package/dist/ui/switch/switch.tokens.d.ts +0 -67
- package/dist/ui/switch/switch.types.d.ts +0 -59
- package/dist/ui/tabs/tab.d.ts +0 -43
- package/dist/ui/tabs/tabs-content.d.ts +0 -36
- package/dist/ui/tabs/tabs-list.d.ts +0 -40
- package/dist/ui/tabs/tabs.d.ts +0 -60
- package/dist/ui/tabs/tabs.tokens.d.ts +0 -94
- package/dist/ui/tabs/tabs.types.d.ts +0 -172
- package/dist/ui/text-field/subcomponents/active-indicator.d.ts +0 -24
- package/dist/ui/text-field/subcomponents/floating-label.d.ts +0 -43
- package/dist/ui/text-field/subcomponents/leading-icon.d.ts +0 -23
- package/dist/ui/text-field/subcomponents/outline-container.d.ts +0 -42
- package/dist/ui/text-field/subcomponents/prefix-suffix.d.ts +0 -24
- package/dist/ui/text-field/subcomponents/supporting-text.d.ts +0 -37
- package/dist/ui/text-field/subcomponents/trailing-icon.d.ts +0 -41
- package/dist/ui/text-field/text-field.d.ts +0 -49
- package/dist/ui/text-field/text-field.tokens.d.ts +0 -76
- package/dist/ui/text-field/text-field.types.d.ts +0 -126
- package/dist/ui/theme-provider/index.d.ts +0 -48
- package/dist/ui/toc.d.ts +0 -80
- package/dist/ui/tooltip/plain-tooltip.d.ts +0 -2
- package/dist/ui/tooltip/rich-tooltip.d.ts +0 -2
- package/dist/ui/tooltip/tooltip-box.d.ts +0 -2
- package/dist/ui/tooltip/tooltip-caret-shape.d.ts +0 -9
- package/dist/ui/tooltip/tooltip.tokens.d.ts +0 -26
- package/dist/ui/tooltip/tooltip.types.d.ts +0 -56
- package/dist/ui/tooltip/use-tooltip-position.d.ts +0 -8
- package/dist/ui/tooltip/use-tooltip-state.d.ts +0 -2
- package/dist/ui/typography/type-scale-tokens.d.ts +0 -162
- package/dist/ui/typography/typography-key-tokens.d.ts +0 -40
- package/dist/ui/typography/typography-tokens.d.ts +0 -220
- package/dist/ui/typography/typography.d.ts +0 -265
- /package/{dist/hooks/index.d.ts → src/hooks/index.ts} +0 -0
- /package/{dist/ui/tooltip/index.d.ts → src/ui/tooltip/index.ts} +0 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
// ─── MD3 Expressive Menu — MenuItem ─────────────────────────────────────────
|
|
2
|
+
// Shape morphing + Standard/Vibrant color variants + selection state
|
|
3
|
+
// Animation: only selectable items animate the leading icon slot
|
|
4
|
+
import * as ContextMenu from "@radix-ui/react-context-menu";
|
|
5
|
+
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
|
6
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
7
|
+
import { AnimatePresence, m } from "motion/react";
|
|
8
|
+
import * as React from "react";
|
|
9
|
+
import { cn } from "../../lib/utils";
|
|
10
|
+
import { Icon } from "../icon";
|
|
11
|
+
import { CHECK_ICON_VARIANTS, MENU_CHECK_ICON_SIZE } from "./menu-animations";
|
|
12
|
+
import { useMenuContext } from "./menu-context";
|
|
13
|
+
import {
|
|
14
|
+
BASELINE_COLORS,
|
|
15
|
+
BASELINE_ITEM_SHAPE,
|
|
16
|
+
ITEM_SHAPE_CLASSES,
|
|
17
|
+
MENU_BASELINE_ITEM_HORIZONTAL_PADDING,
|
|
18
|
+
STANDARD_COLORS,
|
|
19
|
+
VIBRANT_COLORS,
|
|
20
|
+
} from "./menu-tokens";
|
|
21
|
+
import type {
|
|
22
|
+
MenuItemPosition,
|
|
23
|
+
MenuItemProps,
|
|
24
|
+
MenuVariant,
|
|
25
|
+
} from "./menu-types";
|
|
26
|
+
|
|
27
|
+
// ─── Shape helper ─────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
function getItemShapeClass(
|
|
30
|
+
position: MenuItemPosition,
|
|
31
|
+
selected: boolean,
|
|
32
|
+
isStatic: boolean = false,
|
|
33
|
+
menuVariant: MenuVariant = "expressive",
|
|
34
|
+
): string {
|
|
35
|
+
if (menuVariant === "baseline") return BASELINE_ITEM_SHAPE;
|
|
36
|
+
if (selected) return ITEM_SHAPE_CLASSES.selected;
|
|
37
|
+
// Vertical Menu standalone items have 12px border radius
|
|
38
|
+
if (isStatic && position === "standalone") return "rounded-[12px]";
|
|
39
|
+
return ITEM_SHAPE_CLASSES[position];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ─── MenuItem ─────────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* An interactive item within a Menu or MenuGroup.
|
|
46
|
+
*
|
|
47
|
+
* ### Shape morphing
|
|
48
|
+
* `itemPosition` (auto-set by MenuGroup) controls border-radius. When `selected=true`,
|
|
49
|
+
* shape overrides to `CornerMedium (12px)` with a CSS transition.
|
|
50
|
+
*
|
|
51
|
+
* ### Leading icon animation
|
|
52
|
+
* - **Selectable items** (`selected` prop defined): `AnimatePresence` swaps between
|
|
53
|
+
* a check icon and the user `leadingIcon` using FastSpatial expand + FastEffects fade.
|
|
54
|
+
* - **Static items** (`selected` prop undefined): `leadingIcon` renders at fixed width
|
|
55
|
+
* with no enter/exit animation (matches Android's static rendering).
|
|
56
|
+
*
|
|
57
|
+
* ### Selected state
|
|
58
|
+
* Container color transitions with `transition-colors duration-150` (maps to
|
|
59
|
+
* Android's `animateColorAsState` with `FastEffects` spec).
|
|
60
|
+
*/
|
|
61
|
+
export const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(
|
|
62
|
+
(
|
|
63
|
+
{
|
|
64
|
+
children,
|
|
65
|
+
onClick,
|
|
66
|
+
leadingIcon,
|
|
67
|
+
trailingIcon,
|
|
68
|
+
supportingText,
|
|
69
|
+
trailingText,
|
|
70
|
+
selected,
|
|
71
|
+
disabled = false,
|
|
72
|
+
itemPosition = "standalone",
|
|
73
|
+
colorVariant: propColorVariant,
|
|
74
|
+
keepOpen = false,
|
|
75
|
+
className,
|
|
76
|
+
isSubTrigger,
|
|
77
|
+
value,
|
|
78
|
+
role,
|
|
79
|
+
...rest
|
|
80
|
+
},
|
|
81
|
+
ref,
|
|
82
|
+
) => {
|
|
83
|
+
const {
|
|
84
|
+
menuVariant,
|
|
85
|
+
colorVariant: contextColorVariant,
|
|
86
|
+
menuPrimitive,
|
|
87
|
+
} = useMenuContext();
|
|
88
|
+
const colorVariant = propColorVariant ?? contextColorVariant;
|
|
89
|
+
const colors =
|
|
90
|
+
menuVariant === "baseline"
|
|
91
|
+
? BASELINE_COLORS
|
|
92
|
+
: colorVariant === "vibrant"
|
|
93
|
+
? VIBRANT_COLORS
|
|
94
|
+
: STANDARD_COLORS;
|
|
95
|
+
|
|
96
|
+
const isStaticMenu = menuPrimitive === "static";
|
|
97
|
+
const shapeClass = getItemShapeClass(
|
|
98
|
+
itemPosition,
|
|
99
|
+
!!selected,
|
|
100
|
+
isStaticMenu,
|
|
101
|
+
menuVariant,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// A selectable item is one where `selected` prop is explicitly passed
|
|
105
|
+
// (even if false). Non-selectable items do not animate the leading slot.
|
|
106
|
+
const isSelectable = selected !== undefined && !isSubTrigger;
|
|
107
|
+
|
|
108
|
+
// Determine which Radix primitive to use based on selection state and role
|
|
109
|
+
const isCheckbox =
|
|
110
|
+
role === "menuitemcheckbox" ||
|
|
111
|
+
(selected !== undefined && !role && !isSubTrigger);
|
|
112
|
+
const isRadio = role === "menuitemradio";
|
|
113
|
+
|
|
114
|
+
// Select the correct Radix primitive based on which menu family is active.
|
|
115
|
+
// static → Slot (plain HTML, manages own ARIA)
|
|
116
|
+
// context → @radix-ui/react-context-menu primitives
|
|
117
|
+
// dropdown (default) → @radix-ui/react-dropdown-menu primitives
|
|
118
|
+
const ItemPrimitive =
|
|
119
|
+
isStaticMenu || isSubTrigger
|
|
120
|
+
? Slot
|
|
121
|
+
: menuPrimitive === "context"
|
|
122
|
+
? ((isCheckbox
|
|
123
|
+
? ContextMenu.CheckboxItem
|
|
124
|
+
: isRadio
|
|
125
|
+
? ContextMenu.RadioItem
|
|
126
|
+
: ContextMenu.Item) as React.ElementType)
|
|
127
|
+
: ((isCheckbox
|
|
128
|
+
? DropdownMenu.CheckboxItem
|
|
129
|
+
: isRadio
|
|
130
|
+
? DropdownMenu.RadioItem
|
|
131
|
+
: DropdownMenu.Item) as React.ElementType);
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<ItemPrimitive
|
|
135
|
+
ref={ref}
|
|
136
|
+
{...(isStaticMenu || isSubTrigger
|
|
137
|
+
? {
|
|
138
|
+
role:
|
|
139
|
+
role ||
|
|
140
|
+
(isCheckbox
|
|
141
|
+
? "menuitemcheckbox"
|
|
142
|
+
: isRadio
|
|
143
|
+
? "menuitemradio"
|
|
144
|
+
: "menuitem"),
|
|
145
|
+
"aria-checked": isCheckbox || isRadio ? !!selected : undefined,
|
|
146
|
+
"aria-disabled": disabled ? true : undefined,
|
|
147
|
+
tabIndex: disabled ? -1 : 0,
|
|
148
|
+
onKeyDown: (e: React.KeyboardEvent) => {
|
|
149
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
150
|
+
e.preventDefault();
|
|
151
|
+
onClick?.(e as unknown as React.MouseEvent<HTMLDivElement>);
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
onClick,
|
|
155
|
+
}
|
|
156
|
+
: {
|
|
157
|
+
disabled,
|
|
158
|
+
onSelect: keepOpen ? (e: Event) => e.preventDefault() : undefined,
|
|
159
|
+
onClick,
|
|
160
|
+
...(isCheckbox || isRadio ? { checked: !!selected } : {}),
|
|
161
|
+
...(isRadio ? { value: value ?? "" } : {}),
|
|
162
|
+
asChild: true,
|
|
163
|
+
})}
|
|
164
|
+
>
|
|
165
|
+
<div
|
|
166
|
+
// Role provided by Radix primitives via asChild, or manually set when static
|
|
167
|
+
className={cn(
|
|
168
|
+
// Layout
|
|
169
|
+
"relative flex w-full cursor-pointer select-none items-center outline-none",
|
|
170
|
+
// Sizing: min-h 48dp, min-w 112dp, max-w 280dp
|
|
171
|
+
"min-h-12 min-w-28 max-w-70",
|
|
172
|
+
// Horizontal padding
|
|
173
|
+
menuVariant === "baseline"
|
|
174
|
+
? MENU_BASELINE_ITEM_HORIZONTAL_PADDING
|
|
175
|
+
: "px-4",
|
|
176
|
+
// Spacing between items
|
|
177
|
+
isStaticMenu ? "my-0.5" : "",
|
|
178
|
+
// Shape morphing (position-based + selected override)
|
|
179
|
+
shapeClass,
|
|
180
|
+
// Animate border-radius AND background-color together (FastEffects: 150ms)
|
|
181
|
+
"transition-[border-radius,background-color] duration-150 ease-in",
|
|
182
|
+
// Colors based on variant + selection
|
|
183
|
+
selected
|
|
184
|
+
? cn(colors.selectedBg, colors.selectedText)
|
|
185
|
+
: cn(colors.labelText),
|
|
186
|
+
// State layers (only on unselected items)
|
|
187
|
+
!selected && colors.hoverLayer,
|
|
188
|
+
!selected && colors.focusLayer,
|
|
189
|
+
// Focus visible ring (WCAG 2.4.11 — visible focus indicator)
|
|
190
|
+
// Uses ring-inset so the ring doesn't overflow the item bounds.
|
|
191
|
+
"focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-m3-primary",
|
|
192
|
+
// Disabled
|
|
193
|
+
disabled && "pointer-events-none opacity-[0.38]",
|
|
194
|
+
className,
|
|
195
|
+
)}
|
|
196
|
+
{...rest}
|
|
197
|
+
>
|
|
198
|
+
{/* ── Leading slot ── */}
|
|
199
|
+
{(isSelectable || leadingIcon) && (
|
|
200
|
+
<div
|
|
201
|
+
className="flex h-5 w-5 shrink-0 items-center justify-center mr-3"
|
|
202
|
+
aria-hidden="true"
|
|
203
|
+
>
|
|
204
|
+
{isSelectable ? (
|
|
205
|
+
<AnimatePresence initial={false} mode="wait">
|
|
206
|
+
{selected ? (
|
|
207
|
+
<m.span
|
|
208
|
+
key="check"
|
|
209
|
+
className={cn(
|
|
210
|
+
"flex h-full w-full items-center justify-center overflow-hidden",
|
|
211
|
+
colors.selectedIcon,
|
|
212
|
+
)}
|
|
213
|
+
variants={CHECK_ICON_VARIANTS}
|
|
214
|
+
initial="hidden"
|
|
215
|
+
animate="visible"
|
|
216
|
+
exit="exit"
|
|
217
|
+
>
|
|
218
|
+
<Icon name="check" fill={1} size={MENU_CHECK_ICON_SIZE} />
|
|
219
|
+
</m.span>
|
|
220
|
+
) : leadingIcon ? (
|
|
221
|
+
<m.span
|
|
222
|
+
key="icon"
|
|
223
|
+
className={cn(
|
|
224
|
+
"flex h-full w-full items-center justify-center overflow-hidden",
|
|
225
|
+
colors.iconColor,
|
|
226
|
+
)}
|
|
227
|
+
variants={CHECK_ICON_VARIANTS}
|
|
228
|
+
initial="hidden"
|
|
229
|
+
animate="visible"
|
|
230
|
+
exit="exit"
|
|
231
|
+
>
|
|
232
|
+
{leadingIcon}
|
|
233
|
+
</m.span>
|
|
234
|
+
) : (
|
|
235
|
+
// Spacer for selectable items with no icon, to keep text aligned
|
|
236
|
+
<div className="w-5" />
|
|
237
|
+
)}
|
|
238
|
+
</AnimatePresence>
|
|
239
|
+
) : (
|
|
240
|
+
// Static icon for non-selectable items
|
|
241
|
+
<span
|
|
242
|
+
className={cn(
|
|
243
|
+
"flex h-full w-full items-center justify-center",
|
|
244
|
+
colors.iconColor,
|
|
245
|
+
)}
|
|
246
|
+
>
|
|
247
|
+
{leadingIcon}
|
|
248
|
+
</span>
|
|
249
|
+
)}
|
|
250
|
+
</div>
|
|
251
|
+
)}
|
|
252
|
+
|
|
253
|
+
{/* ── Label + Supporting Text ── */}
|
|
254
|
+
<span className="flex flex-1 flex-col">
|
|
255
|
+
<span className="text-body-large leading-snug">{children}</span>
|
|
256
|
+
{supportingText && (
|
|
257
|
+
<span
|
|
258
|
+
className={cn(
|
|
259
|
+
"text-body-medium leading-snug",
|
|
260
|
+
// Source: StandardMenuTokens.ItemSupportingTextColor / VibrantMenuTokens
|
|
261
|
+
selected ? colors.selectedText : colors.supportingTextColor,
|
|
262
|
+
)}
|
|
263
|
+
>
|
|
264
|
+
{supportingText}
|
|
265
|
+
</span>
|
|
266
|
+
)}
|
|
267
|
+
</span>
|
|
268
|
+
|
|
269
|
+
{/* ── Trailing: shortcut text OR trailing icon ── */}
|
|
270
|
+
{(trailingText || trailingIcon) && (
|
|
271
|
+
<span
|
|
272
|
+
className={cn(
|
|
273
|
+
// Minimum 12dp gap from label column (ListTokens)
|
|
274
|
+
"ml-3 flex shrink-0 items-center",
|
|
275
|
+
// Source: StandardMenuTokens.ItemTrailingIconColor / VibrantMenuTokens
|
|
276
|
+
selected ? colors.selectedText : colors.trailingIconColor,
|
|
277
|
+
)}
|
|
278
|
+
aria-hidden={trailingIcon ? "true" : undefined}
|
|
279
|
+
>
|
|
280
|
+
{trailingText ? (
|
|
281
|
+
<span className="text-label-small tracking-wider">
|
|
282
|
+
{trailingText}
|
|
283
|
+
</span>
|
|
284
|
+
) : (
|
|
285
|
+
trailingIcon
|
|
286
|
+
)}
|
|
287
|
+
</span>
|
|
288
|
+
)}
|
|
289
|
+
</div>
|
|
290
|
+
</ItemPrimitive>
|
|
291
|
+
);
|
|
292
|
+
},
|
|
293
|
+
);
|
|
294
|
+
MenuItem.displayName = "MenuItem";
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// ─── MD3 Expressive Menu — Design Token Constants ─────────────────────────────
|
|
2
|
+
// Sourced from: SegmentedMenuTokens.kt, StandardMenuTokens.kt, VibrantMenuTokens.kt,
|
|
3
|
+
// MenuTokens.kt, MenuDefaults.kt, ListTokens.kt
|
|
4
|
+
|
|
5
|
+
// ─── Spacing (px → dp) ────────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
/** Horizontal padding for menu items: 16dp (ItemLeadingSpace / ItemTrailingSpace) */
|
|
8
|
+
export const MENU_ITEM_HORIZONTAL_PADDING = "px-4"; // 16dp
|
|
9
|
+
|
|
10
|
+
/** Horizontal padding for baseline menu items: 12dp */
|
|
11
|
+
export const MENU_BASELINE_ITEM_HORIZONTAL_PADDING = "px-3"; // 12dp
|
|
12
|
+
|
|
13
|
+
/** Min height for selectable items: 44dp (SegmentedMenuTokens.Item) */
|
|
14
|
+
export const MENU_ITEM_MIN_HEIGHT = "min-h-11"; // 44dp = 11 * 4px
|
|
15
|
+
|
|
16
|
+
/** Min height for standard list items: 48dp (MenuListItemContainerHeight) */
|
|
17
|
+
export const MENU_LIST_ITEM_MIN_HEIGHT = "min-h-12"; // 48dp
|
|
18
|
+
|
|
19
|
+
/** Min width of menu container: 112dp (DropdownMenuItemDefaultMinWidth) */
|
|
20
|
+
export const MENU_MIN_WIDTH = "min-w-28"; // 112dp
|
|
21
|
+
|
|
22
|
+
/** Max width of menu container: 280dp (DropdownMenuItemDefaultMaxWidth) */
|
|
23
|
+
export const MENU_MAX_WIDTH = "max-w-70"; // 280dp
|
|
24
|
+
|
|
25
|
+
/** Gap between MenuGroup segments: 2dp (SegmentedMenuTokens.SegmentedGap) */
|
|
26
|
+
export const MENU_GROUP_GAP = "gap-0.5"; // 2dp
|
|
27
|
+
|
|
28
|
+
/** Internal group vertical padding: 4dp (DropdownMenuGroupVerticalPadding) */
|
|
29
|
+
export const MENU_GROUP_PADDING_Y = "py-1"; // 4dp
|
|
30
|
+
|
|
31
|
+
/** Popup container vertical padding: 8dp (DropdownMenuVerticalPadding) */
|
|
32
|
+
export const MENU_POPUP_PADDING_Y = "py-2"; // 8dp
|
|
33
|
+
|
|
34
|
+
/** Leading icon size: 20dp (SegmentedMenuTokens.ItemLeadingIconSize) */
|
|
35
|
+
export const MENU_ICON_SIZE = 20;
|
|
36
|
+
|
|
37
|
+
// ─── Container Shape ──────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Menu popup container shape: CornerExtraSmall = 4px.
|
|
41
|
+
* Source: MenuTokens.ContainerShape = ShapeKeyTokens.CornerExtraSmall
|
|
42
|
+
*/
|
|
43
|
+
export const MENU_CONTAINER_SHAPE = "rounded-[4px]"; // CornerExtraSmall
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Shape for Baseline menu item (no shape morphing, edge-to-edge state layer).
|
|
47
|
+
*/
|
|
48
|
+
export const BASELINE_ITEM_SHAPE = "rounded-none";
|
|
49
|
+
|
|
50
|
+
// ─── Shape: CSS border-radius values (used with motion/react `animate`) ────────
|
|
51
|
+
// These are used for animated shape morphing via Framer Motion's `borderRadius`
|
|
52
|
+
// property — NOT as Tailwind classes (Tailwind cannot animate between values).
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Shape values for MenuGroup container (borderRadius CSS shorthand string).
|
|
56
|
+
* Format: "topLeft topRight bottomRight bottomLeft"
|
|
57
|
+
*
|
|
58
|
+
* Source: MenuDefaults.kt groupShape() + SegmentedMenuTokens/ShapeTokens
|
|
59
|
+
* - ContainerShape (standalone) = CornerLarge (16px) all corners
|
|
60
|
+
* - InactiveContainerShape = CornerSmall (8px) all corners
|
|
61
|
+
* - Leading group: topStart=CornerLarge, topEnd=CornerLarge, bottomStart=CornerSmall, bottomEnd=CornerSmall
|
|
62
|
+
* - Middle group: GroupShape = CornerSmall (8px)
|
|
63
|
+
* - Trailing group: topStart=CornerSmall, topEnd=CornerSmall, bottomStart=CornerLarge, bottomEnd=CornerLarge
|
|
64
|
+
*/
|
|
65
|
+
export const GROUP_SHAPES = {
|
|
66
|
+
/** Active standalone group shape: CornerLarge all corners (16px) */
|
|
67
|
+
standaloneActive: "16px",
|
|
68
|
+
/**
|
|
69
|
+
* Active leading group shape: top=CornerLarge(16px), bottom=CornerSmall(8px)
|
|
70
|
+
* Source: SegmentedMenuTokens — LeadingContainerShape:
|
|
71
|
+
* topStart=CornerLarge, topEnd=CornerLarge, bottomStart=CornerSmall, bottomEnd=CornerSmall
|
|
72
|
+
*/
|
|
73
|
+
leadingActive: "16px 16px 8px 8px",
|
|
74
|
+
/** Active middle group shape: CornerExtraSmall all corners (4px) */
|
|
75
|
+
middleActive: "4px",
|
|
76
|
+
/**
|
|
77
|
+
* Active trailing group shape: top=CornerSmall(8px), bottom=CornerLarge(16px)
|
|
78
|
+
* Source: SegmentedMenuTokens — TrailingContainerShape:
|
|
79
|
+
* topStart=CornerSmall, topEnd=CornerSmall, bottomStart=CornerLarge, bottomEnd=CornerLarge
|
|
80
|
+
*/
|
|
81
|
+
trailingActive: "8px 8px 16px 16px",
|
|
82
|
+
/** Inactive (default, pre-hover) shape for all groups: CornerExtraSmall (4px) */
|
|
83
|
+
inactive: "4px",
|
|
84
|
+
} as const;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Shape values for MenuItem (borderRadius CSS shorthand string).
|
|
88
|
+
* Used as Tailwind classes via arbitrary values for static rendering.
|
|
89
|
+
* Animated shape (selected ↔ unselected) is handled via `transition-[border-radius]`.
|
|
90
|
+
*
|
|
91
|
+
* Source: MenuDefaults.kt itemShape() + SegmentedMenuTokens/ShapeTokens
|
|
92
|
+
* - leading item: topStart/topEnd=CornerMedium(12px), bottomStart/bottomEnd=CornerExtraSmall(4px)
|
|
93
|
+
* - middle item: CornerExtraSmall all (ItemShape = 4px)
|
|
94
|
+
* - trailing item: topStart/topEnd=CornerExtraSmall(4px), bottomStart/bottomEnd=CornerMedium(12px)
|
|
95
|
+
* - standalone item: same as middle (ItemShape = 4px)
|
|
96
|
+
* - selected (all positions): CornerMedium all (ItemSelectedShape = 12px)
|
|
97
|
+
*/
|
|
98
|
+
export const ITEM_SHAPE_CLASSES = {
|
|
99
|
+
leading: "rounded-t-[12px] rounded-b-[4px]",
|
|
100
|
+
middle: "rounded-[4px]",
|
|
101
|
+
trailing: "rounded-t-[4px] rounded-b-[12px]",
|
|
102
|
+
standalone: "rounded-[4px]",
|
|
103
|
+
selected: "rounded-[12px]",
|
|
104
|
+
} as const;
|
|
105
|
+
|
|
106
|
+
// ─── Color token Tailwind classes ─────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Standard color variant tokens (SurfaceContainerLow-based).
|
|
110
|
+
* Source: StandardMenuTokens.kt
|
|
111
|
+
*
|
|
112
|
+
* Container: SurfaceContainerLow
|
|
113
|
+
* Text: OnSurface
|
|
114
|
+
* Icons: OnSurfaceVariant
|
|
115
|
+
* Selected container: TertiaryContainer
|
|
116
|
+
* Selected text/icons: OnTertiaryContainer
|
|
117
|
+
*/
|
|
118
|
+
export const STANDARD_COLORS = {
|
|
119
|
+
/** Group/popup container background (StandardMenuTokens.ContainerColor) */
|
|
120
|
+
containerBg: "bg-m3-surface-container-low",
|
|
121
|
+
/** Label text color (StandardMenuTokens.ItemLabelTextColor) */
|
|
122
|
+
labelText: "text-m3-on-surface",
|
|
123
|
+
/** Leading/trailing icon color (StandardMenuTokens.ItemLeadingIconColor) */
|
|
124
|
+
iconColor: "text-m3-on-surface-variant",
|
|
125
|
+
/** Supporting text below label (StandardMenuTokens.ItemSupportingTextColor) */
|
|
126
|
+
supportingTextColor: "text-m3-on-surface-variant",
|
|
127
|
+
/** Trailing supporting text (StandardMenuTokens.ItemTrailingSupportingTextColor) */
|
|
128
|
+
trailingSupportingTextColor: "text-m3-on-surface-variant",
|
|
129
|
+
/** Trailing icon color (StandardMenuTokens.ItemTrailingIconColor) */
|
|
130
|
+
trailingIconColor: "text-m3-on-surface-variant",
|
|
131
|
+
/** Hover state layer (OnSurface @ 8% opacity) */
|
|
132
|
+
hoverLayer: "hover:bg-m3-on-surface/8",
|
|
133
|
+
/** Focus state layer (OnSurface @ 12% opacity) */
|
|
134
|
+
focusLayer: "focus:bg-m3-on-surface/12",
|
|
135
|
+
/** Selected item background (StandardMenuTokens.ItemSelectedContainerColor) */
|
|
136
|
+
selectedBg: "bg-m3-tertiary-container",
|
|
137
|
+
/** Selected item text (StandardMenuTokens.ItemSelectedLabelTextColor) */
|
|
138
|
+
selectedText: "text-m3-on-tertiary-container",
|
|
139
|
+
/** Selected item icon (StandardMenuTokens.ItemSelectedLeadingIconColor) */
|
|
140
|
+
selectedIcon: "text-m3-on-tertiary-container",
|
|
141
|
+
/** Disabled opacity: 38% (StandardMenuTokens.ItemDisabledLabelTextOpacity) */
|
|
142
|
+
disabledOpacity: "data-disabled:opacity-[0.38]",
|
|
143
|
+
} as const;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Baseline color variant tokens.
|
|
147
|
+
* Container uses SurfaceContainer, Selected uses SecondaryContainer.
|
|
148
|
+
*/
|
|
149
|
+
export const BASELINE_COLORS = {
|
|
150
|
+
containerBg: "bg-m3-surface-container",
|
|
151
|
+
labelText: "text-m3-on-surface",
|
|
152
|
+
iconColor: "text-m3-on-surface-variant",
|
|
153
|
+
supportingTextColor: "text-m3-on-surface-variant",
|
|
154
|
+
trailingSupportingTextColor: "text-m3-on-surface-variant",
|
|
155
|
+
trailingIconColor: "text-m3-on-surface-variant",
|
|
156
|
+
hoverLayer: "hover:bg-m3-on-surface/8",
|
|
157
|
+
focusLayer: "focus:bg-m3-on-surface/12",
|
|
158
|
+
selectedBg: "bg-m3-secondary-container",
|
|
159
|
+
selectedText: "text-m3-on-secondary-container",
|
|
160
|
+
selectedIcon: "text-m3-on-secondary-container",
|
|
161
|
+
disabledOpacity: "data-disabled:opacity-[0.38]",
|
|
162
|
+
} as const;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Vibrant color variant tokens (TertiaryContainer-based).
|
|
166
|
+
* Source: VibrantMenuTokens.kt
|
|
167
|
+
*
|
|
168
|
+
* Container: TertiaryContainer
|
|
169
|
+
* Text: OnTertiaryContainer
|
|
170
|
+
* Icons: OnTertiaryContainer
|
|
171
|
+
* Selected container: Tertiary
|
|
172
|
+
* Selected text/icons: OnTertiary
|
|
173
|
+
*/
|
|
174
|
+
export const VIBRANT_COLORS = {
|
|
175
|
+
/** Group/popup container background (VibrantMenuTokens.ContainerColor) */
|
|
176
|
+
containerBg: "bg-m3-tertiary-container",
|
|
177
|
+
/** Label text color (VibrantMenuTokens.ItemLabelTextColor) */
|
|
178
|
+
labelText: "text-m3-on-tertiary-container",
|
|
179
|
+
/** Leading/trailing icon color (VibrantMenuTokens.ItemLeadingIconColor) */
|
|
180
|
+
iconColor: "text-m3-on-tertiary-container",
|
|
181
|
+
/** Supporting text below label (VibrantMenuTokens.ItemSupportingTextColor) */
|
|
182
|
+
supportingTextColor: "text-m3-on-tertiary-container",
|
|
183
|
+
/** Trailing supporting text (VibrantMenuTokens.ItemTrailingSupportingTextColor) */
|
|
184
|
+
trailingSupportingTextColor: "text-m3-on-tertiary-container",
|
|
185
|
+
/** Trailing icon color (VibrantMenuTokens.ItemTrailingIconColor) */
|
|
186
|
+
trailingIconColor: "text-m3-on-tertiary-container",
|
|
187
|
+
/** Hover state layer (OnTertiaryContainer @ 8% opacity) */
|
|
188
|
+
hoverLayer: "hover:bg-m3-on-tertiary-container/8",
|
|
189
|
+
/** Focus state layer (OnTertiaryContainer @ 12% opacity) */
|
|
190
|
+
focusLayer: "focus:bg-m3-on-tertiary-container/12",
|
|
191
|
+
/** Selected item background (VibrantMenuTokens.ItemSelectedContainerColor = Tertiary) */
|
|
192
|
+
selectedBg: "bg-m3-tertiary",
|
|
193
|
+
/** Selected item text (VibrantMenuTokens.ItemSelectedLabelTextColor = OnTertiary) */
|
|
194
|
+
selectedText: "text-m3-on-tertiary",
|
|
195
|
+
/** Selected item icon (VibrantMenuTokens.ItemSelectedLeadingIconColor = OnTertiary) */
|
|
196
|
+
selectedIcon: "text-m3-on-tertiary",
|
|
197
|
+
/** Disabled opacity: 38% (VibrantMenuTokens.ItemDisabledLabelTextOpacity) */
|
|
198
|
+
disabledOpacity: "data-disabled:opacity-[0.38]",
|
|
199
|
+
} as const;
|
|
200
|
+
|
|
201
|
+
// ─── Divider ──────────────────────────────────────────────────────────────────
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* HorizontalDivider padding: horizontal=12dp, vertical=2dp.
|
|
205
|
+
* Source: MenuDefaults.HorizontalDividerPadding
|
|
206
|
+
*/
|
|
207
|
+
export const DIVIDER_PADDING = "mx-3 my-0.5"; // 12dp horizontal, 2dp vertical
|
|
208
|
+
export const DIVIDER_COLOR = "bg-m3-outline-variant";
|