@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,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file switch.stories.tsx
|
|
3
|
+
* MD3 Expressive Switch — all usage patterns and states.
|
|
4
|
+
*
|
|
5
|
+
* Can be used as:
|
|
6
|
+
* 1. Storybook stories (if Storybook is configured)
|
|
7
|
+
* 2. Standalone demo component for the docs app
|
|
8
|
+
*
|
|
9
|
+
* Covers all 7 patterns from the MD3 Switch specification:
|
|
10
|
+
* 1. Basic toggle
|
|
11
|
+
* 2. With label
|
|
12
|
+
* 3. With icons (both states)
|
|
13
|
+
* 4. With only selected icon
|
|
14
|
+
* 5. Disabled (checked)
|
|
15
|
+
* 6. Disabled (unchecked)
|
|
16
|
+
* 7. All states grid
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
"use client";
|
|
20
|
+
|
|
21
|
+
import * as React from "react";
|
|
22
|
+
import { Switch } from "./switch";
|
|
23
|
+
|
|
24
|
+
// ─── Icon helper ──────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
/** Simple check icon using SVG (no external icon dependency). @internal */
|
|
27
|
+
function CheckIcon() {
|
|
28
|
+
return (
|
|
29
|
+
<svg
|
|
30
|
+
viewBox="0 0 16 16"
|
|
31
|
+
width={16}
|
|
32
|
+
height={16}
|
|
33
|
+
fill="none"
|
|
34
|
+
stroke="currentColor"
|
|
35
|
+
strokeWidth={2.5}
|
|
36
|
+
strokeLinecap="round"
|
|
37
|
+
strokeLinejoin="round"
|
|
38
|
+
aria-hidden="true"
|
|
39
|
+
>
|
|
40
|
+
<polyline points="3,8 6.5,11.5 13,5" />
|
|
41
|
+
</svg>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Simple close/X icon for unselected state. @internal */
|
|
46
|
+
function CloseIcon() {
|
|
47
|
+
return (
|
|
48
|
+
<svg
|
|
49
|
+
viewBox="0 0 16 16"
|
|
50
|
+
width={16}
|
|
51
|
+
height={16}
|
|
52
|
+
fill="none"
|
|
53
|
+
stroke="currentColor"
|
|
54
|
+
strokeWidth={2.5}
|
|
55
|
+
strokeLinecap="round"
|
|
56
|
+
aria-hidden="true"
|
|
57
|
+
>
|
|
58
|
+
<line x1="4" y1="4" x2="12" y2="12" />
|
|
59
|
+
<line x1="12" y1="4" x2="4" y2="12" />
|
|
60
|
+
</svg>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ─── Story 1: Basic ───────────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
/** Basic stateful switch — no label, no icons. */
|
|
67
|
+
export function Basic() {
|
|
68
|
+
const [checked, setChecked] = React.useState(false);
|
|
69
|
+
return (
|
|
70
|
+
<div className="flex flex-col gap-4">
|
|
71
|
+
<p className="text-sm text-neutral-500">
|
|
72
|
+
State: <strong>{checked ? "On" : "Off"}</strong>
|
|
73
|
+
</p>
|
|
74
|
+
<Switch
|
|
75
|
+
checked={checked}
|
|
76
|
+
onCheckedChange={setChecked}
|
|
77
|
+
ariaLabel="Basic switch"
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ─── Story 2: With Label ──────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
/** Switch with a visible text label using htmlFor linkage. */
|
|
86
|
+
export function WithLabel() {
|
|
87
|
+
const [checked, setChecked] = React.useState(true);
|
|
88
|
+
return (
|
|
89
|
+
<Switch checked={checked} onCheckedChange={setChecked} label="Wi-Fi" />
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ─── Story 3: With Icons (both states) ───────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Switch showing icons in both checked and unchecked states.
|
|
97
|
+
* Uses check icon when on, close icon when off.
|
|
98
|
+
*/
|
|
99
|
+
export function WithIcons() {
|
|
100
|
+
const [checked, setChecked] = React.useState(false);
|
|
101
|
+
return (
|
|
102
|
+
<div className="flex flex-col gap-4">
|
|
103
|
+
<Switch
|
|
104
|
+
checked={checked}
|
|
105
|
+
onCheckedChange={setChecked}
|
|
106
|
+
icons
|
|
107
|
+
thumbContent={checked ? <CheckIcon /> : <CloseIcon />}
|
|
108
|
+
label="Notifications"
|
|
109
|
+
/>
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ─── Story 4: Only Selected Icon ─────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Switch showing the icon only in the checked (selected) state.
|
|
118
|
+
* Unselected state has no icon.
|
|
119
|
+
*/
|
|
120
|
+
export function WithOnlySelectedIcon() {
|
|
121
|
+
const [checked, setChecked] = React.useState(false);
|
|
122
|
+
return (
|
|
123
|
+
<Switch
|
|
124
|
+
checked={checked}
|
|
125
|
+
onCheckedChange={setChecked}
|
|
126
|
+
showOnlySelectedIcon
|
|
127
|
+
thumbContent={<CheckIcon />}
|
|
128
|
+
label="Dark mode"
|
|
129
|
+
/>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ─── Story 5: Disabled (checked) ─────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
/** Disabled switch in the checked (on) state. */
|
|
136
|
+
export function DisabledChecked() {
|
|
137
|
+
return (
|
|
138
|
+
<Switch
|
|
139
|
+
checked
|
|
140
|
+
onCheckedChange={() => {}}
|
|
141
|
+
disabled
|
|
142
|
+
label="Always on (disabled)"
|
|
143
|
+
/>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ─── Story 6: Disabled (unchecked) ───────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
/** Disabled switch in the unchecked (off) state. */
|
|
150
|
+
export function DisabledUnchecked() {
|
|
151
|
+
return (
|
|
152
|
+
<Switch
|
|
153
|
+
checked={false}
|
|
154
|
+
onCheckedChange={() => {}}
|
|
155
|
+
disabled
|
|
156
|
+
label="Unavailable feature (disabled)"
|
|
157
|
+
/>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ─── Story 7: All States Grid ─────────────────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Grid displaying all Switch state combinations:
|
|
165
|
+
* - Enabled: checked / unchecked
|
|
166
|
+
* - Disabled: checked / unchecked
|
|
167
|
+
* - With icons: checked / unchecked
|
|
168
|
+
* - With only selected icon: checked / unchecked
|
|
169
|
+
*/
|
|
170
|
+
export function AllStatesGrid() {
|
|
171
|
+
const [checkedMap, setCheckedMap] = React.useState<Record<string, boolean>>({
|
|
172
|
+
enabled_on: true,
|
|
173
|
+
enabled_off: false,
|
|
174
|
+
icons_on: true,
|
|
175
|
+
icons_off: false,
|
|
176
|
+
selected_icon_on: true,
|
|
177
|
+
selected_icon_off: false,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const toggle = (key: string) =>
|
|
181
|
+
setCheckedMap((prev) => ({ ...prev, [key]: !prev[key] }));
|
|
182
|
+
|
|
183
|
+
const Row = ({
|
|
184
|
+
title,
|
|
185
|
+
children,
|
|
186
|
+
}: {
|
|
187
|
+
title: string;
|
|
188
|
+
children: React.ReactNode;
|
|
189
|
+
}) => (
|
|
190
|
+
<div className="flex items-center gap-6">
|
|
191
|
+
<span className="w-48 text-sm text-neutral-500 shrink-0">{title}</span>
|
|
192
|
+
{children}
|
|
193
|
+
</div>
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<div className="flex flex-col gap-6 p-4">
|
|
198
|
+
<Row title="Enabled (unchecked)">
|
|
199
|
+
<Switch
|
|
200
|
+
checked={checkedMap.enabled_off}
|
|
201
|
+
onCheckedChange={() => toggle("enabled_off")}
|
|
202
|
+
ariaLabel="Enabled unchecked"
|
|
203
|
+
/>
|
|
204
|
+
</Row>
|
|
205
|
+
<Row title="Enabled (checked)">
|
|
206
|
+
<Switch
|
|
207
|
+
checked={checkedMap.enabled_on}
|
|
208
|
+
onCheckedChange={() => toggle("enabled_on")}
|
|
209
|
+
ariaLabel="Enabled checked"
|
|
210
|
+
/>
|
|
211
|
+
</Row>
|
|
212
|
+
<Row title="Disabled (unchecked)">
|
|
213
|
+
<Switch
|
|
214
|
+
checked={false}
|
|
215
|
+
onCheckedChange={() => {}}
|
|
216
|
+
disabled
|
|
217
|
+
ariaLabel="Disabled unchecked"
|
|
218
|
+
/>
|
|
219
|
+
</Row>
|
|
220
|
+
<Row title="Disabled (checked)">
|
|
221
|
+
<Switch
|
|
222
|
+
checked
|
|
223
|
+
onCheckedChange={() => {}}
|
|
224
|
+
disabled
|
|
225
|
+
ariaLabel="Disabled checked"
|
|
226
|
+
/>
|
|
227
|
+
</Row>
|
|
228
|
+
<Row title="With icons (off)">
|
|
229
|
+
<Switch
|
|
230
|
+
checked={checkedMap.icons_off}
|
|
231
|
+
onCheckedChange={() => toggle("icons_off")}
|
|
232
|
+
icons
|
|
233
|
+
thumbContent={checkedMap.icons_off ? <CheckIcon /> : <CloseIcon />}
|
|
234
|
+
ariaLabel="With icons unchecked"
|
|
235
|
+
/>
|
|
236
|
+
</Row>
|
|
237
|
+
<Row title="With icons (on)">
|
|
238
|
+
<Switch
|
|
239
|
+
checked={checkedMap.icons_on}
|
|
240
|
+
onCheckedChange={() => toggle("icons_on")}
|
|
241
|
+
icons
|
|
242
|
+
thumbContent={checkedMap.icons_on ? <CheckIcon /> : <CloseIcon />}
|
|
243
|
+
ariaLabel="With icons checked"
|
|
244
|
+
/>
|
|
245
|
+
</Row>
|
|
246
|
+
<Row title="Selected icon only (off)">
|
|
247
|
+
<Switch
|
|
248
|
+
checked={checkedMap.selected_icon_off}
|
|
249
|
+
onCheckedChange={() => toggle("selected_icon_off")}
|
|
250
|
+
showOnlySelectedIcon
|
|
251
|
+
thumbContent={<CheckIcon />}
|
|
252
|
+
ariaLabel="Selected icon only unchecked"
|
|
253
|
+
/>
|
|
254
|
+
</Row>
|
|
255
|
+
<Row title="Selected icon only (on)">
|
|
256
|
+
<Switch
|
|
257
|
+
checked={checkedMap.selected_icon_on}
|
|
258
|
+
onCheckedChange={() => toggle("selected_icon_on")}
|
|
259
|
+
showOnlySelectedIcon
|
|
260
|
+
thumbContent={<CheckIcon />}
|
|
261
|
+
ariaLabel="Selected icon only checked"
|
|
262
|
+
/>
|
|
263
|
+
</Row>
|
|
264
|
+
</div>
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ─── Master Demo ──────────────────────────────────────────────────────────────
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* All switch stories in one scrollable demo page.
|
|
272
|
+
* Used by the docs app to showcase all variants.
|
|
273
|
+
*/
|
|
274
|
+
export function SwitchDemo() {
|
|
275
|
+
return (
|
|
276
|
+
<div className="flex flex-col gap-10 p-8 max-w-2xl">
|
|
277
|
+
<section>
|
|
278
|
+
<h2 className="text-xl font-semibold mb-4">1. Basic</h2>
|
|
279
|
+
<Basic />
|
|
280
|
+
</section>
|
|
281
|
+
<section>
|
|
282
|
+
<h2 className="text-xl font-semibold mb-4">2. With Label</h2>
|
|
283
|
+
<WithLabel />
|
|
284
|
+
</section>
|
|
285
|
+
<section>
|
|
286
|
+
<h2 className="text-xl font-semibold mb-4">
|
|
287
|
+
3. With Icons (Both States)
|
|
288
|
+
</h2>
|
|
289
|
+
<WithIcons />
|
|
290
|
+
</section>
|
|
291
|
+
<section>
|
|
292
|
+
<h2 className="text-xl font-semibold mb-4">4. Only Selected Icon</h2>
|
|
293
|
+
<WithOnlySelectedIcon />
|
|
294
|
+
</section>
|
|
295
|
+
<section>
|
|
296
|
+
<h2 className="text-xl font-semibold mb-4">5. Disabled (Checked)</h2>
|
|
297
|
+
<DisabledChecked />
|
|
298
|
+
</section>
|
|
299
|
+
<section>
|
|
300
|
+
<h2 className="text-xl font-semibold mb-4">6. Disabled (Unchecked)</h2>
|
|
301
|
+
<DisabledUnchecked />
|
|
302
|
+
</section>
|
|
303
|
+
<section>
|
|
304
|
+
<h2 className="text-xl font-semibold mb-4">7. All States Grid</h2>
|
|
305
|
+
<AllStatesGrid />
|
|
306
|
+
</section>
|
|
307
|
+
</div>
|
|
308
|
+
);
|
|
309
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file switch.test.tsx
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive test suite for the MD3 Expressive Switch component.
|
|
5
|
+
* Tests cover: rendering, controlled state, disabled, accessibility,
|
|
6
|
+
* and keyboard interaction.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
|
10
|
+
import * as React from "react";
|
|
11
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
12
|
+
import { Switch } from "./switch";
|
|
13
|
+
|
|
14
|
+
afterEach(cleanup);
|
|
15
|
+
|
|
16
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
17
|
+
// Rendering
|
|
18
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
describe("Switch — Rendering", () => {
|
|
21
|
+
it("renders unchecked by default", () => {
|
|
22
|
+
const onCheckedChange = vi.fn();
|
|
23
|
+
render(
|
|
24
|
+
<Switch
|
|
25
|
+
checked={false}
|
|
26
|
+
onCheckedChange={onCheckedChange}
|
|
27
|
+
ariaLabel="Test switch"
|
|
28
|
+
/>,
|
|
29
|
+
);
|
|
30
|
+
const toggle = screen.getByRole("switch", { name: "Test switch" });
|
|
31
|
+
expect(toggle).toHaveAttribute("aria-checked", "false");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("renders checked when checked=true", () => {
|
|
35
|
+
const onCheckedChange = vi.fn();
|
|
36
|
+
render(
|
|
37
|
+
<Switch
|
|
38
|
+
checked={true}
|
|
39
|
+
onCheckedChange={onCheckedChange}
|
|
40
|
+
ariaLabel="Test switch"
|
|
41
|
+
/>,
|
|
42
|
+
);
|
|
43
|
+
const toggle = screen.getByRole("switch", { name: "Test switch" });
|
|
44
|
+
expect(toggle).toHaveAttribute("aria-checked", "true");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("renders with label text", () => {
|
|
48
|
+
const onCheckedChange = vi.fn();
|
|
49
|
+
render(
|
|
50
|
+
<Switch
|
|
51
|
+
checked={false}
|
|
52
|
+
onCheckedChange={onCheckedChange}
|
|
53
|
+
label="Notifications"
|
|
54
|
+
/>,
|
|
55
|
+
);
|
|
56
|
+
expect(screen.getByText("Notifications")).toBeInTheDocument();
|
|
57
|
+
// Label should be associated with the switch
|
|
58
|
+
const toggle = screen.getByRole("switch", { name: "Notifications" });
|
|
59
|
+
expect(toggle).toBeInTheDocument();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("applies custom className to the button (when no label)", () => {
|
|
63
|
+
const onCheckedChange = vi.fn();
|
|
64
|
+
render(
|
|
65
|
+
<Switch
|
|
66
|
+
checked={false}
|
|
67
|
+
onCheckedChange={onCheckedChange}
|
|
68
|
+
ariaLabel="Test"
|
|
69
|
+
className="custom-class"
|
|
70
|
+
/>,
|
|
71
|
+
);
|
|
72
|
+
const toggle = screen.getByRole("switch");
|
|
73
|
+
expect(toggle).toHaveClass("custom-class");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("applies custom className to the label wrapper (when label exists)", () => {
|
|
77
|
+
const onCheckedChange = vi.fn();
|
|
78
|
+
render(
|
|
79
|
+
<Switch
|
|
80
|
+
checked={false}
|
|
81
|
+
onCheckedChange={onCheckedChange}
|
|
82
|
+
label="Test Label"
|
|
83
|
+
className="wrapper-class"
|
|
84
|
+
/>,
|
|
85
|
+
);
|
|
86
|
+
const label = screen.getByText("Test Label").closest("label");
|
|
87
|
+
expect(label).toHaveClass("wrapper-class");
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
92
|
+
// Interaction
|
|
93
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
describe("Switch — Interaction", () => {
|
|
96
|
+
it("calls onCheckedChange with true when unchecked switch is clicked", () => {
|
|
97
|
+
const onCheckedChange = vi.fn();
|
|
98
|
+
render(
|
|
99
|
+
<Switch
|
|
100
|
+
checked={false}
|
|
101
|
+
onCheckedChange={onCheckedChange}
|
|
102
|
+
ariaLabel="Test"
|
|
103
|
+
/>,
|
|
104
|
+
);
|
|
105
|
+
const toggle = screen.getByRole("switch");
|
|
106
|
+
fireEvent.click(toggle);
|
|
107
|
+
expect(onCheckedChange).toHaveBeenCalledWith(true);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("calls onCheckedChange with false when checked switch is clicked", () => {
|
|
111
|
+
const onCheckedChange = vi.fn();
|
|
112
|
+
render(
|
|
113
|
+
<Switch
|
|
114
|
+
checked={true}
|
|
115
|
+
onCheckedChange={onCheckedChange}
|
|
116
|
+
ariaLabel="Test"
|
|
117
|
+
/>,
|
|
118
|
+
);
|
|
119
|
+
const toggle = screen.getByRole("switch");
|
|
120
|
+
fireEvent.click(toggle);
|
|
121
|
+
expect(onCheckedChange).toHaveBeenCalledWith(false);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("does not call onCheckedChange when disabled", () => {
|
|
125
|
+
const onCheckedChange = vi.fn();
|
|
126
|
+
render(
|
|
127
|
+
<Switch
|
|
128
|
+
checked={false}
|
|
129
|
+
onCheckedChange={onCheckedChange}
|
|
130
|
+
disabled
|
|
131
|
+
ariaLabel="Test"
|
|
132
|
+
/>,
|
|
133
|
+
);
|
|
134
|
+
const toggle = screen.getByRole("switch");
|
|
135
|
+
fireEvent.click(toggle);
|
|
136
|
+
expect(onCheckedChange).not.toHaveBeenCalled();
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
141
|
+
// Accessibility
|
|
142
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
describe("Switch — Accessibility", () => {
|
|
145
|
+
it("has role='switch'", () => {
|
|
146
|
+
render(
|
|
147
|
+
<Switch checked={false} onCheckedChange={() => {}} ariaLabel="Test" />,
|
|
148
|
+
);
|
|
149
|
+
expect(screen.getByRole("switch")).toBeInTheDocument();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("has correct aria-checked state", () => {
|
|
153
|
+
const { rerender } = render(
|
|
154
|
+
<Switch checked={false} onCheckedChange={() => {}} ariaLabel="Test" />,
|
|
155
|
+
);
|
|
156
|
+
expect(screen.getByRole("switch")).toHaveAttribute("aria-checked", "false");
|
|
157
|
+
|
|
158
|
+
rerender(
|
|
159
|
+
<Switch checked={true} onCheckedChange={() => {}} ariaLabel="Test" />,
|
|
160
|
+
);
|
|
161
|
+
expect(screen.getByRole("switch")).toHaveAttribute("aria-checked", "true");
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("has aria-disabled=true when disabled=true", () => {
|
|
165
|
+
render(
|
|
166
|
+
<Switch
|
|
167
|
+
disabled
|
|
168
|
+
checked={false}
|
|
169
|
+
onCheckedChange={() => {}}
|
|
170
|
+
ariaLabel="Test"
|
|
171
|
+
/>,
|
|
172
|
+
);
|
|
173
|
+
expect(screen.getByRole("switch")).toHaveAttribute("aria-disabled", "true");
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("forwards ref to the button element", () => {
|
|
177
|
+
const ref = React.createRef<HTMLButtonElement>();
|
|
178
|
+
render(
|
|
179
|
+
<Switch
|
|
180
|
+
ref={ref}
|
|
181
|
+
checked={false}
|
|
182
|
+
onCheckedChange={() => {}}
|
|
183
|
+
ariaLabel="Test"
|
|
184
|
+
/>,
|
|
185
|
+
);
|
|
186
|
+
expect(ref.current).toBeInstanceOf(HTMLButtonElement);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
191
|
+
// Keyboard Interaction
|
|
192
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
describe("Switch — Keyboard Interaction", () => {
|
|
195
|
+
it("toggles on Space key press", () => {
|
|
196
|
+
const onCheckedChange = vi.fn();
|
|
197
|
+
render(
|
|
198
|
+
<Switch
|
|
199
|
+
checked={false}
|
|
200
|
+
onCheckedChange={onCheckedChange}
|
|
201
|
+
ariaLabel="Space test"
|
|
202
|
+
/>,
|
|
203
|
+
);
|
|
204
|
+
const toggle = screen.getByRole("switch");
|
|
205
|
+
toggle.focus();
|
|
206
|
+
fireEvent.keyDown(toggle, { key: " ", code: "Space" });
|
|
207
|
+
expect(onCheckedChange).toHaveBeenCalledWith(true);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("toggles on Enter key press", () => {
|
|
211
|
+
const onCheckedChange = vi.fn();
|
|
212
|
+
render(
|
|
213
|
+
<Switch
|
|
214
|
+
checked={false}
|
|
215
|
+
onCheckedChange={onCheckedChange}
|
|
216
|
+
ariaLabel="Enter test"
|
|
217
|
+
/>,
|
|
218
|
+
);
|
|
219
|
+
const toggle = screen.getByRole("switch");
|
|
220
|
+
toggle.focus();
|
|
221
|
+
fireEvent.keyDown(toggle, { key: "Enter", code: "Enter" });
|
|
222
|
+
expect(onCheckedChange).toHaveBeenCalledWith(true);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
227
|
+
// Visual Props
|
|
228
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
229
|
+
|
|
230
|
+
describe("Switch — Visual Props", () => {
|
|
231
|
+
it("renders thumbContent when provided", () => {
|
|
232
|
+
render(
|
|
233
|
+
<Switch
|
|
234
|
+
checked={true}
|
|
235
|
+
onCheckedChange={() => {}}
|
|
236
|
+
thumbContent={<span data-testid="custom-icon">✓</span>}
|
|
237
|
+
icons={true}
|
|
238
|
+
ariaLabel="Icon test"
|
|
239
|
+
/>,
|
|
240
|
+
);
|
|
241
|
+
expect(screen.getByTestId("custom-icon")).toBeInTheDocument();
|
|
242
|
+
});
|
|
243
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file switch.tokens.ts
|
|
3
|
+
* MD3 Expressive Switch — Design tokens ported from SwitchTokens.kt.
|
|
4
|
+
* All dimensional values are in px (dp equivalent for web).
|
|
5
|
+
* @see docs/m3/switch/SwitchTokens.kt
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Design tokens for the MD3 Expressive Switch component.
|
|
10
|
+
*
|
|
11
|
+
* Maps directly from `SwitchTokens.kt` (v0_210) to CSS/JS values.
|
|
12
|
+
* Use these as the single source of truth for sizing and opacity.
|
|
13
|
+
*
|
|
14
|
+
* Color tokens are NOT included here — they reference CSS custom properties
|
|
15
|
+
* from the project's MD3 theme system (`--md-sys-color-*`).
|
|
16
|
+
*/
|
|
17
|
+
export const SwitchTokens = {
|
|
18
|
+
// ── Track ─────────────────────────────────────────────────────────────────
|
|
19
|
+
/** SwitchTokens.TrackWidth = 52dp */
|
|
20
|
+
trackWidth: 52,
|
|
21
|
+
/** SwitchTokens.TrackHeight = 32dp */
|
|
22
|
+
trackHeight: 32,
|
|
23
|
+
/** SwitchTokens.TrackOutlineWidth = 2dp */
|
|
24
|
+
trackOutlineWidth: 2,
|
|
25
|
+
|
|
26
|
+
// ── Handle (Thumb) ────────────────────────────────────────────────────────
|
|
27
|
+
/** SwitchTokens.SelectedHandleWidth/Height = 24dp */
|
|
28
|
+
selectedHandleSize: 24,
|
|
29
|
+
/** SwitchTokens.UnselectedHandleWidth/Height = 16dp */
|
|
30
|
+
unselectedHandleSize: 16,
|
|
31
|
+
/** SwitchTokens.IconHandleWidth/Height = 24dp (when thumb has icon content) */
|
|
32
|
+
iconHandleSize: 24,
|
|
33
|
+
/** SwitchTokens.PressedHandleWidth/Height = 28dp */
|
|
34
|
+
pressedHandleSize: 28,
|
|
35
|
+
|
|
36
|
+
// ── State Layer ───────────────────────────────────────────────────────────
|
|
37
|
+
/** SwitchTokens.StateLayerSize = 40dp */
|
|
38
|
+
stateLayerSize: 40,
|
|
39
|
+
|
|
40
|
+
// ── Icon ──────────────────────────────────────────────────────────────────
|
|
41
|
+
/** SwitchTokens.SelectedIconSize / UnselectedIconSize = 16dp */
|
|
42
|
+
iconSize: 16,
|
|
43
|
+
|
|
44
|
+
// ── Opacity (disabled states) ─────────────────────────────────────────────
|
|
45
|
+
/** SwitchTokens.DisabledTrackOpacity = 0.12 */
|
|
46
|
+
disabledTrackOpacity: 0.12,
|
|
47
|
+
/** SwitchTokens.DisabledSelectedHandleOpacity = 1.0 */
|
|
48
|
+
disabledSelectedHandleOpacity: 1.0,
|
|
49
|
+
/** SwitchTokens.DisabledUnselectedHandleOpacity = 0.38 */
|
|
50
|
+
disabledUnselectedHandleOpacity: 0.38,
|
|
51
|
+
/** SwitchTokens.DisabledSelectedIconOpacity = 0.38 */
|
|
52
|
+
disabledSelectedIconOpacity: 0.38,
|
|
53
|
+
/** SwitchTokens.DisabledUnselectedIconOpacity = 0.38 */
|
|
54
|
+
disabledUnselectedIconOpacity: 0.38,
|
|
55
|
+
} as const;
|
|
56
|
+
|
|
57
|
+
// ── MD3 Color token references (CSS custom properties) ────────────────────────
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* CSS custom property references for Switch colors.
|
|
61
|
+
* These map to the project's `--md-sys-color-*` tokens in `colors.css`.
|
|
62
|
+
*
|
|
63
|
+
* DO NOT hardcode hex values — always use these references so the
|
|
64
|
+
* component automatically adapts to light/dark theme switching.
|
|
65
|
+
*/
|
|
66
|
+
export const SwitchColors = {
|
|
67
|
+
// Track
|
|
68
|
+
checkedTrack: "var(--md-sys-color-primary)",
|
|
69
|
+
uncheckedTrack: "var(--md-sys-color-surface-container-highest)",
|
|
70
|
+
uncheckedTrackOutline: "var(--md-sys-color-outline)",
|
|
71
|
+
|
|
72
|
+
// Thumb
|
|
73
|
+
checkedThumb: "var(--md-sys-color-on-primary)",
|
|
74
|
+
uncheckedThumb: "var(--md-sys-color-outline)",
|
|
75
|
+
hoverCheckedThumb: "var(--md-sys-color-primary-container)",
|
|
76
|
+
hoverUncheckedThumb: "var(--md-sys-color-on-surface-variant)",
|
|
77
|
+
disabledCheckedThumb: "var(--md-sys-color-surface)",
|
|
78
|
+
|
|
79
|
+
// Icon
|
|
80
|
+
checkedIcon: "var(--md-sys-color-on-primary-container)",
|
|
81
|
+
uncheckedIcon: "var(--md-sys-color-surface-container-highest)",
|
|
82
|
+
|
|
83
|
+
// State layer
|
|
84
|
+
checkedStateLayer: "var(--md-sys-color-primary)",
|
|
85
|
+
uncheckedStateLayer: "var(--md-sys-color-on-surface)",
|
|
86
|
+
|
|
87
|
+
// Focus indicator
|
|
88
|
+
focusIndicator: "var(--md-sys-color-secondary)",
|
|
89
|
+
} as const;
|