@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
package/src/ui/chip.tsx
ADDED
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file chip.tsx
|
|
3
|
+
*
|
|
4
|
+
* MD3 Expressive Chip component — 4 variants.
|
|
5
|
+
*
|
|
6
|
+
* - `assist` → Triggered actions spanning multiple apps. Flat (bordered) or Elevated.
|
|
7
|
+
* - `filter` → Toggleable selections. Animated checkmark on select.
|
|
8
|
+
* - `input` → Entities/tags with optional avatar and a dedicated remove button.
|
|
9
|
+
* - `suggestion` → Contextual dynamic recommendations. Flat (bordered) or Elevated.
|
|
10
|
+
*
|
|
11
|
+
* @remarks
|
|
12
|
+
* Token references (Kotlin source):
|
|
13
|
+
* AssistChipTokens, FilterChipTokens, InputChipTokens, SuggestionChipTokens
|
|
14
|
+
*
|
|
15
|
+
* Architecture:
|
|
16
|
+
* - Styling: `cva` + `cn` (clsx/tailwind-merge)
|
|
17
|
+
* - Animation: Framer Motion (`LazyMotion` + `domMax`) for animated checkmark
|
|
18
|
+
* - Ripple: `Ripple` + `useRippleState` from `./ripple.tsx`
|
|
19
|
+
* - A11y: `role="checkbox"` (filter), `role="button"` (others); full keyboard support
|
|
20
|
+
*
|
|
21
|
+
* @see https://m3.material.io/components/chips/overview
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
25
|
+
import { AnimatePresence, domMax, LazyMotion, m } from "motion/react";
|
|
26
|
+
import * as React from "react";
|
|
27
|
+
import { cn } from "../lib/utils";
|
|
28
|
+
import { Ripple, useRippleState } from "./ripple";
|
|
29
|
+
|
|
30
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
31
|
+
// Internal Icons
|
|
32
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Animated checkmark icon for selected Filter chips.
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
function CheckIcon({ className }: { className?: string }) {
|
|
39
|
+
return (
|
|
40
|
+
<svg
|
|
41
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
42
|
+
viewBox="0 0 24 24"
|
|
43
|
+
width={18}
|
|
44
|
+
height={18}
|
|
45
|
+
fill="none"
|
|
46
|
+
stroke="currentColor"
|
|
47
|
+
strokeWidth={2.5}
|
|
48
|
+
strokeLinecap="round"
|
|
49
|
+
strokeLinejoin="round"
|
|
50
|
+
aria-hidden="true"
|
|
51
|
+
className={className}
|
|
52
|
+
>
|
|
53
|
+
<polyline points="20 6 9 17 4 12" />
|
|
54
|
+
</svg>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Close (×) icon for the trailing remove button on Input chips.
|
|
60
|
+
* @internal
|
|
61
|
+
*/
|
|
62
|
+
function CloseIcon({ className }: { className?: string }) {
|
|
63
|
+
return (
|
|
64
|
+
<svg
|
|
65
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
66
|
+
viewBox="0 0 24 24"
|
|
67
|
+
width={18}
|
|
68
|
+
height={18}
|
|
69
|
+
fill="none"
|
|
70
|
+
stroke="currentColor"
|
|
71
|
+
strokeWidth={2.5}
|
|
72
|
+
strokeLinecap="round"
|
|
73
|
+
strokeLinejoin="round"
|
|
74
|
+
aria-hidden="true"
|
|
75
|
+
className={className}
|
|
76
|
+
>
|
|
77
|
+
<line x1="18" y1="6" x2="6" y2="18" />
|
|
78
|
+
<line x1="6" y1="6" x2="18" y2="18" />
|
|
79
|
+
</svg>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// CVA Variants
|
|
84
|
+
// Token mapping references (Kotlin source):
|
|
85
|
+
// - FlatOutlineColor (all variants): border-m3-outline-variant
|
|
86
|
+
// - FlatSelectedContainerColor: bg-m3-secondary-container
|
|
87
|
+
// - SelectedLabelTextColor: text-m3-on-secondary-container
|
|
88
|
+
// - ElevatedContainerColor: bg-m3-surface-container-low
|
|
89
|
+
|
|
90
|
+
const chipVariants = cva(
|
|
91
|
+
[
|
|
92
|
+
// Base layout
|
|
93
|
+
"inline-flex items-center h-8 rounded-lg",
|
|
94
|
+
// Typography: LabelLarge
|
|
95
|
+
"text-sm font-medium leading-none",
|
|
96
|
+
// Interaction
|
|
97
|
+
"relative overflow-hidden cursor-pointer select-none",
|
|
98
|
+
"transition-all duration-200 ease-in-out",
|
|
99
|
+
// Remove browser default focus; use MD3 focus ring
|
|
100
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-1",
|
|
101
|
+
"focus-visible:ring-m3-secondary",
|
|
102
|
+
// Expressive scale feedback (MD3 "bouncy" press)
|
|
103
|
+
"active:scale-[0.98]",
|
|
104
|
+
// MD3 State Layer (pseudo-element overlay)
|
|
105
|
+
// Use inset-[-1px] to ensure the state layer covers the 1px border perfectly
|
|
106
|
+
"before:absolute before:inset-[-1px] before:pointer-events-none before:rounded-lg",
|
|
107
|
+
"before:transition-opacity before:duration-200 before:opacity-0",
|
|
108
|
+
"hover:before:opacity-[0.08] focus-visible:before:opacity-[0.10] active:before:opacity-[0.10]",
|
|
109
|
+
].join(" "),
|
|
110
|
+
{
|
|
111
|
+
variants: {
|
|
112
|
+
/**
|
|
113
|
+
* Chip variant controlling default colors and border behavior.
|
|
114
|
+
* Selected/elevated state overrides are applied via `cn()` at runtime.
|
|
115
|
+
*/
|
|
116
|
+
variant: {
|
|
117
|
+
/**
|
|
118
|
+
* Assist chip – FlatOutlineColor: outline-variant, LabelTextColor: on-surface
|
|
119
|
+
* Leading icon color: primary (AssistChipTokens.IconColor)
|
|
120
|
+
*/
|
|
121
|
+
assist:
|
|
122
|
+
"border border-m3-outline-variant text-m3-on-surface before:bg-m3-on-surface",
|
|
123
|
+
/**
|
|
124
|
+
* Filter chip (unselected) – FlatUnselectedOutlineColor: outline-variant
|
|
125
|
+
* UnselectedLabelTextColor: on-surface-variant
|
|
126
|
+
*/
|
|
127
|
+
filter:
|
|
128
|
+
"border border-m3-outline-variant text-m3-on-surface-variant before:bg-m3-on-surface-variant",
|
|
129
|
+
/**
|
|
130
|
+
* Input chip (unselected) – UnselectedOutlineColor: outline-variant
|
|
131
|
+
* UnselectedLabelTextColor: on-surface-variant
|
|
132
|
+
*/
|
|
133
|
+
input:
|
|
134
|
+
"border border-m3-outline-variant text-m3-on-surface-variant before:bg-m3-on-surface-variant",
|
|
135
|
+
/**
|
|
136
|
+
* Suggestion chip – FlatOutlineColor: outline-variant
|
|
137
|
+
* LabelTextColor: on-surface-variant (SuggestionChipTokens)
|
|
138
|
+
*/
|
|
139
|
+
suggestion:
|
|
140
|
+
"border border-m3-outline-variant text-m3-on-surface-variant before:bg-m3-on-surface-variant",
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
defaultVariants: { variant: "assist" },
|
|
144
|
+
},
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
export interface ChipProps
|
|
148
|
+
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "children"> {
|
|
149
|
+
/**
|
|
150
|
+
* Chip variant.
|
|
151
|
+
* - `assist` → Smart/automated actions. Flat by default, can be elevated.
|
|
152
|
+
* - `filter` → Toggleable tag/filter. Shows animated checkmark when selected.
|
|
153
|
+
* - `input` → Entity representation (tag, contact). Has optional avatar + remove button.
|
|
154
|
+
* - `suggestion` → Contextual suggestions. Like assist, flat by default, can be elevated.
|
|
155
|
+
* @default 'assist'
|
|
156
|
+
*/
|
|
157
|
+
variant?: VariantProps<typeof chipVariants>["variant"];
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Renders with elevation shadow (Level 1) and fills background with `surface-container-low`.
|
|
161
|
+
* Applicable to `assist`, `filter` (unselected), and `suggestion` variants.
|
|
162
|
+
* Source: AssistChipTokens.ElevatedContainerColor / SuggestionChipTokens.ElevatedContainerColor
|
|
163
|
+
*/
|
|
164
|
+
elevated?: boolean;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Toggle/selection state.
|
|
168
|
+
* - `filter`: selected → bg `secondary-container`, animated checkmark appears.
|
|
169
|
+
* - `input`: selected → bg `secondary-container`.
|
|
170
|
+
* Used for `role="checkbox"` (filter) / `aria-pressed` (input).
|
|
171
|
+
*/
|
|
172
|
+
selected?: boolean;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Disables the chip. Applies:
|
|
176
|
+
* - `pointer-events-none` – no mouse/touch interaction
|
|
177
|
+
* - `opacity-[0.38]` – DisabledLabelTextOpacity (0.38) per MD3 tokens
|
|
178
|
+
* - `aria-disabled="true"`
|
|
179
|
+
* - `tabIndex={-1}`
|
|
180
|
+
*/
|
|
181
|
+
disabled?: boolean;
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Visible label. Required. Can be a string or ReactNode.
|
|
185
|
+
*/
|
|
186
|
+
label: React.ReactNode;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Optional leading icon element (18×18px recommended).
|
|
190
|
+
* For `filter` chips with `selected=true`, this is replaced by an animated checkmark.
|
|
191
|
+
* For `assist`/`suggestion`: icon color → `primary`
|
|
192
|
+
* For `input` (unselected): icon color → `on-surface-variant`
|
|
193
|
+
*/
|
|
194
|
+
leadingIcon?: React.ReactNode;
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Optional trailing icon element (18×18px recommended).
|
|
198
|
+
* Color: `on-surface-variant` (unselected) / `on-secondary-container` (selected).
|
|
199
|
+
*/
|
|
200
|
+
trailingIcon?: React.ReactNode;
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Avatar element for `input` chips. Takes priority over `leadingIcon`.
|
|
204
|
+
* Rendered as a 24×24px circle (InputChipTokens: AvatarSize = 24.dp, AvatarShape = CornerFull).
|
|
205
|
+
*/
|
|
206
|
+
avatar?: React.ReactNode;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Callback when the trailing remove (×) button is activated on `input` chips.
|
|
210
|
+
* When provided, a dedicated tabbable close button with `aria-label="Remove {label}"` is rendered.
|
|
211
|
+
*/
|
|
212
|
+
onRemove?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const ChipImpl = React.forwardRef<HTMLButtonElement, ChipProps>(
|
|
216
|
+
(
|
|
217
|
+
{
|
|
218
|
+
variant = "assist",
|
|
219
|
+
elevated = false,
|
|
220
|
+
selected = false,
|
|
221
|
+
disabled = false,
|
|
222
|
+
label,
|
|
223
|
+
leadingIcon,
|
|
224
|
+
trailingIcon,
|
|
225
|
+
avatar,
|
|
226
|
+
onRemove,
|
|
227
|
+
className,
|
|
228
|
+
onClick,
|
|
229
|
+
...props
|
|
230
|
+
},
|
|
231
|
+
ref,
|
|
232
|
+
) => {
|
|
233
|
+
const { ripples, onPointerDown, removeRipple } = useRippleState({
|
|
234
|
+
disabled,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const isFilter = variant === "filter";
|
|
238
|
+
const isInput = variant === "input";
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* For input chips, avatar takes priority over leadingIcon.
|
|
242
|
+
* Source: leadingContent() in Chip.kt – "An avatar takes precedence"
|
|
243
|
+
*/
|
|
244
|
+
const resolvedLeadingIcon = isInput && avatar ? avatar : leadingIcon;
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Filter chip: when selected, show animated checkmark.
|
|
248
|
+
* If a leadingIcon is provided, the checkmark replaces it when selected.
|
|
249
|
+
* Source: Chip.kt – AnimatedVisibility with expandHorizontally + fadeIn
|
|
250
|
+
*/
|
|
251
|
+
const showCheckmark = isFilter && selected;
|
|
252
|
+
|
|
253
|
+
/** Trailing slot: custom trailingIcon OR the remove button for input chips */
|
|
254
|
+
const hasTrailingContent = !!trailingIcon || !!onRemove;
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Leading slot: filter chips always have a potential leading slot
|
|
258
|
+
* (checkmark or leadingIcon). Otherwise depends on resolvedLeadingIcon.
|
|
259
|
+
*/
|
|
260
|
+
const hasLeadingContent = isFilter || !!resolvedLeadingIcon;
|
|
261
|
+
|
|
262
|
+
// Source: AssistChipDefaults.ContentPadding / inputChipPadding in Chip.kt
|
|
263
|
+
// No icons: px-4 | Input: px-3
|
|
264
|
+
// Leading only: pl-2 pr-4 | Input: pl-1 pr-3
|
|
265
|
+
// Trailing only: pl-4 pr-2 | Input: pl-3 pr-2
|
|
266
|
+
// Both: px-2 | Input: pl-1 pr-2
|
|
267
|
+
const paddingClass = React.useMemo(
|
|
268
|
+
() =>
|
|
269
|
+
cn(
|
|
270
|
+
!isInput && !hasLeadingContent && !hasTrailingContent && "px-4",
|
|
271
|
+
!isInput && hasLeadingContent && !hasTrailingContent && "pl-2 pr-4",
|
|
272
|
+
!isInput && !hasLeadingContent && hasTrailingContent && "pl-4 pr-2",
|
|
273
|
+
!isInput && hasLeadingContent && hasTrailingContent && "px-2",
|
|
274
|
+
isInput && !hasLeadingContent && !hasTrailingContent && "px-3",
|
|
275
|
+
isInput && hasLeadingContent && !hasTrailingContent && "pl-1 pr-3",
|
|
276
|
+
isInput && !hasLeadingContent && hasTrailingContent && "pl-3 pr-2",
|
|
277
|
+
isInput && hasLeadingContent && hasTrailingContent && "pl-1 pr-2",
|
|
278
|
+
),
|
|
279
|
+
[isInput, hasLeadingContent, hasTrailingContent],
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
// Selected / elevated / disabled state overrides.
|
|
283
|
+
// Source: MD3 tokens — DisabledLabelTextOpacity=0.38, FlatDisabledOutlineOpacity=0.12
|
|
284
|
+
const stateClass = React.useMemo(
|
|
285
|
+
() =>
|
|
286
|
+
cn(
|
|
287
|
+
(isFilter || isInput) &&
|
|
288
|
+
selected &&
|
|
289
|
+
"bg-m3-secondary-container text-m3-on-secondary-container border-none before:bg-m3-on-secondary-container",
|
|
290
|
+
elevated &&
|
|
291
|
+
!selected &&
|
|
292
|
+
"bg-m3-surface-container-low border-none elevation-1",
|
|
293
|
+
elevated && isFilter && selected && "elevation-1",
|
|
294
|
+
disabled && "opacity-[0.38] pointer-events-none cursor-not-allowed",
|
|
295
|
+
disabled && !selected && "border-m3-outline-variant/[.12]",
|
|
296
|
+
disabled &&
|
|
297
|
+
selected &&
|
|
298
|
+
"bg-m3-on-surface/[.12] text-m3-on-surface border-none",
|
|
299
|
+
),
|
|
300
|
+
[isFilter, isInput, selected, elevated, disabled],
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
// Leading icon color tokens:
|
|
304
|
+
// assist/suggestion → Primary | filter unselected → Primary
|
|
305
|
+
// filter selected → OnSecondaryContainer | input unselected → OnSurfaceVariant | input selected → Primary
|
|
306
|
+
const leadingIconColorClass = React.useMemo(
|
|
307
|
+
() =>
|
|
308
|
+
cn(
|
|
309
|
+
(variant === "assist" || variant === "suggestion") &&
|
|
310
|
+
"text-m3-primary",
|
|
311
|
+
isFilter && !selected && "text-m3-primary",
|
|
312
|
+
isFilter && selected && "text-m3-on-secondary-container",
|
|
313
|
+
isInput && !selected && "text-m3-on-surface-variant",
|
|
314
|
+
isInput && selected && "text-m3-primary",
|
|
315
|
+
),
|
|
316
|
+
[variant, isFilter, isInput, selected],
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
const isCompound = !!onRemove;
|
|
320
|
+
const Root = (isCompound ? "div" : "button") as React.ElementType;
|
|
321
|
+
|
|
322
|
+
// Composed class for the root container
|
|
323
|
+
const containerClass = cn(
|
|
324
|
+
chipVariants({ variant }),
|
|
325
|
+
!isCompound && paddingClass,
|
|
326
|
+
!isCompound && "gap-2",
|
|
327
|
+
stateClass,
|
|
328
|
+
// When compound, the root div handles the overall shape but not the main interaction
|
|
329
|
+
// We disable the root's state layer (hover/focus) to avoid the 1px gap issue
|
|
330
|
+
isCompound &&
|
|
331
|
+
"items-stretch cursor-default active:scale-100 before:opacity-0 gap-0",
|
|
332
|
+
className,
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
/** Main hit area content (Leading Icon + Label) */
|
|
336
|
+
const mainContent = (
|
|
337
|
+
<>
|
|
338
|
+
<AnimatePresence initial={false} mode="wait">
|
|
339
|
+
{isFilter ? (
|
|
340
|
+
showCheckmark ? (
|
|
341
|
+
<m.span
|
|
342
|
+
key="checkmark"
|
|
343
|
+
initial={{ width: 0, opacity: 0 }}
|
|
344
|
+
animate={{ width: 18, opacity: 1 }}
|
|
345
|
+
exit={{ width: 0, opacity: 0 }}
|
|
346
|
+
transition={{
|
|
347
|
+
width: { duration: 0.2, ease: [0.2, 0, 0, 1] },
|
|
348
|
+
opacity: { duration: 0.15, ease: "easeOut" },
|
|
349
|
+
}}
|
|
350
|
+
className="flex items-center justify-center shrink-0 overflow-hidden"
|
|
351
|
+
aria-hidden="true"
|
|
352
|
+
>
|
|
353
|
+
<CheckIcon />
|
|
354
|
+
</m.span>
|
|
355
|
+
) : resolvedLeadingIcon ? (
|
|
356
|
+
<m.span
|
|
357
|
+
key="leading-icon"
|
|
358
|
+
initial={{ width: 0, opacity: 0 }}
|
|
359
|
+
animate={{ width: 18, opacity: 1 }}
|
|
360
|
+
exit={{ width: 0, opacity: 0 }}
|
|
361
|
+
transition={{
|
|
362
|
+
width: { duration: 0.2, ease: [0.2, 0, 0, 1] },
|
|
363
|
+
opacity: { duration: 0.15, ease: "easeOut" },
|
|
364
|
+
}}
|
|
365
|
+
className={cn(
|
|
366
|
+
"flex items-center justify-center shrink-0 overflow-hidden [&_.md-icon]:text-[length:inherit]!",
|
|
367
|
+
leadingIconColorClass,
|
|
368
|
+
)}
|
|
369
|
+
style={{ fontSize: 18 }}
|
|
370
|
+
aria-hidden="true"
|
|
371
|
+
>
|
|
372
|
+
{resolvedLeadingIcon}
|
|
373
|
+
</m.span>
|
|
374
|
+
) : null
|
|
375
|
+
) : resolvedLeadingIcon ? (
|
|
376
|
+
isInput && avatar ? (
|
|
377
|
+
<span
|
|
378
|
+
key="avatar"
|
|
379
|
+
className="flex items-center justify-center shrink-0 w-6 h-6 rounded-full overflow-hidden"
|
|
380
|
+
aria-hidden="true"
|
|
381
|
+
>
|
|
382
|
+
{resolvedLeadingIcon}
|
|
383
|
+
</span>
|
|
384
|
+
) : (
|
|
385
|
+
<span
|
|
386
|
+
key="leading-icon"
|
|
387
|
+
className={cn(
|
|
388
|
+
"flex items-center justify-center shrink-0 w-4.5 h-4.5 [&_.md-icon]:text-[length:inherit]!",
|
|
389
|
+
leadingIconColorClass,
|
|
390
|
+
)}
|
|
391
|
+
style={{ fontSize: 18 }}
|
|
392
|
+
aria-hidden="true"
|
|
393
|
+
>
|
|
394
|
+
{resolvedLeadingIcon}
|
|
395
|
+
</span>
|
|
396
|
+
)
|
|
397
|
+
) : null}
|
|
398
|
+
</AnimatePresence>
|
|
399
|
+
|
|
400
|
+
<span className="whitespace-nowrap">{label}</span>
|
|
401
|
+
</>
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
return (
|
|
405
|
+
<LazyMotion features={domMax} strict>
|
|
406
|
+
<Root
|
|
407
|
+
ref={(!isCompound ? ref : undefined) as React.Ref<HTMLButtonElement>}
|
|
408
|
+
type={!isCompound ? "button" : undefined}
|
|
409
|
+
// Filter chips act as checkboxes; Input chips can be buttons/toggleable; others buttons
|
|
410
|
+
// Source: Material Design 3 Accessibility
|
|
411
|
+
{...(isFilter
|
|
412
|
+
? {
|
|
413
|
+
role: "checkbox" as React.AriaRole,
|
|
414
|
+
"aria-checked": selected,
|
|
415
|
+
}
|
|
416
|
+
: isInput && !isCompound && selected
|
|
417
|
+
? { role: "button", "aria-pressed": true }
|
|
418
|
+
: isCompound
|
|
419
|
+
? { role: "group" }
|
|
420
|
+
: { role: "button" })}
|
|
421
|
+
aria-disabled={disabled || undefined}
|
|
422
|
+
tabIndex={isCompound ? -1 : disabled ? -1 : 0}
|
|
423
|
+
disabled={!isCompound ? disabled : undefined}
|
|
424
|
+
onClick={!isCompound ? onClick : undefined}
|
|
425
|
+
onPointerDown={!isCompound ? onPointerDown : undefined}
|
|
426
|
+
className={containerClass}
|
|
427
|
+
// Filter out props that shouldn't be on a div if isCompound
|
|
428
|
+
{...(isCompound ? {} : props)}
|
|
429
|
+
>
|
|
430
|
+
{/* State Ripple layer */}
|
|
431
|
+
{!isCompound && (
|
|
432
|
+
<Ripple ripples={ripples} onRippleDone={removeRipple} />
|
|
433
|
+
)}
|
|
434
|
+
|
|
435
|
+
{/* Main action area: if compound, this is a nested button for a11y & avoids nesting buttons */}
|
|
436
|
+
{isCompound ? (
|
|
437
|
+
<button
|
|
438
|
+
ref={ref}
|
|
439
|
+
type="button"
|
|
440
|
+
tabIndex={disabled ? -1 : 0}
|
|
441
|
+
disabled={disabled}
|
|
442
|
+
onClick={onClick}
|
|
443
|
+
onPointerDown={onPointerDown}
|
|
444
|
+
className={cn(
|
|
445
|
+
"flex items-center h-full grow focus:outline-none appearance-none bg-transparent border-none",
|
|
446
|
+
"text-inherit font-inherit cursor-pointer",
|
|
447
|
+
// Move padding here; keep horizontal padding but remove trailing to fit next to X
|
|
448
|
+
paddingClass,
|
|
449
|
+
"gap-2",
|
|
450
|
+
"pr-0",
|
|
451
|
+
// Re-apply focus ring to internal button instead of root div
|
|
452
|
+
"focus-visible:ring-2 focus-visible:ring-m3-secondary focus-visible:ring-inset",
|
|
453
|
+
"rounded-l-[7px] rounded-r-none",
|
|
454
|
+
// State layer for main area
|
|
455
|
+
"relative overflow-hidden",
|
|
456
|
+
"before:absolute before:-inset-px before:pointer-events-none before:bg-current",
|
|
457
|
+
"before:transition-opacity before:duration-200 before:opacity-0 before:rounded-l-[7px]",
|
|
458
|
+
"hover:before:opacity-[0.08] focus-visible:before:opacity-[0.10] active:before:opacity-[0.10]",
|
|
459
|
+
)}
|
|
460
|
+
>
|
|
461
|
+
<Ripple ripples={ripples} onRippleDone={removeRipple} />
|
|
462
|
+
{mainContent}
|
|
463
|
+
</button>
|
|
464
|
+
) : (
|
|
465
|
+
mainContent
|
|
466
|
+
)}
|
|
467
|
+
|
|
468
|
+
{/* Trailing slot */}
|
|
469
|
+
{hasTrailingContent && (
|
|
470
|
+
<span className="flex items-center justify-center shrink-0">
|
|
471
|
+
{onRemove ? (
|
|
472
|
+
<button
|
|
473
|
+
type="button"
|
|
474
|
+
tabIndex={disabled ? -1 : 0}
|
|
475
|
+
aria-label={
|
|
476
|
+
typeof label === "string" ? `Remove ${label}` : "Remove"
|
|
477
|
+
}
|
|
478
|
+
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
|
479
|
+
e.stopPropagation();
|
|
480
|
+
onRemove(e);
|
|
481
|
+
}}
|
|
482
|
+
onPointerDown={(e: React.PointerEvent<HTMLButtonElement>) =>
|
|
483
|
+
e.stopPropagation()
|
|
484
|
+
}
|
|
485
|
+
className={cn(
|
|
486
|
+
"flex items-center justify-center w-8.5 h-full", // 18px icon + 8px left + 8px right padding
|
|
487
|
+
"cursor-pointer focus-visible:outline-none",
|
|
488
|
+
"transition-all duration-150",
|
|
489
|
+
"relative overflow-hidden rounded-r-[7px] rounded-l-none",
|
|
490
|
+
"before:absolute before:-inset-px before:pointer-events-none before:bg-current",
|
|
491
|
+
"before:transition-opacity before:duration-200 before:opacity-0 before:rounded-r-[7px]",
|
|
492
|
+
"hover:before:opacity-[0.08] active:before:opacity-[0.12]",
|
|
493
|
+
// TrailingIcon color tokens
|
|
494
|
+
selected
|
|
495
|
+
? "text-m3-on-secondary-container"
|
|
496
|
+
: "text-m3-on-surface-variant",
|
|
497
|
+
)}
|
|
498
|
+
>
|
|
499
|
+
<CloseIcon />
|
|
500
|
+
</button>
|
|
501
|
+
) : trailingIcon ? (
|
|
502
|
+
<span
|
|
503
|
+
className={cn(
|
|
504
|
+
"flex items-center justify-center w-4.5 h-4.5 [&_.md-icon]:text-[length:inherit]!",
|
|
505
|
+
selected
|
|
506
|
+
? "text-m3-on-secondary-container"
|
|
507
|
+
: "text-m3-on-surface-variant",
|
|
508
|
+
)}
|
|
509
|
+
style={{ fontSize: 18 }}
|
|
510
|
+
aria-hidden="true"
|
|
511
|
+
>
|
|
512
|
+
{trailingIcon}
|
|
513
|
+
</span>
|
|
514
|
+
) : null}
|
|
515
|
+
</span>
|
|
516
|
+
)}
|
|
517
|
+
</Root>
|
|
518
|
+
</LazyMotion>
|
|
519
|
+
);
|
|
520
|
+
},
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
ChipImpl.displayName = "Chip";
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* MD3 Expressive Chip — 4-variant interactive tag component.
|
|
527
|
+
*
|
|
528
|
+
* @remarks
|
|
529
|
+
* - `filter` chips accept `selected` and render an animated checkmark.
|
|
530
|
+
* - `input` chips accept `onRemove` to render a compound close button.
|
|
531
|
+
* - `elevated` is supported on `assist`, `filter` (unselected), and `suggestion`.
|
|
532
|
+
* - Fully accessible: `role="checkbox"` for filter, `role="group"` for compound chips.
|
|
533
|
+
*
|
|
534
|
+
* @example
|
|
535
|
+
* ```tsx
|
|
536
|
+
* // Assist chip
|
|
537
|
+
* <Chip variant="assist" label="Share" onClick={share} />
|
|
538
|
+
*
|
|
539
|
+
* // Filter chip
|
|
540
|
+
* <Chip variant="filter" label="Unread" selected={showUnread} onClick={toggle} />
|
|
541
|
+
*
|
|
542
|
+
* // Input chip with remove
|
|
543
|
+
* <Chip variant="input" label="React" onRemove={() => removeTag("React")} />
|
|
544
|
+
* ```
|
|
545
|
+
*
|
|
546
|
+
* @see https://m3.material.io/components/chips/overview
|
|
547
|
+
*/
|
|
548
|
+
export const Chip = React.memo(ChipImpl);
|