@bug-on/md3-react 2.0.3 → 3.0.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/.turbo/turbo-build.log +33 -0
- package/CHANGELOG.md +55 -0
- package/dist/index.css.d.ts +2 -0
- package/dist/index.d.mts +6127 -0
- package/dist/index.d.ts +6127 -71
- package/dist/index.js +1653 -614
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1566 -547
- 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/typography.css.d.ts +2 -0
- package/package.json +22 -19
- package/scripts/copy-assets.js +82 -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 +180 -0
- package/src/lib/utils.ts +6 -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 +297 -0
- package/src/ui/button.tsx +669 -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 +604 -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 +122 -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 +190 -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,312 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../lib/utils";
|
|
5
|
+
|
|
6
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
7
|
+
// MD3 Expressive Loading Indicator
|
|
8
|
+
//
|
|
9
|
+
// Spec references:
|
|
10
|
+
// • Loading Indicator Overview (190326 PDF)
|
|
11
|
+
// • Loading Indicator Specs (190326 PDF)
|
|
12
|
+
// • Loading Indicator Accessibility (190326 PDF)
|
|
13
|
+
// • LoadingIndicator.kt (Android reference)
|
|
14
|
+
//
|
|
15
|
+
// Two operational modes:
|
|
16
|
+
// 1. Indeterminate — no `progress` prop → 7-shape morph + SMIL rotation loop
|
|
17
|
+
// 2. Determinate — `progress` (0-1) → Circle→SoftBurst morph + counterclockwise rotation
|
|
18
|
+
//
|
|
19
|
+
// Responsive sizing: strictly [24dp, 240dp]. Container/indicator ratio is preserved.
|
|
20
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
// ─── Indeterminate: Shape & Rotation Keyframe Data (from official MD3 SVG) ──
|
|
23
|
+
const SHAPE_KEY_TIMES =
|
|
24
|
+
"0; 0.14; 0.14; 0.29; 0.29; 0.43; 0.43; 0.57; 0.57; 0.71; 0.71; 0.86; 0.86; 1";
|
|
25
|
+
|
|
26
|
+
const SHAPE_KEY_SPLINES = [
|
|
27
|
+
"0.5 0.2 0 0.8",
|
|
28
|
+
"0.5 0.2 0 0.8",
|
|
29
|
+
"0.5 0.2 0 0.8",
|
|
30
|
+
"0.5 0.2 0 0.8",
|
|
31
|
+
"0.5 0.2 0 0.8",
|
|
32
|
+
"0.5 0.2 0 0.8",
|
|
33
|
+
"0.5 0.2 0 0.8",
|
|
34
|
+
"0.5 0.2 0 0.8",
|
|
35
|
+
"0.5 0.2 0 0.8",
|
|
36
|
+
"0.5 0.2 0 0.8",
|
|
37
|
+
"0.5 0.2 0 0.8",
|
|
38
|
+
"0.5 0.2 0 0.8",
|
|
39
|
+
"0.5 0.2 0 0.8",
|
|
40
|
+
].join("; ");
|
|
41
|
+
|
|
42
|
+
// 7 MD3 morphing shapes, each repeated twice for a smooth hold-and-morph
|
|
43
|
+
const SHAPE_VALUES = [
|
|
44
|
+
"M20.9 10.4 21.4 9.5 21.9 8.7 22.5 7.8 23.2 7.2 24.2 7 25.1 7.4 25.7 8.1 26.2 9 26.8 9.8 27.3 10.6 28.1 11.2 29 11.3 30 11 30.9 10.6 31.8 10.3 32.8 9.9 33.7 10 34.5 10.6 34.9 11.5 34.8 12.5 34.8 13.5 34.7 14.5 34.7 15.5 35.2 16.3 36 16.8 37 17.1 37.9 17.3 38.9 17.5 39.8 17.9 40.4 18.7 40.5 19.7 40 20.5 39.4 21.3 38.7 22 38.1 22.8 37.6 23.7 37.7 24.6 38.3 25.5 38.9 26.2 39.6 27 40.2 27.7 40.5 28.7 40.3 29.6 39.5 30.3 38.6 30.6 37.6 30.8 36.7 31 35.7 31.3 35 31.9 34.6 32.8 34.7 33.8 34.8 34.8 34.9 35.8 34.8 36.8 34.3 37.6 33.4 38.1 32.4 38 31.5 37.6 30.6 37.2 29.7 36.9 28.7 36.6 27.8 36.9 27.1 37.6 26.6 38.5 26.1 39.3 25.5 40.2 24.8 40.8 23.8 41 22.9 40.6 22.3 39.9 21.8 39 21.2 38.2 20.7 37.4 19.9 36.8 19 36.7 18 37 17.1 37.4 16.2 37.7 15.2 38.1 14.3 38 13.5 37.4 13.1 36.5 13.2 35.5 13.2 34.5 13.3 33.5 13.3 32.5 12.8 31.7 12 31.2 11 31 10.1 30.7 9.1 30.5 8.2 30.1 7.6 29.3 7.5 28.3 8 27.5 8.7 26.7 9.3 26 9.9 25.2 10.4 24.3 10.3 23.4 9.7 22.5 9.1 21.8 8.4 21 7.8 20.3 7.5 19.3 7.7 18.4 8.5 17.7 9.4 17.4 10.4 17.2 11.3 17 12.3 16.7 13 16.1 13.4 15.2 13.3 14.2 13.2 13.2 13.1 12.2 13.2 11.2 13.7 10.4 14.6 9.9 15.6 10 16.5 10.4 17.4 10.8 18.3 11.1 19.3 11.4 20.2 11.1Z",
|
|
45
|
+
"M20.3 8.6 21.1 8 22 7.6 23 7.3 23 7.3 24 7.2 25 7.3 25.9 7.5 26.8 8 27.6 8.6 28.4 9.1 28.4 9.1 29.3 9.6 30.3 9.8 31.3 9.9 32.3 10 33.3 10.2 34.2 10.6 34.2 10.6 35 11.2 35.7 11.9 36.3 12.7 36.7 13.6 36.9 14.6 37.2 15.5 37.2 15.5 37.6 16.5 38.2 17.3 38.9 18 39.6 18.7 40.2 19.5 40.6 20.4 40.6 20.4 40.9 21.3 41 22.3 40.9 23.3 40.6 24.3 40.2 25.2 39.8 26.1 39.8 26.1 39.5 27 39.4 28 39.5 29 39.6 30 39.5 31 39.3 32 39.3 32 38.9 32.9 38.3 33.7 37.6 34.4 36.8 35 35.9 35.4 35 35.8 35 35.8 34.1 36.3 33.4 37 32.9 37.9 32.3 38.7 31.6 39.4 30.8 40 30.8 40 29.9 40.4 28.9 40.7 27.9 40.8 27 40.7 26 40.4 25 40.1 25 40.1 24 40 23 40.1 22.1 40.4 21.1 40.7 20.1 40.8 19.1 40.7 19.1 40.7 18.2 40.5 17.3 40 16.4 39.5 15.7 38.8 15.2 37.9 14.6 37.1 14.6 37.1 13.9 36.4 13.1 35.8 12.2 35.4 11.3 35 10.5 34.4 9.7 33.8 9.7 33.8 9.1 32.9 8.7 32 8.5 31.1 8.4 30.1 8.5 29.1 8.6 28.1 8.6 28.1 8.5 27.1 8.3 26.1 7.8 25.2 7.4 24.3 7.1 23.4 7 22.4 7 22.4 7.1 21.4 7.3 20.4 7.8 19.5 8.3 18.7 9.1 18 9.8 17.3 9.8 17.3 10.4 16.5 10.8 15.6 11 14.6 11.3 13.7 11.7 12.8 12.2 11.9 12.2 11.9 12.9 11.2 13.8 10.7 14.7 10.2 15.6 10 16.6 9.9 17.6 9.8 17.6 9.8 18.6 9.6 19.5 9.2Z",
|
|
46
|
+
"M18.6 9.6 19.5 9.2 20.3 8.6 21.1 8 22 7.6 23 7.3 24 7.2 25 7.3 25.9 7.5 26.8 8 27.6 8.6 28.4 9.1 29.3 9.6 30.3 9.8 31.3 9.9 32.3 10 33.3 10.2 34.2 10.6 35 11.2 35.7 11.9 36.3 12.7 36.7 13.6 36.9 14.6 37.2 15.5 37.6 16.4 38.2 17.3 38.9 18 39.6 18.7 40.2 19.5 40.6 20.4 40.9 21.3 41 22.3 40.9 23.3 40.6 24.3 40.2 25.2 39.8 26.1 39.5 27 39.4 28 39.5 29 39.6 30 39.5 31 39.3 32 38.9 32.9 38.3 33.7 37.6 34.4 36.8 35 35.9 35.4 35 35.8 34.1 36.3 33.4 37 32.9 37.9 32.3 38.7 31.6 39.4 30.8 40 29.9 40.4 28.9 40.7 27.9 40.8 27 40.7 26 40.4 25 40.1 24 40 23 40.1 22.1 40.4 21.1 40.7 20.1 40.8 19.1 40.7 18.2 40.5 17.3 40 16.4 39.5 15.7 38.8 15.2 37.9 14.6 37.1 13.9 36.4 13.1 35.8 12.2 35.4 11.3 35 10.5 34.4 9.7 33.8 9.1 32.9 8.7 32 8.5 31.1 8.4 30.1 8.5 29.1 8.6 28.1 8.5 27.1 8.3 26.1 7.8 25.2 7.4 24.3 7.1 23.4 7 22.4 7.1 21.4 7.3 20.4 7.8 19.5 8.3 18.7 9.1 18 9.8 17.3 10.4 16.5 10.8 15.6 11 14.6 11.3 13.7 11.7 12.8 12.2 11.9 12.9 11.2 13.8 10.7 14.7 10.2 15.6 10 16.6 9.9 17.6 9.8Z",
|
|
47
|
+
"M18.6 9.9 19.5 9.4 20.3 8.8 21.1 8.2 22 7.8 23 7.6 23.9 7.5 24.9 7.6 25.9 7.8 26.8 8.2 27.6 8.7 28.5 9.3 29.3 9.9 30.1 10.5 30.9 11 31.7 11.6 32.5 12.2 33.3 12.8 33.7 13.1 34.1 13.3 34.9 13.9 35.7 14.5 36.6 15 37.4 15.6 38.2 16.2 39 16.8 39.7 17.5 40.2 18.3 40.7 19.2 40.9 20.1 41 21.1 40.9 22.1 40.7 23.1 40.3 24 40 24.9 39.7 25.9 39.4 26.8 39 27.8 38.7 28.7 38.4 29.6 38.1 30.6 37.8 31.5 37.5 32.5 37.2 33.4 36.9 34.4 36.6 35.3 36.2 36.2 35.7 37.1 35 37.8 34.3 38.4 33.4 38.9 32.5 39.3 31.5 39.5 30.5 39.5 30 39.5 29.5 39.5 28.5 39.5 27.5 39.5 26.5 39.5 25.5 39.4 24.5 39.4 23.6 39.4 22.6 39.4 21.6 39.5 20.6 39.5 19.6 39.5 18.6 39.5 17.6 39.5 16.6 39.5 15.6 39.3 14.7 39 13.8 38.5 13.1 37.9 12.4 37.2 11.9 36.3 11.5 35.4 11.2 34.5 10.9 33.5 10.6 32.6 10.3 31.6 10 30.7 9.7 29.7 9.3 28.8 9 27.9 8.7 26.9 8.4 26 8 25 7.7 24.1 7.4 23.2 7.1 22.2 7.1 21.7 7 21.2 7.1 20.2 7.3 19.3 7.7 18.4 8.3 17.5 8.9 16.8 9.7 16.2 10.5 15.6 11.4 15.1 12.2 14.5 13 14 13.8 13.4 14.6 12.8 15.4 12.3 16.2 11.7 17 11.1 17.8 10.5Z",
|
|
48
|
+
"M15.4 12.3 16.2 11.7 17 11.1 17.8 10.5 18.6 9.9 19.5 9.4 20.3 8.8 21.1 8.3 22 7.8 23 7.6 23.9 7.5 24.9 7.6 25.9 7.8 26.8 8.2 27.6 8.7 28.5 9.3 29.3 9.9 30.1 10.5 30.9 11 31.7 11.6 32.5 12.2 33.3 12.8 34.1 13.3 34.9 13.9 35.7 14.5 36.6 15 37.4 15.6 38.2 16.2 39 16.8 39.7 17.5 40.2 18.3 40.7 19.2 40.9 20.1 41 21.1 40.9 22.1 40.7 23.1 40.3 24 40 24.9 39.7 25.9 39.4 26.8 39 27.8 38.7 28.7 38.4 29.6 38.1 30.6 37.8 31.5 37.5 32.5 37.2 33.4 36.9 34.4 36.6 35.3 36.2 36.2 35.7 37.1 35 37.8 34.3 38.4 33.4 38.9 32.5 39.3 31.5 39.4 30.5 39.5 29.5 39.5 28.5 39.5 27.5 39.5 26.5 39.5 25.5 39.4 24.5 39.4 23.6 39.4 22.6 39.4 21.6 39.5 20.6 39.5 19.6 39.5 18.6 39.5 17.6 39.5 16.6 39.5 15.6 39.3 14.7 39 13.8 38.5 13.1 37.9 12.4 37.2 11.9 36.3 11.5 35.4 11.2 34.5 10.9 33.5 10.6 32.6 10.3 31.6 10 30.7 9.7 29.7 9.3 28.8 9 27.9 8.7 26.9 8.4 26 8 25 7.7 24.1 7.4 23.2 7.1 22.2 7 21.2 7.1 20.2 7.3 19.3 7.7 18.4 8.3 17.5 8.9 16.8 9.7 16.2 10.5 15.6 11.4 15.1 12.2 14.5 13 14 13.8 13.4 14.6 12.8Z",
|
|
49
|
+
"M17 12.8 17.7 12.1 18.5 11.5 19.3 10.9 20.1 10.5 20.2 10.4 21.1 10 22 9.7 23 9.4 24 9.2 25 9 26 9 27 9 27.6 9.1 28 9.1 28.9 9.3 29.9 9.6 30.9 9.9 31.8 10.3 32.6 10.8 33.5 11.3 34.3 11.9 34.6 12.2 35 12.6 35.7 13.3 36.4 14.1 36.9 14.9 37.4 15.8 37.9 16.6 38.3 17.6 38.6 18.5 38.6 18.7 38.8 19.5 38.9 20.5 39 21.5 39 22.5 38.9 23.5 38.7 24.5 38.5 25.4 38.2 26.3 38.2 26.4 37.8 27.3 37.4 28.2 36.8 29.1 36.2 29.9 35.6 30.6 34.9 31.3 34.2 32 33.8 32.5 33.5 32.8 32.8 33.5 32.1 34.2 31.4 34.9 30.7 35.6 29.9 36.2 29.1 36.8 28.2 37.3 27.9 37.5 27.4 37.8 26.4 38.2 25.5 38.5 24.5 38.7 23.5 38.9 22.5 39 21.5 39 20.5 38.9 20.4 38.9 19.5 38.8 18.6 38.6 17.6 38.3 16.7 37.9 15.8 37.5 14.9 37 14.1 36.4 13.4 35.8 13.3 35.8 12.6 35.1 12 34.3 11.3 33.5 10.8 32.7 10.3 31.8 9.9 30.9 9.6 30 9.4 29.3 9.3 29 9.1 28 9 27 9 26 9 25 9.2 24 9.4 23 9.6 22.1 9.8 21.7 10 21.1 10.4 20.2 10.9 19.4 11.4 18.5 12.1 17.8 12.7 17 13.4 16.3 14.2 15.6 14.2 15.5 14.9 14.9 15.6 14.2 16.3 13.5Z",
|
|
50
|
+
"M33.5 11.3 34.3 11.9 35 12.6 35.3 12.8 35.7 13.3 36.4 14.1 36.9 14.9 37.4 15.8 37.9 16.6 38.3 17.6 38.3 17.7 38.6 18.5 38.8 19.5 38.9 20.5 39 21.5 39 22.5 38.9 23.5 38.9 23.5 38.7 24.5 38.5 25.4 38.2 26.4 37.8 27.3 37.4 28.2 36.9 28.9 36.8 29.1 36.2 29.9 35.6 30.6 34.9 31.3 34.2 32 33.5 32.8 33.1 33.2 32.8 33.5 32.1 34.2 31.4 34.9 30.7 35.6 29.9 36.2 29.1 36.8 28.7 37 28.2 37.3 27.4 37.8 26.4 38.2 25.5 38.5 24.5 38.7 23.5 38.9 23.3 38.9 22.5 39 21.5 39 20.5 38.9 19.5 38.8 18.6 38.6 17.6 38.3 17.6 38.3 16.7 37.9 15.8 37.5 14.9 37 14.1 36.4 13.3 35.8 12.7 35.2 12.6 35.1 12 34.3 11.3 33.5 10.8 32.7 10.3 31.8 9.9 30.9 9.7 30.3 9.6 29.9 9.3 29 9.1 28 9 27 9 26 9 25 9.1 24.5 9.2 24 9.4 23 9.6 22.1 10 21.1 10.4 20.2 10.9 19.4 11.1 19.1 11.5 18.5 12.1 17.7 12.7 17 13.5 16.3 14.2 15.6 14.9 14.9 14.9 14.8 15.6 14.2 16.3 13.5 17 12.8 17.7 12.1 18.5 11.5 19.3 11 19.3 10.9 20.2 10.4 21.1 10 22 9.7 23 9.4 24 9.2 24.7 9.1 25 9 26 9 27 9 28 9.1 28.9 9.3 29.9 9.6 30.4 9.7 30.9 9.9 31.8 10.3 32.6 10.8Z",
|
|
51
|
+
"M33.2 11.1 34.2 11.2 35.1 11.4 36 11.9 36.6 12.7 36.8 13.7 36.9 14.7 36.9 15.7 37 16.7 37.1 17.7 37.3 18.6 37.9 19.4 38.5 20.2 39.2 20.9 39.8 21.7 40.5 22.4 40.9 23.3 41 24.3 40.7 25.2 40.1 26 39.4 26.8 38.8 27.5 38.1 28.3 37.5 29.1 37.1 30 37 31 37 31.9 36.9 32.9 36.8 33.9 36.7 34.9 36.2 35.8 35.5 36.4 34.5 36.8 33.6 36.8 32.6 36.9 31.6 37 30.6 37.1 29.6 37.2 28.8 37.7 28 38.4 27.3 39 26.5 39.7 25.8 40.3 24.9 40.8 23.9 41 23 40.8 22.1 40.2 21.4 39.6 20.6 38.9 19.9 38.3 19.1 37.6 18.3 37.2 17.3 37 16.3 37 15.3 36.9 14.3 36.8 13.3 36.7 12.4 36.4 11.7 35.7 11.3 34.8 11.2 33.8 11.1 32.8 11 31.8 11 30.8 10.9 29.9 10.4 29 9.8 28.2 9.1 27.5 8.5 26.7 7.8 26 7.3 25.1 7 24.2 7.1 23.2 7.6 22.3 8.2 21.6 8.9 20.8 9.5 20.1 10.2 19.3 10.7 18.5 10.9 17.5 11 16.6 11.1 15.6 11.1 14.6 11.2 13.6 11.5 12.6 12.1 11.9 13 11.4 13.9 11.2 14.9 11.1 15.9 11 16.9 11 17.9 10.9 18.8 10.6 19.6 10 20.4 9.3 21.1 8.6 21.9 8 22.6 7.4 23.6 7 24.6 7.1 25.5 7.5 26.2 8.1 27 8.7 27.7 9.4 28.5 10 29.3 10.6 30.2 10.9 31.2 11 32.2 11.1Z",
|
|
52
|
+
"M27.7 9.4 28.5 10 29.3 10.6 30.2 10.9 31.2 11 32.2 11.1 33.2 11.1 34.2 11.2 35.1 11.4 36 11.9 36.6 12.7 36.8 13.7 36.9 14.7 36.9 15.7 37 16.7 37.1 17.7 37.3 18.6 37.9 19.4 38.5 20.2 39.2 20.9 39.8 21.7 40.5 22.4 40.9 23.3 41 24.3 40.7 25.2 40.1 26 39.4 26.8 38.8 27.5 38.1 28.3 37.5 29.1 37.1 30 37 31 37 31.9 36.9 32.9 36.8 33.9 36.7 34.9 36.2 35.8 35.5 36.4 34.5 36.8 33.6 36.9 32.6 36.9 31.6 37 30.6 37.1 29.6 37.2 28.8 37.7 28 38.4 27.3 39 26.5 39.7 25.8 40.3 24.9 40.8 23.9 41 23 40.8 22.1 40.2 21.4 39.6 20.6 38.9 19.9 38.3 19.1 37.6 18.3 37.2 17.3 37 16.3 37 15.3 36.9 14.3 36.8 13.3 36.7 12.4 36.4 11.7 35.7 11.3 34.8 11.2 33.8 11.1 32.8 11 31.8 11 30.8 10.9 29.9 10.4 29 9.8 28.2 9.1 27.5 8.5 26.7 7.8 26 7.3 25.1 7 24.2 7.1 23.2 7.6 22.3 8.2 21.6 8.9 20.8 9.5 20.1 10.2 19.3 10.7 18.5 10.9 17.5 11 16.5 11.1 15.6 11.1 14.6 11.2 13.6 11.5 12.6 12.1 11.9 13 11.4 13.9 11.2 14.9 11.1 15.9 11 16.9 11 17.9 10.9 18.8 10.6 19.6 10 20.4 9.3 21.1 8.6 21.9 8 22.6 7.4 23.6 7 24.6 7.1 25.5 7.5 26.2 8.1 27 8.7Z",
|
|
53
|
+
"M27.9 10.6 28.8 10.3 29.8 10.1 30.8 10 31.8 10.1 32.7 10.3 33.7 10.6 34.6 11.1 34.8 11.3 35.4 11.7 36.1 12.4 36.7 13.1 37.2 14 37.6 14.9 37.9 15.9 38 16.9 38 17.9 37.8 18.8 37.5 19.8 37.1 20.7 36.8 21.6 36.5 22.6 36.4 23.6 36.4 24.4 36.4 24.6 36.5 25.5 36.8 26.5 37.2 27.4 37.6 28.3 37.8 29.3 38 30.3 38 31.3 37.8 32.3 37.6 33.2 37.2 34.1 36.6 35 36 35.7 35.3 36.4 34.4 37 34.1 37.2 33.6 37.4 32.6 37.8 31.6 37.9 30.6 38 29.7 37.9 28.7 37.7 27.8 37.3 26.8 36.9 25.9 36.6 24.9 36.4 23.9 36.4 22.9 36.4 22 36.6 21 37 20.1 37.4 20.1 37.4 19.2 37.7 18.2 37.9 17.2 38 16.2 37.9 15.3 37.7 14.3 37.4 13.4 36.9 12.6 36.3 11.9 35.6 11.3 34.9 10.8 34 10.4 33.1 10.1 32.1 10 31.1 10 30.6 10 30.1 10.2 29.2 10.5 28.2 10.9 27.3 11.2 26.4 11.5 25.4 11.6 24.4 11.6 23.4 11.5 22.5 11.2 21.5 10.8 20.6 10.4 19.7 10.2 18.7 10 17.7 10 16.7 10 16.6 10.2 15.7 10.4 14.8 10.8 13.9 11.4 13 12 12.3 12.7 11.6 13.6 11 14.4 10.6 15.4 10.2 16.4 10.1 17.4 10 18.3 10.1 19.3 10.3 20.2 10.7 20.9 11 21.2 11.1 22.1 11.4 23.1 11.6 24.1 11.6 25.1 11.6 26 11.4 27 11Z",
|
|
54
|
+
"M36 35.7 35.3 36.4 34.4 37 33.6 37.4 32.6 37.8 31.6 37.9 30.6 38 29.7 37.9 28.7 37.7 27.8 37.3 26.8 36.9 25.9 36.6 24.9 36.4 23.9 36.4 22.9 36.4 22 36.6 21 37 20.1 37.4 19.2 37.7 18.2 37.9 17.2 38 16.2 37.9 15.3 37.7 14.3 37.4 13.4 36.9 12.6 36.3 11.9 35.6 11.3 34.9 10.8 34 10.4 33.1 10.1 32.1 10 31.1 10 30.2 10.2 29.2 10.5 28.2 10.9 27.3 11.2 26.4 11.5 25.4 11.6 24.4 11.6 23.4 11.5 22.5 11.2 21.5 10.8 20.6 10.4 19.7 10.2 18.7 10 17.7 10 16.7 10.2 15.7 10.4 14.8 10.8 13.9 11.4 13 12 12.3 12.7 11.6 13.6 11 14.4 10.6 15.4 10.2 16.4 10.1 17.4 10 18.3 10.1 19.3 10.3 20.2 10.7 21.2 11.1 22.1 11.4 23.1 11.6 24.1 11.6 25.1 11.6 26 11.4 27 11 27.9 10.6 28.8 10.3 29.8 10.1 30.8 10 31.8 10.1 32.7 10.3 33.7 10.6 34.6 11.1 35.4 11.7 36.1 12.4 36.7 13.1 37.2 14 37.6 14.9 37.9 15.9 38 16.9 38 17.8 37.8 18.8 37.5 19.8 37.1 20.7 36.8 21.6 36.5 22.6 36.4 23.6 36.4 24.6 36.5 25.5 36.8 26.5 37.2 27.4 37.6 28.3 37.8 29.3 38 30.3 38 31.3 37.8 32.3 37.6 33.2 37.2 34.1 36.6 35Z",
|
|
55
|
+
"M32.1 32.1 31.4 32.8 30.7 33.5 29.9 34.1 29.1 34.7 28.3 35.3 27.6 35.8 27.5 35.8 26.6 36.4 25.8 36.8 24.9 37.3 24 37.7 23.1 38 22.1 38.3 21.2 38.6 20.2 38.8 19.2 38.9 18.2 39 17.2 39 16.6 38.9 16.3 38.9 15.3 38.7 14.3 38.4 13.4 38 12.5 37.5 11.7 36.9 11.1 36.3 10.5 35.5 10 34.6 9.6 33.7 9.3 32.7 9.1 31.7 9.1 31.4 9 30.8 9 29.8 9.1 28.8 9.2 27.8 9.4 26.8 9.7 25.9 10 24.9 10.3 24 10.7 23.1 11.2 22.2 11.6 21.4 12.2 20.5 12.2 20.4 12.7 19.7 13.3 18.9 13.9 18.1 14.5 17.3 15.2 16.6 15.9 15.9 16.6 15.2 17.3 14.5 18.1 13.9 18.9 13.3 19.7 12.7 20.4 12.2 20.5 12.2 21.4 11.6 22.2 11.2 23.1 10.7 24 10.3 24.9 10 25.9 9.7 26.8 9.4 27.8 9.2 28.8 9.1 29.8 9 30.8 9 31.4 9.1 31.7 9.1 32.7 9.3 33.7 9.6 34.6 10 35.5 10.5 36.3 11.1 36.9 11.7 37.5 12.5 38 13.4 38.4 14.3 38.7 15.3 38.9 16.3 38.9 16.6 39 17.2 39 18.2 38.9 19.2 38.8 20.2 38.6 21.2 38.3 22.1 38 23.1 37.7 24 37.3 24.9 36.8 25.8 36.4 26.6 35.8 27.5 35.8 27.6 35.3 28.3 34.7 29.1 34.1 29.9 33.5 30.7 32.8 31.4Z",
|
|
56
|
+
"M24.3 10.2 24.9 10 25.9 9.7 26.8 9.4 27.1 9.4 27.8 9.2 28.8 9.1 29.8 9 29.9 9 30.8 9 31.7 9.1 32.7 9.3 32.8 9.3 33.7 9.6 34.6 10 35.5 10.5 35.5 10.5 36.3 11.1 36.9 11.7 37.5 12.5 37.5 12.5 38 13.4 38.4 14.3 38.7 15.2 38.7 15.3 38.9 16.3 39 17.2 39 18.1 39 18.2 38.9 19.2 38.8 20.2 38.6 20.9 38.6 21.2 38.3 22.1 38 23.1 37.8 23.7 37.7 24 37.3 24.9 36.8 25.8 36.5 26.4 36.4 26.6 35.8 27.5 35.3 28.3 35 28.8 34.7 29.1 34.1 29.9 33.5 30.7 33.1 31.1 32.8 31.4 32.1 32.1 31.4 32.8 31.1 33.1 30.7 33.5 29.9 34.1 29.1 34.7 28.8 35 28.3 35.3 27.5 35.8 26.6 36.4 26.4 36.5 25.8 36.8 24.9 37.3 24 37.7 23.7 37.8 23.1 38 22.1 38.3 21.2 38.6 20.9 38.6 20.2 38.8 19.2 38.9 18.2 39 18.1 39 17.2 39 16.3 38.9 15.3 38.7 15.2 38.7 14.3 38.4 13.4 38 12.5 37.5 12.5 37.5 11.7 36.9 11.1 36.3 10.5 35.5 10.5 35.5 10 34.6 9.6 33.7 9.3 32.8 9.3 32.7 9.1 31.7 9 30.8 9 29.9 9 29.8 9.1 28.8 9.2 27.8 9.4 27.1 9.4 26.8 9.7 25.9 10 24.9 10.2 24.3 10.3 24 10.7 23.1 11.2 22.2 11.5 21.6 11.6 21.4 12.2 20.5 12.7 19.7 13 19.2 13.3 18.9 13.9 18.1 14.5 17.3 14.9 16.9 15.2 16.6 15.9 15.9 16.6 15.2 16.9 14.9 17.3 14.5 18.1 13.9 18.9 13.3 19.2 13 19.7 12.7 20.5 12.2 21.4 11.6 21.6 11.5 22.2 11.2 23.1 10.7 24 10.3Z",
|
|
57
|
+
"M22.5 7.8 23.2 7.2 24.2 7 25.1 7.4 25.7 8.1 26.2 9 26.8 9.8 27.3 10.6 28.1 11.2 29 11.3 30 11 30.9 10.6 31.8 10.3 32.8 9.9 33.7 10 34.5 10.6 34.9 11.5 34.8 12.5 34.8 13.5 34.7 14.5 34.7 15.5 35.2 16.3 36 16.8 37 17 37.9 17.3 38.9 17.5 39.8 17.9 40.4 18.7 40.5 19.7 40 20.5 39.3 21.3 38.7 22 38.1 22.8 37.6 23.7 37.7 24.6 38.3 25.4 38.9 26.2 39.6 27 40.2 27.7 40.5 28.6 40.3 29.6 39.5 30.3 38.6 30.6 37.6 30.8 36.7 31 35.7 31.3 35 31.9 34.6 32.8 34.7 33.8 34.8 34.8 34.8 35.8 34.8 36.8 34.3 37.6 33.4 38.1 32.4 38 31.5 37.6 30.6 37.2 29.7 36.9 28.7 36.6 27.8 36.9 27.1 37.6 26.6 38.5 26.1 39.3 25.5 40.2 24.8 40.8 23.8 41 22.9 40.6 22.3 39.9 21.8 39 21.2 38.2 20.7 37.4 19.9 36.8 19 36.7 18 37 17.1 37.4 16.2 37.7 15.2 38.1 14.3 38 13.5 37.4 13.1 36.5 13.2 35.5 13.2 34.5 13.3 33.5 13.3 32.5 12.8 31.7 12 31.2 11 31 10.1 30.7 9.1 30.5 8.2 30.1 7.6 29.3 7.5 28.3 8 27.5 8.7 26.7 9.3 26 9.9 25.2 10.4 24.3 10.3 23.4 9.7 22.6 9.1 21.8 8.4 21 7.8 20.3 7.5 19.4 7.7 18.4 8.5 17.7 9.4 17.4 10.4 17.2 11.3 17 12.3 16.7 13 16.1 13.4 15.2 13.3 14.2 13.2 13.2 13.2 12.2 13.2 11.2 13.7 10.4 14.6 9.9 15.6 10 16.5 10.4 17.4 10.8 18.3 11.1 19.3 11.4 20.2 11.1 20.9 10.4 21.4 9.5 21.9 8.7Z",
|
|
58
|
+
].join(";");
|
|
59
|
+
|
|
60
|
+
const ROTATE_KEY_TIMES = "0; 0.14; 0.29; 0.43; 0.57; 0.71; 0.86; 1";
|
|
61
|
+
const ROTATE_KEY_SPLINES = [
|
|
62
|
+
"0.5 0.2 0 0.8",
|
|
63
|
+
"0.5 0.2 0 0.8",
|
|
64
|
+
"0.5 0.2 0 0.8",
|
|
65
|
+
"0.5 0.2 0 0.8",
|
|
66
|
+
"0.5 0.2 0 0.8",
|
|
67
|
+
"0.5 0.2 0 0.8",
|
|
68
|
+
"0.5 0.2 0 0.8",
|
|
69
|
+
].join("; ");
|
|
70
|
+
const ROTATE_VALUES =
|
|
71
|
+
"0 24 24; 154 24 24; 309 24 24; 463 24 24; 617 24 24; 771 24 24; 926 24 24; 1080 24 24";
|
|
72
|
+
|
|
73
|
+
// ─── Determinate: Circle → SoftBurst path data (viewBox 4 4 40 40, cx=24, cy=24) ─
|
|
74
|
+
// Circle: perfect circle, r≈17 → matches the contained indicator's visual weight
|
|
75
|
+
const DETERMINATE_CIRCLE =
|
|
76
|
+
"M24 7 C34.49 7 41 13.51 41 24 C41 34.49 34.49 41 24 41 C13.51 41 7 34.49 7 24 C7 13.51 13.51 7 24 7 Z";
|
|
77
|
+
|
|
78
|
+
// SoftBurst: shape 1 from the indeterminate sequence (same visual as the spec)
|
|
79
|
+
const DETERMINATE_SOFT_BURST =
|
|
80
|
+
"M20.9 10.4 21.4 9.5 21.9 8.7 22.5 7.8 23.2 7.2 24.2 7 25.1 7.4 25.7 8.1 26.2 9 26.8 9.8 27.3 10.6 28.1 11.2 29 11.3 30 11 30.9 10.6 31.8 10.3 32.8 9.9 33.7 10 34.5 10.6 34.9 11.5 34.8 12.5 34.8 13.5 34.7 14.5 34.7 15.5 35.2 16.3 36 16.8 37 17.1 37.9 17.3 38.9 17.5 39.8 17.9 40.4 18.7 40.5 19.7 40 20.5 39.4 21.3 38.7 22 38.1 22.8 37.6 23.7 37.7 24.6 38.3 25.5 38.9 26.2 39.6 27 40.2 27.7 40.5 28.7 40.3 29.6 39.5 30.3 38.6 30.6 37.6 30.8 36.7 31 35.7 31.3 35 31.9 34.6 32.8 34.7 33.8 34.8 34.8 34.9 35.8 34.8 36.8 34.3 37.6 33.4 38.1 32.4 38 31.5 37.6 30.6 37.2 29.7 36.9 28.7 36.6 27.8 36.9 27.1 37.6 26.6 38.5 26.1 39.3 25.5 40.2 24.8 40.8 23.8 41 22.9 40.6 22.3 39.9 21.8 39 21.2 38.2 20.7 37.4 19.9 36.8 19 36.7 18 37 17.1 37.4 16.2 37.7 15.2 38.1 14.3 38 13.5 37.4 13.1 36.5 13.2 35.5 13.2 34.5 13.3 33.5 13.3 32.5 12.8 31.7 12 31.2 11 31 10.1 30.7 9.1 30.5 8.2 30.1 7.6 29.3 7.5 28.3 8 27.5 8.7 26.7 9.3 26 9.9 25.2 10.4 24.3 10.3 23.4 9.7 22.5 9.1 21.8 8.4 21 7.8 20.3 7.5 19.3 7.7 18.4 8.5 17.7 9.4 17.4 10.4 17.2 11.3 17 12.3 16.7 13 16.1 13.4 15.2 13.3 14.2 13.2 13.2 13.1 12.2 13.2 11.2 13.7 10.4 14.6 9.9 15.6 10 16.5 10.4 17.4 10.8 18.3 11.1 19.3 11.4 20.2 11.1Z";
|
|
81
|
+
|
|
82
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
83
|
+
export interface LoadingIndicatorProps
|
|
84
|
+
extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
|
|
85
|
+
/**
|
|
86
|
+
* Visual style variant.
|
|
87
|
+
* - `uncontained` (default): bare indicator, no container background
|
|
88
|
+
* - `contained`: indicator inside a circular container
|
|
89
|
+
* (MD3 spec: 38dp container / 24dp active indicator)
|
|
90
|
+
*/
|
|
91
|
+
variant?: "uncontained" | "contained";
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Indicator size in dp (pixels). Clamped to the MD3 spec range [24dp, 240dp].
|
|
95
|
+
* Defaults to 48dp.
|
|
96
|
+
*
|
|
97
|
+
* Size guidelines:
|
|
98
|
+
* - Small displays: 24–48dp
|
|
99
|
+
* - Medium displays: 48–80dp
|
|
100
|
+
* - Large/XL displays (desktop): up to 240dp
|
|
101
|
+
*
|
|
102
|
+
* @default 48
|
|
103
|
+
*/
|
|
104
|
+
size?: number;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Determinate progress value between 0 and 1.
|
|
108
|
+
* - **Omit** (default) for indeterminate mode: continuous morphing loop.
|
|
109
|
+
* - **Provide** for determinate mode: Circle→SoftBurst morph + counterclockwise rotation.
|
|
110
|
+
*
|
|
111
|
+
* @example `progress={0.7}` shows 70% progress
|
|
112
|
+
*/
|
|
113
|
+
progress?: number;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Active indicator color override. Falls back to MD3 system color tokens.
|
|
117
|
+
* Supports any valid CSS color value or CSS variable.
|
|
118
|
+
* @example "#ff5722" | "var(--brand-color)"
|
|
119
|
+
*/
|
|
120
|
+
color?: string;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Required accessible label describing what is loading.
|
|
124
|
+
* @example "Loading news article"
|
|
125
|
+
*/
|
|
126
|
+
"aria-label": string;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ─── Indeterminate SVG (pure SMIL, zero JS overhead) ─────────────────────────
|
|
130
|
+
const IndeterminateSvg = React.memo(function IndeterminateSvg({
|
|
131
|
+
size,
|
|
132
|
+
}: {
|
|
133
|
+
size: number;
|
|
134
|
+
}) {
|
|
135
|
+
// Trì hoãn chèn <animate> 1 frame bằng RequestAnimationFrame
|
|
136
|
+
// giúp bypass hoàn toàn lỗi Chromium đóng băng SMIL khi parent container đang thực hiện CSS transform/animation.
|
|
137
|
+
const [ready, setReady] = React.useState(false);
|
|
138
|
+
React.useEffect(() => {
|
|
139
|
+
const raf = requestAnimationFrame(() => setReady(true));
|
|
140
|
+
return () => cancelAnimationFrame(raf);
|
|
141
|
+
}, []);
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<svg
|
|
145
|
+
viewBox="4 4 40 40"
|
|
146
|
+
width={size}
|
|
147
|
+
height={size}
|
|
148
|
+
fill="currentColor"
|
|
149
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
150
|
+
aria-hidden="true"
|
|
151
|
+
focusable="false"
|
|
152
|
+
>
|
|
153
|
+
<path d={SHAPE_VALUES.split(";")[0]}>
|
|
154
|
+
{ready && (
|
|
155
|
+
<>
|
|
156
|
+
<animate
|
|
157
|
+
attributeName="d"
|
|
158
|
+
dur="5s"
|
|
159
|
+
repeatCount="indefinite"
|
|
160
|
+
calcMode="spline"
|
|
161
|
+
keySplines={SHAPE_KEY_SPLINES}
|
|
162
|
+
keyTimes={SHAPE_KEY_TIMES}
|
|
163
|
+
values={SHAPE_VALUES}
|
|
164
|
+
/>
|
|
165
|
+
<animateTransform
|
|
166
|
+
attributeName="transform"
|
|
167
|
+
attributeType="XML"
|
|
168
|
+
type="rotate"
|
|
169
|
+
dur="5s"
|
|
170
|
+
repeatCount="indefinite"
|
|
171
|
+
calcMode="spline"
|
|
172
|
+
keySplines={ROTATE_KEY_SPLINES}
|
|
173
|
+
keyTimes={ROTATE_KEY_TIMES}
|
|
174
|
+
values={ROTATE_VALUES}
|
|
175
|
+
/>
|
|
176
|
+
</>
|
|
177
|
+
)}
|
|
178
|
+
</path>
|
|
179
|
+
</svg>
|
|
180
|
+
);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// ─── Determinate SVG (React-controlled morph + counterclockwise rotation) ────
|
|
184
|
+
// Uses CSS transition for smooth d attribute changes where supported (Chromium),
|
|
185
|
+
// with a style-based transform fallback for rotation.
|
|
186
|
+
const DeterminateSvg = React.memo(function DeterminateSvg({
|
|
187
|
+
size,
|
|
188
|
+
progress,
|
|
189
|
+
}: {
|
|
190
|
+
size: number;
|
|
191
|
+
progress: number;
|
|
192
|
+
}) {
|
|
193
|
+
// Clamp and interpolate progress between 0 (circle) and 1 (soft burst)
|
|
194
|
+
const p = Math.min(1, Math.max(0, progress));
|
|
195
|
+
|
|
196
|
+
// Counterclockwise rotation: 0° at 0% → -180° at 100% (per Android spec)
|
|
197
|
+
const rotation = -180 * p;
|
|
198
|
+
|
|
199
|
+
// The path morphs from circle (p=0) to soft-burst (p=1).
|
|
200
|
+
// We use a SMIL animate with begin="indefinite" that we snap to progress.
|
|
201
|
+
// For maximum compatibility, render the correct endpoint shape based on progress,
|
|
202
|
+
// relying on the CSS `d` property transition for smooth animation.
|
|
203
|
+
const pathD = p < 0.5 ? DETERMINATE_CIRCLE : DETERMINATE_SOFT_BURST;
|
|
204
|
+
|
|
205
|
+
return (
|
|
206
|
+
<svg
|
|
207
|
+
viewBox="4 4 40 40"
|
|
208
|
+
width={size}
|
|
209
|
+
height={size}
|
|
210
|
+
fill="currentColor"
|
|
211
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
212
|
+
aria-hidden="true"
|
|
213
|
+
focusable="false"
|
|
214
|
+
style={{
|
|
215
|
+
transform: `rotate(${rotation}deg)`,
|
|
216
|
+
transition: "transform 0.3s ease",
|
|
217
|
+
}}
|
|
218
|
+
>
|
|
219
|
+
<path d={pathD} style={{ transition: "d 0.3s ease" }} />
|
|
220
|
+
</svg>
|
|
221
|
+
);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// ─── Main Component ───────────────────────────────────────────────────────────
|
|
225
|
+
export const LoadingIndicator = React.forwardRef<
|
|
226
|
+
HTMLDivElement,
|
|
227
|
+
LoadingIndicatorProps
|
|
228
|
+
>(
|
|
229
|
+
(
|
|
230
|
+
{
|
|
231
|
+
variant = "uncontained",
|
|
232
|
+
size = 48,
|
|
233
|
+
progress,
|
|
234
|
+
color,
|
|
235
|
+
className,
|
|
236
|
+
"aria-label": ariaLabel,
|
|
237
|
+
style,
|
|
238
|
+
...restProps
|
|
239
|
+
},
|
|
240
|
+
ref,
|
|
241
|
+
) => {
|
|
242
|
+
const isContained = variant === "contained";
|
|
243
|
+
const isDeterminate = progress !== undefined;
|
|
244
|
+
|
|
245
|
+
// MD3 spec: strict size range [24dp, 240dp]
|
|
246
|
+
const clampedSize = Math.min(240, Math.max(24, size));
|
|
247
|
+
|
|
248
|
+
// Proportional scaling based on the default 48dp spec size
|
|
249
|
+
const scaleFactor = clampedSize / 48;
|
|
250
|
+
|
|
251
|
+
// MD3 spec: contained → 38dp container, 24dp active indicator
|
|
252
|
+
// Ratio: 38/24 = 1.583 — preserved at all sizes
|
|
253
|
+
const containerSize = isContained ? 38 * scaleFactor : undefined;
|
|
254
|
+
const indicatorSize = isContained ? 24 * scaleFactor : clampedSize;
|
|
255
|
+
|
|
256
|
+
// MD3 color tokens (with per-variant defaults)
|
|
257
|
+
const activeColor = isContained
|
|
258
|
+
? (color ?? "var(--md-sys-color-indicator-contained-active)")
|
|
259
|
+
: (color ?? "var(--md-sys-color-indicator-active)");
|
|
260
|
+
|
|
261
|
+
// aria-valuenow is only set in determinate mode (per ARIA spec)
|
|
262
|
+
const ariaNow = isDeterminate
|
|
263
|
+
? Math.round(Math.min(1, Math.max(0, progress)) * 100)
|
|
264
|
+
: undefined;
|
|
265
|
+
|
|
266
|
+
return (
|
|
267
|
+
<div
|
|
268
|
+
ref={ref}
|
|
269
|
+
role="progressbar"
|
|
270
|
+
aria-label={ariaLabel}
|
|
271
|
+
aria-valuemin={0}
|
|
272
|
+
aria-valuemax={100}
|
|
273
|
+
aria-valuenow={ariaNow}
|
|
274
|
+
className={cn(
|
|
275
|
+
"inline-flex items-center justify-center shrink-0",
|
|
276
|
+
className,
|
|
277
|
+
)}
|
|
278
|
+
style={{
|
|
279
|
+
width: clampedSize,
|
|
280
|
+
height: clampedSize,
|
|
281
|
+
color: activeColor,
|
|
282
|
+
...style,
|
|
283
|
+
}}
|
|
284
|
+
{...restProps}
|
|
285
|
+
>
|
|
286
|
+
{isContained ? (
|
|
287
|
+
<div
|
|
288
|
+
className="flex items-center justify-center rounded-full"
|
|
289
|
+
style={{
|
|
290
|
+
width: containerSize,
|
|
291
|
+
height: containerSize,
|
|
292
|
+
backgroundColor:
|
|
293
|
+
"var(--md-sys-color-indicator-contained-container)",
|
|
294
|
+
}}
|
|
295
|
+
>
|
|
296
|
+
{isDeterminate ? (
|
|
297
|
+
<DeterminateSvg size={indicatorSize} progress={progress} />
|
|
298
|
+
) : (
|
|
299
|
+
<IndeterminateSvg size={indicatorSize} />
|
|
300
|
+
)}
|
|
301
|
+
</div>
|
|
302
|
+
) : isDeterminate ? (
|
|
303
|
+
<DeterminateSvg size={indicatorSize} progress={progress} />
|
|
304
|
+
) : (
|
|
305
|
+
<IndeterminateSvg size={indicatorSize} />
|
|
306
|
+
)}
|
|
307
|
+
</div>
|
|
308
|
+
);
|
|
309
|
+
},
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
LoadingIndicator.displayName = "LoadingIndicator";
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// ─── MD3 Expressive Menu — ContextMenu (right-click / long-press) ─────────────
|
|
2
|
+
// Wraps @radix-ui/react-context-menu and provides MenuContext with
|
|
3
|
+
// menuPrimitive="context" so MenuItem automatically selects ContextMenu primitives.
|
|
4
|
+
//
|
|
5
|
+
// Visual styling is identical to Menu — baseline or expressive variant.
|
|
6
|
+
// Positioning, scroll lock, and outside click are all handled by Radix.
|
|
7
|
+
import * as RxContextMenu from "@radix-ui/react-context-menu";
|
|
8
|
+
import { AnimatePresence, m } from "motion/react";
|
|
9
|
+
import * as React from "react";
|
|
10
|
+
import { cn } from "../../lib/utils";
|
|
11
|
+
import { FAST_SPATIAL_SPRING } from "./menu-animations";
|
|
12
|
+
import { MenuProvider, useMenuContext } from "./menu-context";
|
|
13
|
+
import {
|
|
14
|
+
BASELINE_COLORS,
|
|
15
|
+
MENU_CONTAINER_SHAPE,
|
|
16
|
+
MENU_GROUP_GAP,
|
|
17
|
+
MENU_MAX_WIDTH,
|
|
18
|
+
MENU_MIN_WIDTH,
|
|
19
|
+
MENU_POPUP_PADDING_Y,
|
|
20
|
+
STANDARD_COLORS,
|
|
21
|
+
VIBRANT_COLORS,
|
|
22
|
+
} from "./menu-tokens";
|
|
23
|
+
import type {
|
|
24
|
+
ContextMenuContentProps,
|
|
25
|
+
ContextMenuProps,
|
|
26
|
+
ContextMenuTriggerProps,
|
|
27
|
+
} from "./menu-types";
|
|
28
|
+
|
|
29
|
+
// ─── ContextMenu (Root) ───────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Root of a context menu (right-click / long-press triggered popup).
|
|
33
|
+
*
|
|
34
|
+
* Wraps `@radix-ui/react-context-menu` Root and provides `MenuContext` with
|
|
35
|
+
* `menuPrimitive="context"` so nested `MenuItem` components automatically use
|
|
36
|
+
* ContextMenu Radix primitives for correct accessibility and keyboard navigation.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* // Baseline context menu
|
|
40
|
+
* <ContextMenu variant="baseline">
|
|
41
|
+
* <ContextMenuTrigger asChild>
|
|
42
|
+
* <div>Right-click me</div>
|
|
43
|
+
* </ContextMenuTrigger>
|
|
44
|
+
* <ContextMenuContent>
|
|
45
|
+
* <MenuItem>Copy</MenuItem>
|
|
46
|
+
* <MenuItem>Paste</MenuItem>
|
|
47
|
+
* </ContextMenuContent>
|
|
48
|
+
* </ContextMenu>
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* // Expressive context menu with groups
|
|
52
|
+
* <ContextMenu variant="expressive">
|
|
53
|
+
* <ContextMenuTrigger asChild><canvas /></ContextMenuTrigger>
|
|
54
|
+
* <ContextMenuContent>
|
|
55
|
+
* <MenuGroup>
|
|
56
|
+
* <MenuItem>Cut</MenuItem>
|
|
57
|
+
* <MenuItem>Copy</MenuItem>
|
|
58
|
+
* </MenuGroup>
|
|
59
|
+
* <MenuGroup>
|
|
60
|
+
* <MenuItem>Paste</MenuItem>
|
|
61
|
+
* </MenuGroup>
|
|
62
|
+
* </ContextMenuContent>
|
|
63
|
+
* </ContextMenu>
|
|
64
|
+
*/
|
|
65
|
+
export function ContextMenu({
|
|
66
|
+
children,
|
|
67
|
+
variant = "baseline",
|
|
68
|
+
colorVariant = "standard",
|
|
69
|
+
}: ContextMenuProps) {
|
|
70
|
+
const [open, setOpen] = React.useState(false);
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<MenuProvider
|
|
74
|
+
variant={variant}
|
|
75
|
+
colorVariant={colorVariant}
|
|
76
|
+
menuPrimitive="context"
|
|
77
|
+
open={open}
|
|
78
|
+
onOpenChange={setOpen}
|
|
79
|
+
>
|
|
80
|
+
<RxContextMenu.Root onOpenChange={setOpen}>{children}</RxContextMenu.Root>
|
|
81
|
+
</MenuProvider>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
ContextMenu.displayName = "ContextMenu";
|
|
85
|
+
|
|
86
|
+
// ─── ContextMenuTrigger ───────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The trigger area for a ContextMenu. Right-click (or long-press on touch)
|
|
90
|
+
* within this area opens the context menu.
|
|
91
|
+
*
|
|
92
|
+
* Use `asChild` to apply trigger behavior to your own element without an
|
|
93
|
+
* extra wrapper `<span>`.
|
|
94
|
+
*/
|
|
95
|
+
export const ContextMenuTrigger = React.forwardRef<
|
|
96
|
+
React.ComponentRef<typeof RxContextMenu.Trigger>,
|
|
97
|
+
ContextMenuTriggerProps &
|
|
98
|
+
React.ComponentPropsWithoutRef<typeof RxContextMenu.Trigger>
|
|
99
|
+
>(({ children, asChild = true, ...props }, ref) => (
|
|
100
|
+
<RxContextMenu.Trigger ref={ref} asChild={asChild} {...props}>
|
|
101
|
+
{children}
|
|
102
|
+
</RxContextMenu.Trigger>
|
|
103
|
+
));
|
|
104
|
+
ContextMenuTrigger.displayName = "ContextMenuTrigger";
|
|
105
|
+
|
|
106
|
+
// ─── ContextMenuContent (popup panel) ─────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The popup container for the context menu's contents.
|
|
110
|
+
*
|
|
111
|
+
* Renders into a portal via Radix. Uses Framer Motion `AnimatePresence` for
|
|
112
|
+
* smooth enter/exit animations. Transform-origin is automatically set by
|
|
113
|
+
* Radix via `--radix-context-menu-content-transform-origin`.
|
|
114
|
+
*
|
|
115
|
+
* Visual styling follows `variant` from `ContextMenuContext`:
|
|
116
|
+
* - `baseline` → CornerExtraSmall (4px), standard M3 styling
|
|
117
|
+
* - `expressive` → rounded-2xl, shape-morphing groups, elevation-2
|
|
118
|
+
*
|
|
119
|
+
* @param hasOverflow - Set true when using SubMenu to prevent clipping
|
|
120
|
+
*/
|
|
121
|
+
export const ContextMenuContent = React.forwardRef<
|
|
122
|
+
React.ComponentRef<typeof RxContextMenu.Content>,
|
|
123
|
+
ContextMenuContentProps &
|
|
124
|
+
Omit<
|
|
125
|
+
React.ComponentPropsWithoutRef<typeof RxContextMenu.Content>,
|
|
126
|
+
"asChild"
|
|
127
|
+
>
|
|
128
|
+
>(
|
|
129
|
+
(
|
|
130
|
+
{
|
|
131
|
+
children,
|
|
132
|
+
hasOverflow = false,
|
|
133
|
+
colorVariant: propColorVariant,
|
|
134
|
+
separatorStyle = "gap",
|
|
135
|
+
className,
|
|
136
|
+
...props
|
|
137
|
+
},
|
|
138
|
+
ref,
|
|
139
|
+
) => {
|
|
140
|
+
const {
|
|
141
|
+
open,
|
|
142
|
+
variant,
|
|
143
|
+
colorVariant: contextColorVariant,
|
|
144
|
+
} = useMenuContext();
|
|
145
|
+
const colorVariant = propColorVariant ?? contextColorVariant;
|
|
146
|
+
|
|
147
|
+
const colors =
|
|
148
|
+
variant === "baseline"
|
|
149
|
+
? BASELINE_COLORS
|
|
150
|
+
: colorVariant === "vibrant"
|
|
151
|
+
? VIBRANT_COLORS
|
|
152
|
+
: STANDARD_COLORS;
|
|
153
|
+
|
|
154
|
+
const isExpressiveGap =
|
|
155
|
+
variant === "expressive" && separatorStyle === "gap";
|
|
156
|
+
|
|
157
|
+
const containerClassName =
|
|
158
|
+
variant === "expressive"
|
|
159
|
+
? cn(
|
|
160
|
+
"z-50 flex flex-col w-full",
|
|
161
|
+
MENU_MIN_WIDTH,
|
|
162
|
+
MENU_MAX_WIDTH,
|
|
163
|
+
isExpressiveGap ? MENU_GROUP_GAP : "",
|
|
164
|
+
isExpressiveGap ? "bg-transparent" : colors.containerBg,
|
|
165
|
+
isExpressiveGap ? "" : "rounded-2xl",
|
|
166
|
+
isExpressiveGap ? "" : "elevation-2",
|
|
167
|
+
hasOverflow || isExpressiveGap
|
|
168
|
+
? "overflow-visible"
|
|
169
|
+
: "overflow-hidden",
|
|
170
|
+
"outline-none",
|
|
171
|
+
className,
|
|
172
|
+
)
|
|
173
|
+
: cn(
|
|
174
|
+
"z-50 flex flex-col",
|
|
175
|
+
MENU_MIN_WIDTH,
|
|
176
|
+
MENU_MAX_WIDTH,
|
|
177
|
+
MENU_POPUP_PADDING_Y,
|
|
178
|
+
MENU_GROUP_GAP,
|
|
179
|
+
colors.containerBg,
|
|
180
|
+
MENU_CONTAINER_SHAPE,
|
|
181
|
+
"elevation-2",
|
|
182
|
+
hasOverflow ? "overflow-visible" : "overflow-hidden",
|
|
183
|
+
"outline-none",
|
|
184
|
+
className,
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
// Helper to recursively flatten fragments
|
|
188
|
+
const flattenChildren = (nodes: React.ReactNode): React.ReactElement[] => {
|
|
189
|
+
return React.Children.toArray(nodes).reduce(
|
|
190
|
+
(acc: React.ReactElement[], child) => {
|
|
191
|
+
if (React.isValidElement(child)) {
|
|
192
|
+
if (child.type === React.Fragment) {
|
|
193
|
+
return acc.concat(
|
|
194
|
+
flattenChildren(
|
|
195
|
+
(child as React.ReactElement<{ children?: React.ReactNode }>)
|
|
196
|
+
.props.children,
|
|
197
|
+
),
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
acc.push(child as React.ReactElement);
|
|
201
|
+
}
|
|
202
|
+
return acc;
|
|
203
|
+
},
|
|
204
|
+
[],
|
|
205
|
+
);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
let renderedChildren: React.ReactNode = children;
|
|
209
|
+
|
|
210
|
+
if (variant === "expressive") {
|
|
211
|
+
const validChildren = flattenChildren(children);
|
|
212
|
+
const groupCount = validChildren.length;
|
|
213
|
+
|
|
214
|
+
const enhancedChildren = validChildren.map((child, i) =>
|
|
215
|
+
React.cloneElement(
|
|
216
|
+
child as React.ReactElement<{
|
|
217
|
+
index?: number;
|
|
218
|
+
count?: number;
|
|
219
|
+
isGapVariant?: boolean;
|
|
220
|
+
className?: string;
|
|
221
|
+
}>,
|
|
222
|
+
{
|
|
223
|
+
index: i,
|
|
224
|
+
count: groupCount,
|
|
225
|
+
isGapVariant: isExpressiveGap,
|
|
226
|
+
},
|
|
227
|
+
),
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
renderedChildren =
|
|
231
|
+
separatorStyle === "divider"
|
|
232
|
+
? enhancedChildren.reduce<React.ReactNode[]>((acc, child, i) => {
|
|
233
|
+
if (i > 0) {
|
|
234
|
+
acc.push(
|
|
235
|
+
<hr
|
|
236
|
+
key={`divider-${(child as React.ReactElement).key || i}`}
|
|
237
|
+
className={cn(
|
|
238
|
+
"mx-3 my-0.5 h-px border-0 bg-m3-outline-variant",
|
|
239
|
+
)}
|
|
240
|
+
/>,
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
acc.push(child);
|
|
244
|
+
return acc;
|
|
245
|
+
}, [])
|
|
246
|
+
: enhancedChildren;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
<AnimatePresence>
|
|
251
|
+
{open && (
|
|
252
|
+
<RxContextMenu.Portal forceMount>
|
|
253
|
+
<RxContextMenu.Content ref={ref} asChild forceMount {...props}>
|
|
254
|
+
<m.div
|
|
255
|
+
className={containerClassName}
|
|
256
|
+
initial={{ opacity: 0, scale: 0.95, y: -4 }}
|
|
257
|
+
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
258
|
+
exit={{ opacity: 0, scale: 0.95, y: -4 }}
|
|
259
|
+
transition={FAST_SPATIAL_SPRING}
|
|
260
|
+
style={{
|
|
261
|
+
...(props.style as React.CSSProperties),
|
|
262
|
+
transformOrigin:
|
|
263
|
+
"var(--radix-context-menu-content-transform-origin)",
|
|
264
|
+
}}
|
|
265
|
+
>
|
|
266
|
+
{renderedChildren}
|
|
267
|
+
</m.div>
|
|
268
|
+
</RxContextMenu.Content>
|
|
269
|
+
</RxContextMenu.Portal>
|
|
270
|
+
)}
|
|
271
|
+
</AnimatePresence>
|
|
272
|
+
);
|
|
273
|
+
},
|
|
274
|
+
);
|
|
275
|
+
ContextMenuContent.displayName = "ContextMenuContent";
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// ─── MD3 Expressive Menu — Barrel Export ─────────────────────────────────────
|
|
2
|
+
|
|
3
|
+
// Components — ContextMenu (right-click / long-press popup)
|
|
4
|
+
export {
|
|
5
|
+
ContextMenu,
|
|
6
|
+
ContextMenuContent,
|
|
7
|
+
ContextMenuTrigger,
|
|
8
|
+
} from "./context-menu";
|
|
9
|
+
// Components — Menu (dropdown popup)
|
|
10
|
+
export { Menu, MenuContent, MenuTrigger } from "./menu";
|
|
11
|
+
|
|
12
|
+
// Animation variants (for consumers extending animations)
|
|
13
|
+
export {
|
|
14
|
+
CHECK_ICON_VARIANTS,
|
|
15
|
+
FAST_EFFECTS_TRANSITION,
|
|
16
|
+
FAST_SPATIAL_SPRING,
|
|
17
|
+
MENU_CHECK_ICON_SIZE,
|
|
18
|
+
MENU_CONTAINER_VARIANTS,
|
|
19
|
+
SUBMENU_CONTAINER_VARIANTS,
|
|
20
|
+
} from "./menu-animations";
|
|
21
|
+
|
|
22
|
+
// Context (for advanced usage / extending)
|
|
23
|
+
export { MenuProvider, useMenuContext } from "./menu-context";
|
|
24
|
+
export { MenuDivider } from "./menu-divider";
|
|
25
|
+
export { MenuGroup } from "./menu-group";
|
|
26
|
+
export { MenuItem } from "./menu-item";
|
|
27
|
+
|
|
28
|
+
// Tokens (for consumers who need raw values)
|
|
29
|
+
export {
|
|
30
|
+
DIVIDER_COLOR,
|
|
31
|
+
DIVIDER_PADDING,
|
|
32
|
+
GROUP_SHAPES,
|
|
33
|
+
ITEM_SHAPE_CLASSES,
|
|
34
|
+
MENU_GROUP_GAP,
|
|
35
|
+
MENU_ICON_SIZE,
|
|
36
|
+
MENU_ITEM_MIN_HEIGHT,
|
|
37
|
+
MENU_MAX_WIDTH,
|
|
38
|
+
MENU_MIN_WIDTH,
|
|
39
|
+
STANDARD_COLORS,
|
|
40
|
+
VIBRANT_COLORS,
|
|
41
|
+
} from "./menu-tokens";
|
|
42
|
+
|
|
43
|
+
// Types
|
|
44
|
+
export type {
|
|
45
|
+
// ContextMenu
|
|
46
|
+
ContextMenuContentProps,
|
|
47
|
+
ContextMenuProps,
|
|
48
|
+
ContextMenuTriggerProps,
|
|
49
|
+
MenuColorVariant,
|
|
50
|
+
MenuContentProps,
|
|
51
|
+
MenuDividerProps,
|
|
52
|
+
MenuGroupPosition,
|
|
53
|
+
MenuGroupProps,
|
|
54
|
+
MenuItemPosition,
|
|
55
|
+
MenuItemProps,
|
|
56
|
+
MenuPrimitive,
|
|
57
|
+
MenuProps,
|
|
58
|
+
MenuTriggerProps,
|
|
59
|
+
MenuVariant,
|
|
60
|
+
SubMenuProps,
|
|
61
|
+
// Vertical Menu
|
|
62
|
+
VerticalMenuContentProps,
|
|
63
|
+
VerticalMenuDividerProps,
|
|
64
|
+
VerticalMenuGroupProps,
|
|
65
|
+
VerticalMenuProps,
|
|
66
|
+
VerticalMenuSeparatorStyle,
|
|
67
|
+
} from "./menu-types";
|
|
68
|
+
|
|
69
|
+
export { SubMenu } from "./sub-menu";
|
|
70
|
+
|
|
71
|
+
// Vertical Menu components (static, always-visible — unchanged)
|
|
72
|
+
export {
|
|
73
|
+
VerticalMenu,
|
|
74
|
+
VerticalMenuContent,
|
|
75
|
+
VerticalMenuDivider,
|
|
76
|
+
VerticalMenuGroup,
|
|
77
|
+
} from "./vertical-menu";
|