@bug-on/md3-react 2.0.2 → 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 +23 -0
- package/dist/index.css.d.ts +2 -0
- package/dist/index.d.mts +6127 -0
- package/dist/index.d.ts +6127 -69
- package/dist/index.js +2536 -665
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2443 -603
- 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 +23 -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/{dist/hooks/index.d.ts → src/hooks/index.ts} +1 -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/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/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/ui/tooltip/index.d.ts → src/ui/tooltip/index.ts} +0 -0
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file switch.tsx
|
|
3
|
+
* MD3 Expressive Switch — ARIA switch pattern with Framer Motion animations.
|
|
4
|
+
* Spec: https://m3.material.io/components/switch/overview
|
|
5
|
+
*
|
|
6
|
+
* Key decisions:
|
|
7
|
+
* - Uses `<button role="switch">` (no <input>) per MD3 accessibility spec
|
|
8
|
+
* - Framer Motion for ALL animations (thumb x, size morph, state layer, icons)
|
|
9
|
+
* - Hover state via useState (required for Framer Motion color animate)
|
|
10
|
+
* - Disabled colors via rgba() literals (color-mix() not animatable by FM)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
AnimatePresence,
|
|
15
|
+
domMax,
|
|
16
|
+
LazyMotion,
|
|
17
|
+
m,
|
|
18
|
+
useReducedMotion,
|
|
19
|
+
} from "motion/react";
|
|
20
|
+
import * as React from "react";
|
|
21
|
+
import { cn } from "../../lib/utils";
|
|
22
|
+
import { Ripple, type RippleOrigin } from "../ripple";
|
|
23
|
+
import { SwitchColors, SwitchTokens } from "./switch.tokens";
|
|
24
|
+
import type { SwitchProps } from "./switch.types";
|
|
25
|
+
|
|
26
|
+
// ─── Animation constants ───────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* FastSpatial spring — equivalent to MotionSchemeKeyTokens.FastSpatial.
|
|
30
|
+
* Used for thumb translation and size morph on checked state change.
|
|
31
|
+
*/
|
|
32
|
+
const FAST_SPATIAL_SPRING = {
|
|
33
|
+
type: "spring",
|
|
34
|
+
stiffness: 500,
|
|
35
|
+
damping: 40,
|
|
36
|
+
} as const;
|
|
37
|
+
|
|
38
|
+
/** Instant transition (SnapSpec equivalent) — used when thumb is pressed. */
|
|
39
|
+
const SNAP_TRANSITION = { duration: 0 } as const;
|
|
40
|
+
|
|
41
|
+
/** Color transition for track/thumb color changes. */
|
|
42
|
+
const COLOR_TRANSITION = { duration: 0.2, ease: "easeInOut" } as const;
|
|
43
|
+
|
|
44
|
+
/** State layer spring — for hover/focus overlay. */
|
|
45
|
+
const STATE_LAYER_SPRING = {
|
|
46
|
+
type: "spring",
|
|
47
|
+
stiffness: 400,
|
|
48
|
+
damping: 30,
|
|
49
|
+
} as const;
|
|
50
|
+
|
|
51
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Computes thumb size in px based on current interaction state.
|
|
55
|
+
* @internal
|
|
56
|
+
*/
|
|
57
|
+
function resolveThumbSize(
|
|
58
|
+
isPressed: boolean,
|
|
59
|
+
checked: boolean,
|
|
60
|
+
hasIcon: boolean,
|
|
61
|
+
): number {
|
|
62
|
+
if (isPressed) return SwitchTokens.pressedHandleSize;
|
|
63
|
+
if (checked || hasIcon) return SwitchTokens.selectedHandleSize;
|
|
64
|
+
return SwitchTokens.unselectedHandleSize;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Computes thumb X offset. Mirrors ThumbNode.measure() from Switch.kt.
|
|
69
|
+
* @internal
|
|
70
|
+
*/
|
|
71
|
+
function resolveThumbX(
|
|
72
|
+
checked: boolean,
|
|
73
|
+
isPressed: boolean,
|
|
74
|
+
thumbSize: number,
|
|
75
|
+
): number {
|
|
76
|
+
const { trackHeight, trackWidth, trackOutlineWidth, selectedHandleSize } =
|
|
77
|
+
SwitchTokens;
|
|
78
|
+
const thumbPaddingStart = (trackHeight - thumbSize) / 2;
|
|
79
|
+
const thumbPadding = (trackHeight - selectedHandleSize) / 2;
|
|
80
|
+
const maxBound = trackWidth - selectedHandleSize - thumbPadding;
|
|
81
|
+
|
|
82
|
+
if (isPressed && checked) return maxBound - trackOutlineWidth;
|
|
83
|
+
if (isPressed) return thumbPaddingStart + trackOutlineWidth;
|
|
84
|
+
if (checked) return maxBound;
|
|
85
|
+
return thumbPaddingStart;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Resolves the thumb background color based on interaction state.
|
|
90
|
+
* @internal
|
|
91
|
+
*/
|
|
92
|
+
function resolveThumbColor(
|
|
93
|
+
checked: boolean,
|
|
94
|
+
disabled: boolean,
|
|
95
|
+
isInteracting: boolean,
|
|
96
|
+
checkedThumbColor?: string,
|
|
97
|
+
uncheckedThumbColor?: string,
|
|
98
|
+
): string {
|
|
99
|
+
if (disabled) {
|
|
100
|
+
return checked
|
|
101
|
+
? SwitchColors.disabledCheckedThumb
|
|
102
|
+
: "rgba(28, 27, 31, 0.38)";
|
|
103
|
+
}
|
|
104
|
+
if (checked) {
|
|
105
|
+
return isInteracting
|
|
106
|
+
? (checkedThumbColor ?? SwitchColors.hoverCheckedThumb)
|
|
107
|
+
: (checkedThumbColor ?? SwitchColors.checkedThumb);
|
|
108
|
+
}
|
|
109
|
+
return isInteracting
|
|
110
|
+
? (uncheckedThumbColor ?? SwitchColors.hoverUncheckedThumb)
|
|
111
|
+
: (uncheckedThumbColor ?? SwitchColors.uncheckedThumb);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Determines if the thumb icon should be visible.
|
|
116
|
+
* @internal
|
|
117
|
+
*/
|
|
118
|
+
function isIconVisible(
|
|
119
|
+
thumbContent: React.ReactNode | undefined,
|
|
120
|
+
icons: boolean,
|
|
121
|
+
showOnlySelectedIcon: boolean,
|
|
122
|
+
checked: boolean,
|
|
123
|
+
): boolean {
|
|
124
|
+
if (thumbContent == null) return false;
|
|
125
|
+
if (icons) return true;
|
|
126
|
+
return showOnlySelectedIcon && checked;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ─── SwitchVisual ──────────────────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
interface ColorOverrides {
|
|
132
|
+
checkedTrackColor?: string;
|
|
133
|
+
uncheckedTrackColor?: string;
|
|
134
|
+
checkedThumbColor?: string;
|
|
135
|
+
uncheckedThumbColor?: string;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
interface SwitchVisualProps extends ColorOverrides {
|
|
139
|
+
checked: boolean;
|
|
140
|
+
disabled: boolean;
|
|
141
|
+
isPressed: boolean;
|
|
142
|
+
isHovered: boolean;
|
|
143
|
+
isFocused: boolean;
|
|
144
|
+
thumbContent: React.ReactNode | undefined;
|
|
145
|
+
icons: boolean;
|
|
146
|
+
showOnlySelectedIcon: boolean;
|
|
147
|
+
prefersReduced: boolean;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Animated switch visual (track + state layer + thumb + icon). @internal */
|
|
151
|
+
const SwitchVisual = React.memo(function SwitchVisual({
|
|
152
|
+
checked,
|
|
153
|
+
disabled,
|
|
154
|
+
isPressed,
|
|
155
|
+
isHovered,
|
|
156
|
+
isFocused,
|
|
157
|
+
thumbContent,
|
|
158
|
+
icons,
|
|
159
|
+
showOnlySelectedIcon,
|
|
160
|
+
prefersReduced,
|
|
161
|
+
checkedTrackColor,
|
|
162
|
+
uncheckedTrackColor,
|
|
163
|
+
checkedThumbColor,
|
|
164
|
+
uncheckedThumbColor,
|
|
165
|
+
}: SwitchVisualProps) {
|
|
166
|
+
const hasIcon = icons && thumbContent != null;
|
|
167
|
+
const thumbSize = resolveThumbSize(isPressed, checked, hasIcon);
|
|
168
|
+
const thumbX = resolveThumbX(checked, isPressed, thumbSize);
|
|
169
|
+
|
|
170
|
+
// ── Track colors ──────────────────────────────────────────────────────────
|
|
171
|
+
const trackBg = checked
|
|
172
|
+
? (checkedTrackColor ?? SwitchColors.checkedTrack)
|
|
173
|
+
: (uncheckedTrackColor ?? SwitchColors.uncheckedTrack);
|
|
174
|
+
|
|
175
|
+
const trackBorderColor = checked
|
|
176
|
+
? "rgba(0, 0, 0, 0)"
|
|
177
|
+
: SwitchColors.uncheckedTrackOutline;
|
|
178
|
+
|
|
179
|
+
const trackBorderWidth = checked ? 0 : SwitchTokens.trackOutlineWidth;
|
|
180
|
+
|
|
181
|
+
// Disabled track: use rgba literals (color-mix not animatable)
|
|
182
|
+
// Light: on-surface = #1c1b1f → rgba(28,27,31,0.12)
|
|
183
|
+
// We use CSS custom property approach with opacity wrapper instead
|
|
184
|
+
const trackOpacity = disabled ? SwitchTokens.disabledTrackOpacity : 1;
|
|
185
|
+
|
|
186
|
+
// ── Thumb colors ──────────────────────────────────────────────────────────
|
|
187
|
+
const isInteracting = isHovered || isFocused || isPressed;
|
|
188
|
+
const thumbBg = resolveThumbColor(
|
|
189
|
+
checked,
|
|
190
|
+
disabled,
|
|
191
|
+
isInteracting,
|
|
192
|
+
checkedThumbColor,
|
|
193
|
+
uncheckedThumbColor,
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
// ── Icon color ────────────────────────────────────────────────────────────
|
|
197
|
+
const iconColor = disabled
|
|
198
|
+
? checked
|
|
199
|
+
? "rgba(28, 27, 31, 0.38)"
|
|
200
|
+
: "rgba(230, 225, 229, 0.38)"
|
|
201
|
+
: checked
|
|
202
|
+
? SwitchColors.checkedIcon
|
|
203
|
+
: SwitchColors.uncheckedIcon;
|
|
204
|
+
|
|
205
|
+
// ── State layer ───────────────────────────────────────────────────────────
|
|
206
|
+
const stateLayerColor = checked
|
|
207
|
+
? SwitchColors.checkedStateLayer
|
|
208
|
+
: SwitchColors.uncheckedStateLayer;
|
|
209
|
+
const stateLayerOpacity =
|
|
210
|
+
isPressed || isFocused ? 0.12 : isHovered ? 0.08 : 0;
|
|
211
|
+
const stateLayerX = thumbX + thumbSize / 2 - SwitchTokens.stateLayerSize / 2;
|
|
212
|
+
|
|
213
|
+
// ── Icon visibility ───────────────────────────────────────────────────────
|
|
214
|
+
const showIcon = isIconVisible(
|
|
215
|
+
thumbContent,
|
|
216
|
+
icons,
|
|
217
|
+
showOnlySelectedIcon,
|
|
218
|
+
checked,
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
// ── Motion: no animation when reduced ─────────────────────────────────────
|
|
222
|
+
const colorTransition = prefersReduced ? { duration: 0 } : COLOR_TRANSITION;
|
|
223
|
+
|
|
224
|
+
const stateLayerTransition = prefersReduced
|
|
225
|
+
? { duration: 0 }
|
|
226
|
+
: STATE_LAYER_SPRING;
|
|
227
|
+
|
|
228
|
+
return (
|
|
229
|
+
<div
|
|
230
|
+
className="relative"
|
|
231
|
+
style={{
|
|
232
|
+
width: SwitchTokens.trackWidth,
|
|
233
|
+
height: SwitchTokens.trackHeight,
|
|
234
|
+
}}
|
|
235
|
+
aria-hidden="true"
|
|
236
|
+
>
|
|
237
|
+
{/* Track */}
|
|
238
|
+
<m.div
|
|
239
|
+
className="absolute inset-0 rounded-full"
|
|
240
|
+
style={{ borderStyle: "solid", opacity: trackOpacity }}
|
|
241
|
+
animate={{
|
|
242
|
+
backgroundColor: trackBg,
|
|
243
|
+
borderColor: trackBorderColor,
|
|
244
|
+
borderWidth: trackBorderWidth,
|
|
245
|
+
}}
|
|
246
|
+
transition={colorTransition}
|
|
247
|
+
/>
|
|
248
|
+
|
|
249
|
+
{/* State layer — 40dp circle centered on thumb */}
|
|
250
|
+
<m.div
|
|
251
|
+
className="absolute rounded-full pointer-events-none"
|
|
252
|
+
style={{
|
|
253
|
+
width: SwitchTokens.stateLayerSize,
|
|
254
|
+
height: SwitchTokens.stateLayerSize,
|
|
255
|
+
top: (SwitchTokens.trackHeight - SwitchTokens.stateLayerSize) / 2,
|
|
256
|
+
backgroundColor: stateLayerColor,
|
|
257
|
+
}}
|
|
258
|
+
animate={{ x: stateLayerX, opacity: stateLayerOpacity }}
|
|
259
|
+
transition={stateLayerTransition}
|
|
260
|
+
/>
|
|
261
|
+
|
|
262
|
+
{/* Thumb */}
|
|
263
|
+
<m.div
|
|
264
|
+
className="absolute rounded-full flex items-center justify-center overflow-hidden"
|
|
265
|
+
style={{ top: "50%", left: 0, y: "-50%" }}
|
|
266
|
+
animate={{
|
|
267
|
+
x: thumbX,
|
|
268
|
+
width: thumbSize,
|
|
269
|
+
height: thumbSize,
|
|
270
|
+
backgroundColor: thumbBg,
|
|
271
|
+
}}
|
|
272
|
+
transition={
|
|
273
|
+
prefersReduced
|
|
274
|
+
? { duration: 0 }
|
|
275
|
+
: isPressed
|
|
276
|
+
? SNAP_TRANSITION
|
|
277
|
+
: {
|
|
278
|
+
x: FAST_SPATIAL_SPRING,
|
|
279
|
+
width: FAST_SPATIAL_SPRING,
|
|
280
|
+
height: FAST_SPATIAL_SPRING,
|
|
281
|
+
backgroundColor: colorTransition,
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
>
|
|
285
|
+
{/* Icon cross-fade */}
|
|
286
|
+
<AnimatePresence mode="wait">
|
|
287
|
+
{showIcon && (
|
|
288
|
+
<m.span
|
|
289
|
+
key={checked ? "icon-on" : "icon-off"}
|
|
290
|
+
className="flex items-center justify-center"
|
|
291
|
+
style={{
|
|
292
|
+
width: SwitchTokens.iconSize,
|
|
293
|
+
height: SwitchTokens.iconSize,
|
|
294
|
+
color: iconColor,
|
|
295
|
+
fontSize: SwitchTokens.iconSize,
|
|
296
|
+
}}
|
|
297
|
+
initial={prefersReduced ? false : { opacity: 0, scale: 0.5 }}
|
|
298
|
+
animate={{ opacity: 1, scale: 1 }}
|
|
299
|
+
exit={prefersReduced ? {} : { opacity: 0, scale: 0.5 }}
|
|
300
|
+
transition={prefersReduced ? { duration: 0 } : { duration: 0.15 }}
|
|
301
|
+
>
|
|
302
|
+
{thumbContent}
|
|
303
|
+
</m.span>
|
|
304
|
+
)}
|
|
305
|
+
</AnimatePresence>
|
|
306
|
+
</m.div>
|
|
307
|
+
</div>
|
|
308
|
+
);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// ─── Switch ────────────────────────────────────────────────────────────────────
|
|
312
|
+
|
|
313
|
+
const SwitchComponent = React.forwardRef<HTMLButtonElement, SwitchProps>(
|
|
314
|
+
(
|
|
315
|
+
{
|
|
316
|
+
checked,
|
|
317
|
+
onCheckedChange,
|
|
318
|
+
disabled = false,
|
|
319
|
+
thumbContent,
|
|
320
|
+
icons = false,
|
|
321
|
+
showOnlySelectedIcon = false,
|
|
322
|
+
label,
|
|
323
|
+
ariaLabel,
|
|
324
|
+
className,
|
|
325
|
+
checkedTrackColor,
|
|
326
|
+
uncheckedTrackColor,
|
|
327
|
+
checkedThumbColor,
|
|
328
|
+
uncheckedThumbColor,
|
|
329
|
+
},
|
|
330
|
+
ref,
|
|
331
|
+
) => {
|
|
332
|
+
const prefersReduced = useReducedMotion() ?? false;
|
|
333
|
+
|
|
334
|
+
const [isPressed, setIsPressed] = React.useState(false);
|
|
335
|
+
const [isHovered, setIsHovered] = React.useState(false);
|
|
336
|
+
const [isFocused, setIsFocused] = React.useState(false);
|
|
337
|
+
const [ripples, setRipples] = React.useState<RippleOrigin[]>([]);
|
|
338
|
+
|
|
339
|
+
const generatedId = React.useId();
|
|
340
|
+
const switchId = label ? `switch-${generatedId}` : undefined;
|
|
341
|
+
|
|
342
|
+
// ── Event handlers ────────────────────────────────────────────────────
|
|
343
|
+
const handleClick = React.useCallback(() => {
|
|
344
|
+
if (!disabled) onCheckedChange(!checked);
|
|
345
|
+
}, [disabled, checked, onCheckedChange]);
|
|
346
|
+
|
|
347
|
+
const handleKeyDown = React.useCallback(
|
|
348
|
+
(e: React.KeyboardEvent<HTMLButtonElement>) => {
|
|
349
|
+
if (disabled) return;
|
|
350
|
+
if (e.key === " " || e.key === "Enter") {
|
|
351
|
+
e.preventDefault();
|
|
352
|
+
onCheckedChange(!checked);
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
[disabled, checked, onCheckedChange],
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
const handlePointerDown = React.useCallback(
|
|
359
|
+
(e: React.PointerEvent<HTMLButtonElement>) => {
|
|
360
|
+
if (disabled) return;
|
|
361
|
+
setIsPressed(true);
|
|
362
|
+
// Ripple
|
|
363
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
364
|
+
const x = e.clientX - rect.left;
|
|
365
|
+
const y = e.clientY - rect.top;
|
|
366
|
+
const rippleSize = Math.hypot(rect.width, rect.height) * 2;
|
|
367
|
+
setRipples((prev) => [
|
|
368
|
+
...prev,
|
|
369
|
+
{ id: Date.now(), x, y, size: rippleSize },
|
|
370
|
+
]);
|
|
371
|
+
},
|
|
372
|
+
[disabled],
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
const handlePointerUp = React.useCallback(() => {
|
|
376
|
+
setIsPressed(false);
|
|
377
|
+
}, []);
|
|
378
|
+
|
|
379
|
+
const handlePointerEnter = React.useCallback(() => {
|
|
380
|
+
if (!disabled) setIsHovered(true);
|
|
381
|
+
}, [disabled]);
|
|
382
|
+
|
|
383
|
+
const handlePointerLeave = React.useCallback(() => {
|
|
384
|
+
setIsHovered(false);
|
|
385
|
+
setIsPressed(false);
|
|
386
|
+
}, []);
|
|
387
|
+
|
|
388
|
+
const handleFocus = React.useCallback(() => setIsFocused(true), []);
|
|
389
|
+
const handleBlur = React.useCallback(() => setIsFocused(false), []);
|
|
390
|
+
|
|
391
|
+
const removeRipple = React.useCallback(
|
|
392
|
+
(id: number) => setRipples((prev) => prev.filter((r) => r.id !== id)),
|
|
393
|
+
[],
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
// ── Shared accessible label ───────────────────────────────────────────
|
|
397
|
+
// When visible label wraps, aria-label is redundant (label text is
|
|
398
|
+
// announced via htmlFor linkage). Apply aria-label only when no label.
|
|
399
|
+
const buttonAriaLabel = label ? undefined : ariaLabel;
|
|
400
|
+
|
|
401
|
+
// ── Switch button ─────────────────────────────────────────────────────
|
|
402
|
+
const switchButton = (
|
|
403
|
+
<button
|
|
404
|
+
ref={ref}
|
|
405
|
+
id={switchId}
|
|
406
|
+
type="button"
|
|
407
|
+
role="switch"
|
|
408
|
+
aria-checked={checked}
|
|
409
|
+
aria-disabled={disabled || undefined}
|
|
410
|
+
aria-label={buttonAriaLabel}
|
|
411
|
+
tabIndex={disabled ? -1 : 0}
|
|
412
|
+
disabled={disabled}
|
|
413
|
+
onClick={handleClick}
|
|
414
|
+
onKeyDown={handleKeyDown}
|
|
415
|
+
onPointerDown={handlePointerDown}
|
|
416
|
+
onPointerUp={handlePointerUp}
|
|
417
|
+
onPointerLeave={handlePointerLeave}
|
|
418
|
+
onPointerEnter={handlePointerEnter}
|
|
419
|
+
onFocus={handleFocus}
|
|
420
|
+
onBlur={handleBlur}
|
|
421
|
+
className={cn(
|
|
422
|
+
"relative inline-flex items-center justify-center cursor-pointer select-none",
|
|
423
|
+
// Touch target: 48×48 minimum (pad around 32px track)
|
|
424
|
+
"min-w-12 min-h-12",
|
|
425
|
+
// Focus ring — MD3 FocusIndicatorColor = secondary
|
|
426
|
+
"focus-visible:outline-2 focus-visible:outline-offset-2 rounded-full",
|
|
427
|
+
"focus-visible:outline-(--md-sys-color-secondary)",
|
|
428
|
+
// Disabled
|
|
429
|
+
disabled && "pointer-events-none cursor-not-allowed",
|
|
430
|
+
!label && className,
|
|
431
|
+
)}
|
|
432
|
+
>
|
|
433
|
+
{/* Overflow clip wrapper for ripple */}
|
|
434
|
+
<div className="relative overflow-hidden rounded-full">
|
|
435
|
+
<SwitchVisual
|
|
436
|
+
checked={checked}
|
|
437
|
+
disabled={disabled}
|
|
438
|
+
isPressed={isPressed}
|
|
439
|
+
isHovered={isHovered}
|
|
440
|
+
isFocused={isFocused}
|
|
441
|
+
thumbContent={thumbContent}
|
|
442
|
+
icons={icons}
|
|
443
|
+
showOnlySelectedIcon={showOnlySelectedIcon}
|
|
444
|
+
prefersReduced={prefersReduced}
|
|
445
|
+
checkedTrackColor={checkedTrackColor}
|
|
446
|
+
uncheckedTrackColor={uncheckedTrackColor}
|
|
447
|
+
checkedThumbColor={checkedThumbColor}
|
|
448
|
+
uncheckedThumbColor={uncheckedThumbColor}
|
|
449
|
+
/>
|
|
450
|
+
<Ripple
|
|
451
|
+
ripples={ripples}
|
|
452
|
+
onRippleDone={removeRipple}
|
|
453
|
+
disabled={disabled}
|
|
454
|
+
/>
|
|
455
|
+
</div>
|
|
456
|
+
</button>
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
// ── With label ────────────────────────────────────────────────────────
|
|
460
|
+
const content = label ? (
|
|
461
|
+
<label
|
|
462
|
+
htmlFor={switchId}
|
|
463
|
+
className={cn(
|
|
464
|
+
"inline-flex items-center gap-3 cursor-pointer select-none",
|
|
465
|
+
disabled && "cursor-not-allowed pointer-events-none opacity-[0.38]",
|
|
466
|
+
className,
|
|
467
|
+
)}
|
|
468
|
+
>
|
|
469
|
+
{switchButton}
|
|
470
|
+
<span className="text-sm leading-none text-(--md-sys-color-on-surface)">
|
|
471
|
+
{label}
|
|
472
|
+
</span>
|
|
473
|
+
</label>
|
|
474
|
+
) : (
|
|
475
|
+
switchButton
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
return (
|
|
479
|
+
<LazyMotion features={domMax} strict>
|
|
480
|
+
{content}
|
|
481
|
+
</LazyMotion>
|
|
482
|
+
);
|
|
483
|
+
},
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
SwitchComponent.displayName = "Switch";
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* MD3 Expressive Switch component.
|
|
490
|
+
*
|
|
491
|
+
* Toggles a single item on or off. Implements the ARIA switch pattern
|
|
492
|
+
* (`role="switch"`) without `<input>`. Fully animated per MD3 spec:
|
|
493
|
+
* thumb translation, size morph (16→24→28px), state layer, and icon cross-fade.
|
|
494
|
+
*
|
|
495
|
+
* @example
|
|
496
|
+
* ```tsx
|
|
497
|
+
* <Switch checked={isOn} onCheckedChange={setIsOn} label="Wi-Fi" />
|
|
498
|
+
* <Switch checked={isOn} onCheckedChange={setIsOn} icons thumbContent={<CheckIcon />} />
|
|
499
|
+
* <Switch checked={isOn} onCheckedChange={setIsOn} disabled />
|
|
500
|
+
* ```
|
|
501
|
+
*
|
|
502
|
+
* @see https://m3.material.io/components/switch/overview
|
|
503
|
+
*/
|
|
504
|
+
export const Switch = React.memo(SwitchComponent);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file switch.types.ts
|
|
3
|
+
* MD3 Expressive Switch — TypeScript prop definitions.
|
|
4
|
+
* Spec: https://m3.material.io/components/switch/overview
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type * as React from "react";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Props for the `Switch` component.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* <Switch checked={isOn} onCheckedChange={setIsOn} label="Wi-Fi" />
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export interface SwitchProps {
|
|
18
|
+
/** Controlled checked (on) state. */
|
|
19
|
+
checked: boolean;
|
|
20
|
+
/** Called when the switch is toggled. Not called when disabled. */
|
|
21
|
+
onCheckedChange: (checked: boolean) => void;
|
|
22
|
+
/** Disables interaction and applies disabled visual state. @default false */
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Optional icon content rendered inside the thumb.
|
|
26
|
+
* Expected to measure 16dp (SwitchTokens.iconSize).
|
|
27
|
+
*/
|
|
28
|
+
thumbContent?: React.ReactNode;
|
|
29
|
+
/**
|
|
30
|
+
* When true, shows thumb icons in both selected and unselected states.
|
|
31
|
+
* Requires `thumbContent` to be provided.
|
|
32
|
+
* @default false
|
|
33
|
+
*/
|
|
34
|
+
icons?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* When true, shows the icon only in the selected/checked state.
|
|
37
|
+
* Requires `thumbContent` to be provided.
|
|
38
|
+
* @default false
|
|
39
|
+
*/
|
|
40
|
+
showOnlySelectedIcon?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Visible label text rendered adjacent to the switch.
|
|
43
|
+
* When provided, wraps the switch in a `<label>` for accessibility.
|
|
44
|
+
*/
|
|
45
|
+
label?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Overrides the accessible name. Used when no visible `label` is provided.
|
|
48
|
+
* Maps to the `aria-label` attribute.
|
|
49
|
+
*/
|
|
50
|
+
ariaLabel?: string;
|
|
51
|
+
/** Additional CSS class names applied to the outermost wrapper. */
|
|
52
|
+
className?: string;
|
|
53
|
+
// ── Advanced color overrides ──────────────────────────────────────────────
|
|
54
|
+
/** Override track background color when checked. Defaults to MD3 primary. */
|
|
55
|
+
checkedTrackColor?: string;
|
|
56
|
+
/** Override track background color when unchecked. Defaults to MD3 surface-container-highest. */
|
|
57
|
+
uncheckedTrackColor?: string;
|
|
58
|
+
/** Override thumb color when checked. Defaults to MD3 on-primary. */
|
|
59
|
+
checkedThumbColor?: string;
|
|
60
|
+
/** Override thumb color when unchecked. Defaults to MD3 outline. */
|
|
61
|
+
uncheckedThumbColor?: string;
|
|
62
|
+
}
|
|
@@ -2,9 +2,16 @@
|
|
|
2
2
|
* @file tabs/index.ts
|
|
3
3
|
* Public exports for the MD3 Expressive Tabs component system.
|
|
4
4
|
*/
|
|
5
|
+
|
|
5
6
|
export { Tab } from "./tab";
|
|
6
7
|
export { Tabs } from "./tabs";
|
|
7
8
|
export { TabsColors, TabsTokens } from "./tabs.tokens";
|
|
8
|
-
export type {
|
|
9
|
+
export type {
|
|
10
|
+
TabProps,
|
|
11
|
+
TabsContentProps,
|
|
12
|
+
TabsListProps,
|
|
13
|
+
TabsProps,
|
|
14
|
+
TabsVariant,
|
|
15
|
+
} from "./tabs.types";
|
|
9
16
|
export { TabsContent } from "./tabs-content";
|
|
10
17
|
export { TabsList } from "./tabs-list";
|