@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,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file slider.tokens.ts
|
|
3
|
+
* MD3 Expressive Slider — Design tokens ported from:
|
|
4
|
+
* - SliderTokens.kt (Jetpack Compose M3 Expressive)
|
|
5
|
+
* - M3 Expressive visual spec (handle squeeze, gaps, asymmetric radii)
|
|
6
|
+
*
|
|
7
|
+
* All dimensional values are in px (dp equivalents for web).
|
|
8
|
+
* Colors reference CSS custom properties — do NOT hardcode hex.
|
|
9
|
+
* @see docs/m3/sliders/Slider.kt
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { SliderTrackSize } from "./slider.types";
|
|
13
|
+
|
|
14
|
+
// ─── Dimensional tokens ───────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Core dimensional design tokens for the MD3 Expressive Slider.
|
|
18
|
+
* Maps directly from SliderTokens.kt to CSS/JS values.
|
|
19
|
+
*/
|
|
20
|
+
export const SliderTokens = {
|
|
21
|
+
// ── Track sizes (height for horizontal, width for vertical) ────────────────
|
|
22
|
+
trackSizes: {
|
|
23
|
+
xs: 16,
|
|
24
|
+
s: 24,
|
|
25
|
+
m: 40,
|
|
26
|
+
l: 56,
|
|
27
|
+
xl: 96,
|
|
28
|
+
} satisfies Record<SliderTrackSize, number>,
|
|
29
|
+
|
|
30
|
+
// ── Track shapes (border radius) ──────────────────────────────────────────
|
|
31
|
+
trackShapes: {
|
|
32
|
+
xs: 8,
|
|
33
|
+
s: 8,
|
|
34
|
+
m: 12,
|
|
35
|
+
l: 16,
|
|
36
|
+
xl: 28,
|
|
37
|
+
} satisfies Record<SliderTrackSize, number>,
|
|
38
|
+
|
|
39
|
+
// ── Thumb (handle) ────────────────────────────────────────────────────────
|
|
40
|
+
/** Thumb height grows with track size */
|
|
41
|
+
thumbHeights: {
|
|
42
|
+
xs: 44,
|
|
43
|
+
s: 44,
|
|
44
|
+
m: 52,
|
|
45
|
+
l: 68,
|
|
46
|
+
xl: 108,
|
|
47
|
+
} satisfies Record<SliderTrackSize, number>,
|
|
48
|
+
/**
|
|
49
|
+
* Thumb width at rest and on hover.
|
|
50
|
+
* MD3 Expressive handle pill: 4dp wide.
|
|
51
|
+
*/
|
|
52
|
+
thumbWidthDefault: 4,
|
|
53
|
+
/**
|
|
54
|
+
* Thumb width while pressed/dragging.
|
|
55
|
+
* "Squeeze" animation: 4dp → 2dp (M3 Expressive behavior).
|
|
56
|
+
*/
|
|
57
|
+
thumbWidthPressed: 2,
|
|
58
|
+
|
|
59
|
+
// ── Track–thumb gap ───────────────────────────────────────────────────────
|
|
60
|
+
/**
|
|
61
|
+
* Transparent gap between the active/inactive track segments and the thumb.
|
|
62
|
+
* Maps to ActiveHandleLeadingSpace / ActiveHandleTrailingSpace = 6dp.
|
|
63
|
+
*
|
|
64
|
+
* This gap is rendered by mathematically subtracting it from the track
|
|
65
|
+
* segment width — NOT using margin/padding.
|
|
66
|
+
*/
|
|
67
|
+
thumbGap: 6,
|
|
68
|
+
|
|
69
|
+
// ── Track corner radii ────────────────────────────────────────────────────
|
|
70
|
+
/**
|
|
71
|
+
* Corner radius on the INNER ends of each track segment (facing the thumb).
|
|
72
|
+
* Fixed to 2px according to MD3 Expressive Slider specs (TrackInsideCornerSize).
|
|
73
|
+
* Outer ends use `trackSize / 2` (pill cap) — computed inline per component.
|
|
74
|
+
*/
|
|
75
|
+
trackInnerRadius: 2,
|
|
76
|
+
|
|
77
|
+
// ── Stop indicator / Tick ─────────────────────────────────────────────────
|
|
78
|
+
/** Tick dot size (width and height). = 4dp. */
|
|
79
|
+
tickSize: 4,
|
|
80
|
+
|
|
81
|
+
// ── Touch target ─────────────────────────────────────────────────────────
|
|
82
|
+
/**
|
|
83
|
+
* Minimum touch target for the thumb wrapper.
|
|
84
|
+
* MD3 requires 48dp minimum touch target for interactive elements.
|
|
85
|
+
*/
|
|
86
|
+
thumbTouchTarget: 48,
|
|
87
|
+
|
|
88
|
+
// ── Value indicator ───────────────────────────────────────────────────────
|
|
89
|
+
/** Offset above the thumb center for the value indicator tooltip. */
|
|
90
|
+
valueIndicatorOffset: 52,
|
|
91
|
+
|
|
92
|
+
// ── Inset icon ────────────────────────────────────────────────────────────
|
|
93
|
+
/** Standard icon size map for inset icons inside the track. */
|
|
94
|
+
insetIconSizes: {
|
|
95
|
+
xs: 0,
|
|
96
|
+
s: 0,
|
|
97
|
+
m: 24,
|
|
98
|
+
l: 24,
|
|
99
|
+
xl: 32,
|
|
100
|
+
} satisfies Record<SliderTrackSize, number>,
|
|
101
|
+
/**
|
|
102
|
+
* Padding between icon and track edge (horizontal).
|
|
103
|
+
* Keeps the icon visually centered within the pill track.
|
|
104
|
+
*/
|
|
105
|
+
insetIconPadding: 4,
|
|
106
|
+
} as const;
|
|
107
|
+
|
|
108
|
+
// ─── Color tokens ─────────────────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* CSS custom property references for Slider colors.
|
|
112
|
+
* Maps to --md-sys-color-* tokens in the MD3 theme system.
|
|
113
|
+
*
|
|
114
|
+
* DO NOT hardcode hex/rgba values — these references automatically
|
|
115
|
+
* adapt to light/dark theme.
|
|
116
|
+
*/
|
|
117
|
+
export const SliderColors = {
|
|
118
|
+
// ── Track ─────────────────────────────────────────────────────────────────
|
|
119
|
+
/** Active track color. Maps to ActiveTrackColor token. */
|
|
120
|
+
activeTrack: "var(--md-sys-color-primary)",
|
|
121
|
+
/** Inactive track color. Maps to InactiveTrackColor token. */
|
|
122
|
+
inactiveTrack: "var(--md-sys-color-secondary-container)",
|
|
123
|
+
/**
|
|
124
|
+
* Disabled track color.
|
|
125
|
+
* Uses opacity + on-surface per MD3 disabled state spec.
|
|
126
|
+
*/
|
|
127
|
+
disabledActiveTrack: "var(--md-sys-color-on-surface)",
|
|
128
|
+
disabledInactiveTrack: "var(--md-sys-color-on-surface)",
|
|
129
|
+
|
|
130
|
+
// ── Thumb ─────────────────────────────────────────────────────────────────
|
|
131
|
+
/** Thumb (handle) color. Maps to HandleColor token. */
|
|
132
|
+
thumb: "var(--md-sys-color-primary)",
|
|
133
|
+
/** Disabled thumb color. */
|
|
134
|
+
disabledThumb: "var(--md-sys-color-on-surface)",
|
|
135
|
+
|
|
136
|
+
// ── Value indicator ───────────────────────────────────────────────────────
|
|
137
|
+
/** Value indicator (tooltip) background. Maps to InverseSurface. */
|
|
138
|
+
valueIndicatorBg: "var(--md-sys-color-inverse-surface)",
|
|
139
|
+
/** Value indicator (tooltip) text. Maps to InverseOnSurface. */
|
|
140
|
+
valueIndicatorText: "var(--md-sys-color-inverse-on-surface)",
|
|
141
|
+
|
|
142
|
+
// ── Ticks ────────────────────────────────────────────────────────────────
|
|
143
|
+
/**
|
|
144
|
+
* Tick on the INACTIVE portion of the track.
|
|
145
|
+
* Maps to TickMarkActiveContainerColor (in spec: primary color stands out).
|
|
146
|
+
*/
|
|
147
|
+
tickOnInactive: "var(--md-sys-color-primary)",
|
|
148
|
+
/**
|
|
149
|
+
* Tick on the ACTIVE portion of the track.
|
|
150
|
+
* Maps to TickMarkInactiveContainerColor (secondary-container blends in).
|
|
151
|
+
*/
|
|
152
|
+
tickOnActive: "var(--md-sys-color-secondary-container)",
|
|
153
|
+
disabledTick: "var(--md-sys-color-on-surface)",
|
|
154
|
+
} as const;
|
|
155
|
+
|
|
156
|
+
// ─── Animation constants ──────────────────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* FastSpatial spring for thumb width squeeze animation.
|
|
160
|
+
* Equivalent to MotionSchemeKeyTokens.FastSpatial in Compose.
|
|
161
|
+
*/
|
|
162
|
+
export const SLIDER_THUMB_SPRING = {
|
|
163
|
+
type: "spring",
|
|
164
|
+
stiffness: 500,
|
|
165
|
+
damping: 40,
|
|
166
|
+
} as const;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* DefaultSpatial spring for thumb position/track updates.
|
|
170
|
+
* Slightly looser feel for position changes.
|
|
171
|
+
*/
|
|
172
|
+
export const SLIDER_POSITION_SPRING = {
|
|
173
|
+
type: "spring",
|
|
174
|
+
stiffness: 400,
|
|
175
|
+
damping: 38,
|
|
176
|
+
} as const;
|
|
177
|
+
|
|
178
|
+
/** Color crossfade for active/inactive track color transitions. */
|
|
179
|
+
export const SLIDER_COLOR_TRANSITION = {
|
|
180
|
+
duration: 0.18,
|
|
181
|
+
ease: "easeInOut",
|
|
182
|
+
} as const;
|
|
183
|
+
|
|
184
|
+
/** Value indicator appear/disappear animation. */
|
|
185
|
+
export const SLIDER_INDICATOR_TRANSITION = {
|
|
186
|
+
type: "spring",
|
|
187
|
+
stiffness: 450,
|
|
188
|
+
damping: 32,
|
|
189
|
+
} as const;
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file slider.tsx
|
|
3
|
+
* MD3 Expressive Slider — Main composition component.
|
|
4
|
+
*
|
|
5
|
+
* Supports:
|
|
6
|
+
* - Controlled and uncontrolled usage patterns
|
|
7
|
+
* - Horizontal and vertical orientations
|
|
8
|
+
* - Discrete mode (step snapping + tick marks)
|
|
9
|
+
* - Centered active track (isCentered)
|
|
10
|
+
* - Value indicator tooltip
|
|
11
|
+
* - Full keyboard navigation (ARIA Slider pattern)
|
|
12
|
+
* - Click-to-jump on track
|
|
13
|
+
*
|
|
14
|
+
* Architecture: Flat (no Context needed for single thumb variant).
|
|
15
|
+
* Track + Thumb are internal sub-components composed here.
|
|
16
|
+
*
|
|
17
|
+
* @see https://m3.material.io/components/sliders/overview
|
|
18
|
+
* @see docs/m3/sliders/Slider.kt
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { domMax, LazyMotion } from "motion/react";
|
|
22
|
+
import * as React from "react";
|
|
23
|
+
import { cn } from "../../lib/utils";
|
|
24
|
+
import { useSliderMath } from "./hooks/useSliderMath";
|
|
25
|
+
import { SliderTokens } from "./slider.tokens";
|
|
26
|
+
import type { SliderProps } from "./slider.types";
|
|
27
|
+
import { SliderThumb } from "./slider-thumb";
|
|
28
|
+
import { SliderTrack } from "./slider-track";
|
|
29
|
+
|
|
30
|
+
// ─── Slider ───────────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
const SliderComponent = React.forwardRef<HTMLDivElement, SliderProps>(
|
|
33
|
+
(
|
|
34
|
+
{
|
|
35
|
+
value: controlledValue,
|
|
36
|
+
defaultValue,
|
|
37
|
+
onValueChange,
|
|
38
|
+
onValueChangeEnd,
|
|
39
|
+
min = 0,
|
|
40
|
+
max = 100,
|
|
41
|
+
step = 0,
|
|
42
|
+
orientation = "horizontal",
|
|
43
|
+
trackSize = "m",
|
|
44
|
+
trackShape = "md3",
|
|
45
|
+
variant = "primary",
|
|
46
|
+
isCentered = false,
|
|
47
|
+
disabled = false,
|
|
48
|
+
showValueIndicator = false,
|
|
49
|
+
showTicks = false,
|
|
50
|
+
insetIcon,
|
|
51
|
+
insetIconAtMin,
|
|
52
|
+
insetIconTrailing,
|
|
53
|
+
insetIconAtMax,
|
|
54
|
+
formatValue,
|
|
55
|
+
className,
|
|
56
|
+
"aria-label": ariaLabel,
|
|
57
|
+
"aria-labelledby": ariaLabelledBy,
|
|
58
|
+
},
|
|
59
|
+
ref,
|
|
60
|
+
) => {
|
|
61
|
+
const isHorizontal = orientation === "horizontal";
|
|
62
|
+
|
|
63
|
+
// ── Controlled / Uncontrolled state ────────────────────────────────────
|
|
64
|
+
const midpoint = min + (max - min) / 2;
|
|
65
|
+
const initialValue = defaultValue ?? midpoint;
|
|
66
|
+
const [internalValue, setInternalValue] = React.useState(initialValue);
|
|
67
|
+
const resolvedValue =
|
|
68
|
+
controlledValue !== undefined ? controlledValue : internalValue;
|
|
69
|
+
|
|
70
|
+
const { coerce, snap, toPercent, ticks } = useSliderMath({
|
|
71
|
+
min,
|
|
72
|
+
max,
|
|
73
|
+
step,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const safeValue = snap(coerce(resolvedValue));
|
|
77
|
+
const percent = toPercent(safeValue);
|
|
78
|
+
|
|
79
|
+
// ── Track ref (for click-to-jump + thumb drag constraint) ──────────────
|
|
80
|
+
const trackRef = React.useRef<HTMLDivElement>(null);
|
|
81
|
+
|
|
82
|
+
// ── onChange handler ───────────────────────────────────────────────────
|
|
83
|
+
const handleValueChange = React.useCallback(
|
|
84
|
+
(newValue: number) => {
|
|
85
|
+
const clamped = snap(coerce(newValue));
|
|
86
|
+
if (controlledValue === undefined) {
|
|
87
|
+
setInternalValue(clamped);
|
|
88
|
+
}
|
|
89
|
+
onValueChange?.(clamped);
|
|
90
|
+
},
|
|
91
|
+
[coerce, controlledValue, onValueChange, snap],
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const handleValueChangeEnd = React.useCallback(
|
|
95
|
+
(newValue: number) => {
|
|
96
|
+
onValueChangeEnd?.(snap(coerce(newValue)));
|
|
97
|
+
},
|
|
98
|
+
[coerce, onValueChangeEnd, snap],
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// ── Click-to-jump on track ─────────────────────────────────────────────
|
|
102
|
+
const handleTrackPointerDown = React.useCallback(
|
|
103
|
+
(e: React.PointerEvent<HTMLDivElement>) => {
|
|
104
|
+
if (disabled) return;
|
|
105
|
+
const trackEl = trackRef.current;
|
|
106
|
+
if (!trackEl) return;
|
|
107
|
+
|
|
108
|
+
const rect = trackEl.getBoundingClientRect();
|
|
109
|
+
let clickPercent: number;
|
|
110
|
+
|
|
111
|
+
const insetLimit =
|
|
112
|
+
SliderTokens.thumbGap + SliderTokens.thumbWidthDefault / 2;
|
|
113
|
+
const trackInset = Math.min(
|
|
114
|
+
SliderTokens.trackSizes[trackSize] / 2,
|
|
115
|
+
insetLimit,
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
if (isHorizontal) {
|
|
119
|
+
const x = e.clientX - rect.left;
|
|
120
|
+
clickPercent = (x - trackInset) / (rect.width - trackInset * 2);
|
|
121
|
+
} else {
|
|
122
|
+
const y = rect.bottom - e.clientY;
|
|
123
|
+
clickPercent = (y - trackInset) / (rect.height - trackInset * 2);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const clamped = Math.max(0, Math.min(1, clickPercent));
|
|
127
|
+
const rawValue = min + clamped * (max - min);
|
|
128
|
+
const newValue = snap(coerce(rawValue));
|
|
129
|
+
handleValueChange(newValue);
|
|
130
|
+
},
|
|
131
|
+
[
|
|
132
|
+
coerce,
|
|
133
|
+
disabled,
|
|
134
|
+
handleValueChange,
|
|
135
|
+
isHorizontal,
|
|
136
|
+
max,
|
|
137
|
+
min,
|
|
138
|
+
snap,
|
|
139
|
+
trackSize,
|
|
140
|
+
],
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
// ── Generate unique IDs for ARIA ───────────────────────────────────────
|
|
144
|
+
const id = React.useId();
|
|
145
|
+
const thumbId = `slider-thumb-${id}`;
|
|
146
|
+
|
|
147
|
+
// ── Inset icon guard (MD3 spec) ───────────────────────────────────────────
|
|
148
|
+
// Only track sizes >= 40dp support inset icon. Not valid with isCentered.
|
|
149
|
+
const supportsInsetIcon =
|
|
150
|
+
!isCentered && SliderTokens.trackSizes[trackSize] >= 40;
|
|
151
|
+
const hasAnyInsetIcon = !!(insetIcon || insetIconTrailing);
|
|
152
|
+
if (hasAnyInsetIcon && !supportsInsetIcon) {
|
|
153
|
+
console.warn(
|
|
154
|
+
"[Slider] insetIcon is only supported on track sizes >= 40dp (e.g. xl track sizes). " +
|
|
155
|
+
"See MD3 spec: https://m3.material.io/components/sliders/specs",
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<LazyMotion features={domMax} strict>
|
|
161
|
+
<div
|
|
162
|
+
ref={ref}
|
|
163
|
+
className={cn(
|
|
164
|
+
"relative flex items-center",
|
|
165
|
+
isHorizontal ? "w-full flex-row" : "h-full flex-col",
|
|
166
|
+
disabled && "pointer-events-none",
|
|
167
|
+
className,
|
|
168
|
+
)}
|
|
169
|
+
style={isHorizontal ? { minWidth: 0 } : { minHeight: 0 }}
|
|
170
|
+
>
|
|
171
|
+
<SliderTrack
|
|
172
|
+
percent={percent}
|
|
173
|
+
trackSize={trackSize}
|
|
174
|
+
trackShape={trackShape}
|
|
175
|
+
orientation={orientation}
|
|
176
|
+
isCentered={isCentered}
|
|
177
|
+
min={min}
|
|
178
|
+
max={max}
|
|
179
|
+
disabled={disabled}
|
|
180
|
+
variant={variant}
|
|
181
|
+
trackRef={trackRef}
|
|
182
|
+
onTrackPointerDown={handleTrackPointerDown}
|
|
183
|
+
ticks={showTicks ? ticks : []}
|
|
184
|
+
insetIcon={supportsInsetIcon ? insetIcon : undefined}
|
|
185
|
+
insetIconAtMin={supportsInsetIcon ? insetIconAtMin : undefined}
|
|
186
|
+
insetIconTrailing={
|
|
187
|
+
supportsInsetIcon ? insetIconTrailing : undefined
|
|
188
|
+
}
|
|
189
|
+
insetIconAtMax={supportsInsetIcon ? insetIconAtMax : undefined}
|
|
190
|
+
value={safeValue}
|
|
191
|
+
/>
|
|
192
|
+
|
|
193
|
+
<SliderThumb
|
|
194
|
+
value={safeValue}
|
|
195
|
+
percent={percent}
|
|
196
|
+
min={min}
|
|
197
|
+
max={max}
|
|
198
|
+
step={step}
|
|
199
|
+
disabled={disabled}
|
|
200
|
+
orientation={orientation}
|
|
201
|
+
trackSize={trackSize}
|
|
202
|
+
variant={variant}
|
|
203
|
+
showValueIndicator={showValueIndicator}
|
|
204
|
+
trackRef={trackRef}
|
|
205
|
+
onValueChange={handleValueChange}
|
|
206
|
+
onValueChangeEnd={handleValueChangeEnd}
|
|
207
|
+
formatValue={formatValue}
|
|
208
|
+
thumbId={thumbId}
|
|
209
|
+
zIndex={1}
|
|
210
|
+
aria-label={ariaLabel}
|
|
211
|
+
aria-labelledby={ariaLabelledBy}
|
|
212
|
+
/>
|
|
213
|
+
</div>
|
|
214
|
+
</LazyMotion>
|
|
215
|
+
);
|
|
216
|
+
},
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
SliderComponent.displayName = "Slider";
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* MD3 Expressive Slider component.
|
|
223
|
+
*
|
|
224
|
+
* A pill-shaped vertical handle that slides along a rounded track.
|
|
225
|
+
* Supports continuous and discrete (step) modes, horizontal/vertical
|
|
226
|
+
* orientations, and centered active track.
|
|
227
|
+
*
|
|
228
|
+
* Features:
|
|
229
|
+
* - M3 Expressive handle: 4px pill that squeezes to 2px on press
|
|
230
|
+
* - 6px transparent gap between track and thumb
|
|
231
|
+
* - Asymmetric corner radii: outer ends=9999px, inner ends=2px
|
|
232
|
+
* - Tick marks for discrete mode
|
|
233
|
+
* - Optional floating value indicator tooltip
|
|
234
|
+
* - Full keyboard navigation per WAI-ARIA Slider pattern
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* ```tsx
|
|
238
|
+
* // Basic controlled
|
|
239
|
+
* <Slider value={volume} onValueChange={setVolume} aria-label="Volume" />
|
|
240
|
+
*
|
|
241
|
+
* // Discrete with ticks
|
|
242
|
+
* <Slider defaultValue={0} step={10} min={0} max={100} />
|
|
243
|
+
*
|
|
244
|
+
* // Vertical orientation
|
|
245
|
+
* <div className="h-64">
|
|
246
|
+
* <Slider defaultValue={50} orientation="vertical" aria-label="Brightness" />
|
|
247
|
+
* </div>
|
|
248
|
+
*
|
|
249
|
+
* // Centered active track
|
|
250
|
+
* <Slider defaultValue={0} isCentered showValueIndicator />
|
|
251
|
+
*
|
|
252
|
+
* // Large track with value tooltip
|
|
253
|
+
* <Slider defaultValue={50} trackSize="l" showValueIndicator
|
|
254
|
+
* formatValue={(v) => `${v}%`} />
|
|
255
|
+
* ```
|
|
256
|
+
*
|
|
257
|
+
* @see https://m3.material.io/components/sliders/overview
|
|
258
|
+
*/
|
|
259
|
+
export const Slider = React.memo(SliderComponent);
|