@tinybigui/react 0.9.0 → 0.11.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/README.md CHANGED
@@ -12,7 +12,7 @@ A modern, accessible React component library implementing Google's Material Desi
12
12
 
13
13
  ## ✅ Status
14
14
 
15
- > **Latest Release: v0.9.0** (2026-06-09)
15
+ > **Latest Release: v0.11.0** (2026-06-09)
16
16
  >
17
17
  > **29 MD3 components** published to npm with full TypeScript support and WCAG 2.1 AA accessibility.
18
18
  >
@@ -149,24 +149,24 @@ See [THEMING.md](./THEMING.md) for the full customization guide.
149
149
 
150
150
  ### Phase 2: Navigation ✅
151
151
 
152
- | Component | Status | Description |
153
- | ------------------ | ------ | ------------------------------------ |
154
- | `AppBar` | ✅ | Four size variants, scroll elevation |
155
- | `Tabs` | ✅ | Primary and secondary tab variants |
156
- | `NavigationDrawer` | ✅ | Modal and standard navigation drawer |
157
- | `NavigationBar` | ✅ | Bottom navigation with badges |
158
- | `Search` | ✅ | SearchBar and SearchView overlay |
152
+ | Component | Status | Description |
153
+ | ------------------ | ------ | ------------------------------------------------------------------- |
154
+ | `AppBar` | ✅ | M3 expressive flexible slot architecture, subtitle growth (v0.10.0) |
155
+ | `Tabs` | ✅ | Primary and secondary tab variants |
156
+ | `NavigationDrawer` | ✅ | Modal and standard navigation drawer |
157
+ | `NavigationBar` | ✅ | Bottom navigation with badges |
158
+ | `Search` | ✅ | SearchBar and SearchView overlay |
159
159
 
160
160
  ### Phase 3: Feedback ✅
161
161
 
162
- | Component | Status | Description |
163
- | ------------- | ------ | --------------------------------------- |
164
- | `Dialog` | ✅ | Basic and fullscreen modal dialogs |
165
- | `Snackbar` | ✅ | Provider, stacking, imperative API |
166
- | `Menu` | ✅ | Dropdown, context menu, submenus |
167
- | `Progress` | ✅ | Linear and circular indicators |
168
- | `BottomSheet` | ✅ | Standard and modal with snap points |
169
- | `Tooltip` | ✅ | Plain and rich tooltip with positioning |
162
+ | Component | Status | Description |
163
+ | ------------- | ------ | ---------------------------------------------------------------- |
164
+ | `Dialog` | ✅ | Basic and fullscreen modal dialogs |
165
+ | `Snackbar` | ✅ | Provider, stacking, imperative API |
166
+ | `Menu` | ✅ | Dropdown, context menu, submenus |
167
+ | `Progress` | ✅ | Linear and circular indicators |
168
+ | `BottomSheet` | ✅ | MD3 expressive handle, variants-vs-states architecture (v0.11.0) |
169
+ | `Tooltip` | ✅ | Plain and rich tooltip with positioning |
170
170
 
171
171
  ### Phase 4: Data Display ✅
172
172
 
package/dist/index.cjs CHANGED
@@ -211,65 +211,111 @@ function useScrollElevation(options = {}) {
211
211
  };
212
212
  }
213
213
  var AppBarHeadless = React.forwardRef(
214
- ({ className, children, scrolled: scrolledProp, onScrollStateChange }, ref) => {
214
+ ({ className, children, scrolled: scrolledProp, onScrollStateChange, ...htmlProps }, ref) => {
215
215
  const { isScrolled } = useScrollElevation({
216
216
  scrolled: scrolledProp,
217
217
  onScrollStateChange
218
218
  });
219
- return /* @__PURE__ */ jsxRuntime.jsx("header", { ref, role: "banner", className, "data-scrolled": isScrolled, children });
219
+ return /* @__PURE__ */ jsxRuntime.jsx(
220
+ "header",
221
+ {
222
+ ...htmlProps,
223
+ ref,
224
+ role: "banner",
225
+ className,
226
+ "data-scrolled": isScrolled ? "" : void 0,
227
+ children
228
+ }
229
+ );
220
230
  }
221
231
  );
222
232
  AppBarHeadless.displayName = "AppBarHeadless";
223
233
  var appBarVariants = classVarianceAuthority.cva(
224
234
  [
225
- // Base classes (always applied)
226
- "w-full",
235
+ // Layout
236
+ "w-full flex flex-col",
237
+ // Color (base — at rest)
227
238
  "bg-surface text-on-surface",
228
- "flex flex-col",
229
- // Elevation transition using MD3 motion tokens
230
- "transition-shadow duration-medium2 ease-standard"
239
+ // Elevation base
240
+ "shadow-elevation-0",
241
+ // Scroll state — effects properties animated with standard spring (no overshoot)
242
+ "transition-[background-color,box-shadow]",
243
+ "duration-spring-standard-default-effects",
244
+ "ease-spring-standard-default-effects",
245
+ // On scroll: surface-container background + elevation-2
246
+ "group-data-[scrolled]/appbar:bg-surface-container",
247
+ "group-data-[scrolled]/appbar:shadow-elevation-2"
231
248
  ],
232
249
  {
233
250
  variants: {
234
251
  /**
235
- * Size variant (MD3 specification)
236
- * Controls bar height, title placement, and type scale
252
+ * Size variant controls bar height and which title row is shown.
253
+ * With-subtitle height growth is handled via group-data-[with-subtitle]/appbar below.
237
254
  */
238
255
  variant: {
239
- /** 64dp, title left-aligned, title-large */
256
+ /** 64dp fixed — title left-aligned in top row */
240
257
  small: "h-appbar-small",
241
- /** 64dp, title centered, title-large */
242
- "center-aligned": "h-appbar-small variant-center-aligned",
243
- /** min 112dp, title bottom-left, headline-medium (grows with subtitle) */
244
- medium: "min-h-appbar-medium",
245
- /** min 120dp, title bottom-left, display-small (grows with subtitle) */
246
- large: "min-h-appbar-large"
247
- },
248
- /**
249
- * Scroll state — controls surface elevation and background color
250
- * MD3: flat surface at rest → surface-container background + elevation-2 on scroll
251
- */
252
- scrolled: {
253
- false: "shadow-elevation-0",
254
- true: "bg-surface-container shadow-elevation-2"
258
+ /** 64dp fixed — title centered in top row */
259
+ "center-aligned": "h-appbar-small",
260
+ /**
261
+ * 112dp no-subtitle / 136dp with-subtitle — title in expanded bottom row.
262
+ * group-data-[with-subtitle]/appbar switches to the taller token.
263
+ */
264
+ medium: [
265
+ "min-h-appbar-medium",
266
+ "group-data-[with-subtitle]/appbar:min-h-appbar-medium-subtitle"
267
+ ],
268
+ /**
269
+ * 120dp no-subtitle / 152dp with-subtitle — title in expanded bottom row.
270
+ * group-data-[with-subtitle]/appbar switches to the taller token.
271
+ */
272
+ large: [
273
+ "min-h-appbar-large",
274
+ "group-data-[with-subtitle]/appbar:min-h-appbar-large-subtitle"
275
+ ]
276
+ }
277
+ },
278
+ defaultVariants: {
279
+ variant: "small"
280
+ }
281
+ }
282
+ );
283
+ var appBarTopRowVariants = classVarianceAuthority.cva(["flex items-center justify-between", "px-1"], {
284
+ variants: {
285
+ variant: {
286
+ small: "flex-1",
287
+ "center-aligned": "flex-1",
288
+ medium: "h-16 shrink-0",
289
+ large: "h-16 shrink-0"
290
+ }
291
+ },
292
+ defaultVariants: {
293
+ variant: "small"
294
+ }
295
+ });
296
+ var appBarLeadingVariants = classVarianceAuthority.cva(["flex shrink-0 items-center", "text-on-surface"]);
297
+ var appBarHeadlineBlockVariants = classVarianceAuthority.cva(
298
+ ["flex min-w-0 flex-1 flex-col justify-center", "px-1"],
299
+ {
300
+ variants: {
301
+ variant: {
302
+ small: "",
303
+ "center-aligned": "items-center text-center",
304
+ medium: "",
305
+ large: ""
255
306
  }
256
307
  },
257
308
  defaultVariants: {
258
- variant: "small",
259
- scrolled: false
309
+ variant: "small"
260
310
  }
261
311
  }
262
312
  );
263
313
  var appBarTitleVariants = classVarianceAuthority.cva("text-on-surface font-normal", {
264
314
  variants: {
265
315
  variant: {
266
- /** title-large: 22px / 28px line-height */
267
316
  small: "text-title-large truncate",
268
- /** title-large: 22px / 28px line-height, centered */
269
317
  "center-aligned": "text-title-large truncate",
270
- /** headline-medium: 28px / 36px line-height */
271
318
  medium: "text-headline-medium",
272
- /** display-small: 36px / 44px line-height */
273
319
  large: "text-display-small"
274
320
  }
275
321
  },
@@ -277,23 +323,27 @@ var appBarTitleVariants = classVarianceAuthority.cva("text-on-surface font-norma
277
323
  variant: "small"
278
324
  }
279
325
  });
280
- var appBarSubtitleVariants = classVarianceAuthority.cva("font-normal", {
326
+ var appBarSubtitleVariants = classVarianceAuthority.cva("text-on-surface-variant font-normal", {
281
327
  variants: {
282
328
  variant: {
283
- /** title-medium: 16px / 24px, on-surface-variant color */
284
- small: "text-title-medium text-on-surface-variant truncate",
285
- /** title-medium: 16px / 24px, centered, on-surface-variant color */
286
- "center-aligned": "text-title-medium text-on-surface-variant truncate",
287
- /** title-large: 22px / 28px, on-surface color */
288
- medium: "text-title-large text-on-surface",
289
- /** headline-small: 24px / 32px, on-surface color */
290
- large: "text-headline-small text-on-surface"
329
+ small: "text-label-medium truncate",
330
+ "center-aligned": "text-label-medium truncate",
331
+ medium: "text-label-large",
332
+ large: "text-title-medium"
291
333
  }
292
334
  },
293
335
  defaultVariants: {
294
336
  variant: "small"
295
337
  }
296
338
  });
339
+ var appBarTrailingVariants = classVarianceAuthority.cva([
340
+ "flex shrink-0 items-center gap-0.5",
341
+ "text-on-surface-variant"
342
+ ]);
343
+ var appBarExpandedTitleVariants = classVarianceAuthority.cva([
344
+ "flex flex-1 flex-col justify-end",
345
+ "gap-0.5 px-4 pb-4"
346
+ ]);
297
347
  var AppBar = React.forwardRef(
298
348
  ({
299
349
  variant = "small",
@@ -310,76 +360,54 @@ var AppBar = React.forwardRef(
310
360
  onScrollStateChange
311
361
  });
312
362
  const isExpandedVariant = variant === "medium" || variant === "large";
363
+ const hasSubtitle = subtitle != null;
313
364
  return /* @__PURE__ */ jsxRuntime.jsxs(
314
365
  AppBarHeadless,
315
366
  {
316
367
  ref,
317
368
  scrolled: isScrolled,
318
- className: cn(appBarVariants({ variant, scrolled: isScrolled }), className),
369
+ className: cn(
370
+ appBarVariants({ variant }),
371
+ // group/appbar: enables group-data-[scrolled]/appbar and
372
+ // group-data-[with-subtitle]/appbar child selectors in all slots
373
+ "group/appbar",
374
+ className
375
+ ),
376
+ "data-with-subtitle": hasSubtitle ? "" : void 0,
319
377
  children: [
320
- /* @__PURE__ */ jsxRuntime.jsxs(
321
- "div",
322
- {
323
- "data-slot": "top-row",
324
- className: cn(
325
- "flex items-center justify-between",
326
- "px-1",
327
- // Small and center-aligned: fill the full bar height
328
- !isExpandedVariant && "flex-1",
329
- // Expanded variants: fixed height for the top row (64dp)
330
- isExpandedVariant && "h-16 shrink-0"
331
- ),
332
- children: [
333
- navigationIcon != null && /* @__PURE__ */ jsxRuntime.jsx("div", { "data-slot": "navigation", className: "flex shrink-0 items-center", children: navigationIcon }),
334
- !isExpandedVariant && /* @__PURE__ */ jsxRuntime.jsxs(
335
- "div",
336
- {
337
- className: cn(
338
- "flex min-w-0 flex-1 flex-col justify-center px-1",
339
- variant === "center-aligned" && "text-center"
340
- ),
341
- children: [
342
- /* @__PURE__ */ jsxRuntime.jsx("span", { "data-testid": "appbar-title", className: cn(appBarTitleVariants({ variant })), children: title }),
343
- subtitle != null && /* @__PURE__ */ jsxRuntime.jsx(
344
- "span",
345
- {
346
- "data-testid": "appbar-subtitle",
347
- className: cn(appBarSubtitleVariants({ variant })),
348
- children: subtitle
349
- }
350
- )
351
- ]
352
- }
353
- ),
354
- actions != null && /* @__PURE__ */ jsxRuntime.jsx("div", { "data-slot": "actions", className: "flex shrink-0 items-center gap-0.5", children: actions })
355
- ]
356
- }
357
- ),
358
- isExpandedVariant && /* @__PURE__ */ jsxRuntime.jsxs(
359
- "div",
360
- {
361
- "data-slot": "expanded-title",
362
- className: cn("flex flex-1 flex-col justify-end", "gap-0.5 px-4 pb-4"),
363
- children: [
364
- /* @__PURE__ */ jsxRuntime.jsx(
365
- "span",
366
- {
367
- "data-testid": "appbar-title",
368
- className: cn("min-w-0", appBarTitleVariants({ variant })),
369
- children: title
370
- }
371
- ),
372
- subtitle != null && /* @__PURE__ */ jsxRuntime.jsx(
373
- "span",
374
- {
375
- "data-testid": "appbar-subtitle",
376
- className: cn("min-w-0", appBarSubtitleVariants({ variant })),
377
- children: subtitle
378
- }
379
- )
380
- ]
381
- }
382
- )
378
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-slot": "top-row", className: cn(appBarTopRowVariants({ variant })), children: [
379
+ navigationIcon != null && /* @__PURE__ */ jsxRuntime.jsx("div", { "data-slot": "navigation", className: cn(appBarLeadingVariants()), children: navigationIcon }),
380
+ !isExpandedVariant && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn(appBarHeadlineBlockVariants({ variant })), children: [
381
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "data-testid": "appbar-title", className: cn(appBarTitleVariants({ variant })), children: title }),
382
+ hasSubtitle && /* @__PURE__ */ jsxRuntime.jsx(
383
+ "span",
384
+ {
385
+ "data-testid": "appbar-subtitle",
386
+ className: cn(appBarSubtitleVariants({ variant })),
387
+ children: subtitle
388
+ }
389
+ )
390
+ ] }),
391
+ actions != null && /* @__PURE__ */ jsxRuntime.jsx("div", { "data-slot": "actions", className: cn(appBarTrailingVariants()), children: actions })
392
+ ] }),
393
+ isExpandedVariant && /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-slot": "expanded-title", className: cn(appBarExpandedTitleVariants()), children: [
394
+ /* @__PURE__ */ jsxRuntime.jsx(
395
+ "span",
396
+ {
397
+ "data-testid": "appbar-title",
398
+ className: cn("min-w-0", appBarTitleVariants({ variant })),
399
+ children: title
400
+ }
401
+ ),
402
+ hasSubtitle && /* @__PURE__ */ jsxRuntime.jsx(
403
+ "span",
404
+ {
405
+ "data-testid": "appbar-subtitle",
406
+ className: cn("min-w-0", appBarSubtitleVariants({ variant })),
407
+ children: subtitle
408
+ }
409
+ )
410
+ ] })
383
411
  ]
384
412
  }
385
413
  );
@@ -11132,7 +11160,7 @@ var BottomSheetModalPanel = ({
11132
11160
  "aria-modal": "true",
11133
11161
  className: cn(className, getAnimationClassName?.(animationState)),
11134
11162
  "data-animation-state": animationState,
11135
- "data-dragging": isDragging || void 0,
11163
+ "data-dragging": isDragging ? "" : void 0,
11136
11164
  style: panelStyle,
11137
11165
  onTransitionEnd,
11138
11166
  children
@@ -11269,7 +11297,7 @@ var BottomSheetHeadless = React.forwardRef(
11269
11297
  ref,
11270
11298
  className: cn(className, getAnimationClassName?.(animationState)),
11271
11299
  "data-animation-state": animationState,
11272
- "data-dragging": isDragging || void 0,
11300
+ "data-dragging": isDragging ? "" : void 0,
11273
11301
  style: panelStyle,
11274
11302
  onTransitionEnd: handleTransitionEnd,
11275
11303
  children
@@ -11284,7 +11312,6 @@ var bottomSheetAnimationVariants = classVarianceAuthority.cva("", {
11284
11312
  variants: {
11285
11313
  animationState: {
11286
11314
  // entering: initial mount frame — sheet starts below viewport (translateY(100%))
11287
- // The CSS is handled inside animate-md-slide-in-bottom keyframes
11288
11315
  entering: ["opacity-0"],
11289
11316
  // visible: entry animation active — animate-md-slide-in-bottom runs once
11290
11317
  visible: ["animate-md-slide-in-bottom"],
@@ -11301,52 +11328,38 @@ var bottomSheetAnimationVariants = classVarianceAuthority.cva("", {
11301
11328
  var bottomSheetVariants = classVarianceAuthority.cva(
11302
11329
  [
11303
11330
  // Position: fixed to bottom edge, full width
11304
- "fixed",
11305
- "bottom-0",
11306
- "left-0",
11307
- "right-0",
11331
+ "fixed bottom-0 left-0 right-0",
11308
11332
  // Surface token
11309
11333
  "bg-surface-container-low",
11310
11334
  // Elevation level 1 per MD3 spec
11311
11335
  "shadow-elevation-1",
11312
- // Shape: extra-large top corners (28dp), bottom corners are 0 (attached to screen edge)
11336
+ // Shape: extra-large top corners (28dp), square bottom (screen-attached)
11337
+ // NOTE: measurement-derived value from MD3 spec; permitted exception per component-variants rule
11313
11338
  "rounded-t-xl",
11314
11339
  // Layout
11315
- "flex",
11316
- "flex-col",
11317
- // Max width constraint (full width up to 640dp)
11340
+ "flex flex-col",
11341
+ // Width constraint (full width up to 640dp)
11318
11342
  "mx-auto",
11319
11343
  // NOTE: measurement-derived value from MD3 spec; permitted exception
11320
11344
  "w-[640px] max-w-full",
11321
- // Clip content during height transitions (sheet shrinks/grows from bottom edge)
11345
+ // Clip content during height transitions
11322
11346
  "overflow-hidden",
11323
- // Transition: height for snap spring (MD3 spec sheet "resizes" between heights)
11324
- // Standard personality, default speed tier, spatial: no overshoot.
11325
- // During drag, data-[dragging=true]:transition-none suppresses this so the
11326
- // sheet height follows the pointer 1:1 without transition lag.
11327
- // After drag release, the spring transition animates height to the new snap position.
11347
+ // Snap spring: spatial property (height), standard personality, default tier
11328
11348
  "transition-[height]",
11329
11349
  "duration-spring-standard-default-spatial",
11330
11350
  "ease-spring-standard-default-spatial",
11331
- "data-[dragging=true]:transition-none",
11351
+ // Suppress spring while dragging so the sheet follows the pointer 1:1
11352
+ "data-[dragging]:transition-none",
11332
11353
  "will-change-[height]",
11333
- // Responsive layout: when viewport > 640dp, apply wider top margin per MD3 spec.
11334
- // The sheet remains bottom-anchored at all sizes. Side centering is handled by
11335
- // mx-auto + max-w-[640px] — at 752dp viewport this naturally produces 56dp side
11336
- // margins on each side (exactly matching MD3 measurements).
11337
- // Top margin is expressed as max-height so the sheet cannot overlap the top edge:
11338
- // - Default: 72dp top margin (max-h-[calc(100vh-72px)])
11339
- // - Wide viewport (> 640dp): 56dp top margin (sm:max-h-[calc(100vh-56px)])
11354
+ // Responsive layout: top margin expressed as max-height
11340
11355
  // NOTE: measurement-derived values from MD3 spec; permitted exception
11341
11356
  "max-h-[calc(100vh-72px)]",
11342
- "sm:max-h-[calc(100vh-56px)]",
11343
- // Top corners rounded at wide layout (sheet floats away from screen edge)
11344
- "rounded-t-xl"
11357
+ "sm:max-h-[calc(100vh-56px)]"
11345
11358
  ],
11346
11359
  {
11347
11360
  variants: {
11348
11361
  variant: {
11349
- // Modal: above scrim (z-50)
11362
+ // Modal: rendered above the scrim (z-50)
11350
11363
  modal: "z-50",
11351
11364
  // Standard: sits above normal content but below overlays
11352
11365
  standard: "z-10"
@@ -11358,62 +11371,96 @@ var bottomSheetVariants = classVarianceAuthority.cva(
11358
11371
  }
11359
11372
  );
11360
11373
  var bottomSheetScrimVariants = classVarianceAuthority.cva([
11361
- "fixed",
11362
- "inset-0",
11363
- "z-40",
11364
- "bg-scrim",
11365
- "opacity-32",
11366
- "transition-opacity",
11367
- "duration-short4",
11368
- "ease-standard"
11374
+ "fixed inset-0 z-40",
11375
+ "bg-scrim opacity-32",
11376
+ // Screen-level effects transition (scrim enters/exits the screen, not an on-screen state change)
11377
+ "transition-opacity duration-short4 ease-standard"
11369
11378
  ]);
11370
11379
  var bottomSheetHandleWrapperVariants = classVarianceAuthority.cva([
11371
- // Center the handle pill horizontally
11372
- "flex",
11373
- "items-center",
11374
- "justify-center",
11375
- // Top/bottom padding creates the 48dp touch target area
11376
- // 22dp top + 4dp handle + 22dp bottom ≈ 48dp interaction zone (per MD3 measurements)
11380
+ // Center the pill horizontally; provide positioning context for overlays
11381
+ "relative flex items-center justify-center w-full",
11382
+ // 48dp touch target (22dp top + 4dp pill + 22dp bottom)
11377
11383
  // NOTE: measurement-derived value from MD3 spec; permitted exception
11378
11384
  "py-[22px]",
11379
- // Full width so the touch target spans the sheet
11380
- "w-full",
11381
- // Focus ring styling for keyboard/switch navigation
11382
- // MD3 spec: focus indicator color = secondary, thickness = 3dp, offset = 2dp
11385
+ // Suppress browser default focus outline — the focus-ring overlay slot handles it
11383
11386
  "focus-visible:outline-none",
11384
- "focus-visible:ring-3",
11385
- "focus-visible:ring-secondary",
11386
- "focus-visible:ring-offset-2",
11387
- "focus-visible:rounded-sm",
11388
- // Cursor affordance
11387
+ // Cursor affordance for drag interaction
11389
11388
  "cursor-ns-resize"
11390
11389
  ]);
11390
+ var bottomSheetHandleStateLayerVariants = classVarianceAuthority.cva([
11391
+ // Overlay positioned centrally — sits behind the pill
11392
+ "absolute pointer-events-none",
11393
+ // Pill-shaped to complement the handle's form
11394
+ "rounded-full",
11395
+ // Sized wider than the pill to provide a visible state layer halo
11396
+ // 48dp wide × 16dp tall — centred by the flex wrapper
11397
+ "w-12 h-4",
11398
+ // State-layer color (same role as the pill)
11399
+ "bg-on-surface-variant",
11400
+ // Effects transition — opacity must NOT overshoot
11401
+ "transition-opacity duration-spring-standard-fast-effects ease-spring-standard-fast-effects",
11402
+ // Opacity at rest
11403
+ "opacity-0",
11404
+ // Hover: 8%
11405
+ "group-data-[hovered]/handle:opacity-8",
11406
+ // Focus-visible: 10%
11407
+ "group-data-[focus-visible]/handle:opacity-10",
11408
+ // Pressed: 10% (doubled selector wins over hover at same cascade position)
11409
+ "group-data-[pressed]/handle:group-data-[pressed]/handle:opacity-10",
11410
+ // Dragging: 16% (MD3 dragged state — highest on-screen opacity)
11411
+ // Doubled selector wins over hover + pressed
11412
+ "group-data-[dragging]/handle:group-data-[dragging]/handle:opacity-16"
11413
+ ]);
11414
+ var bottomSheetHandleFocusRingVariants = classVarianceAuthority.cva([
11415
+ "absolute pointer-events-none",
11416
+ "rounded-full",
11417
+ // Sized to sit around the state layer halo
11418
+ "w-14 h-5",
11419
+ // MD3 focus indicator: secondary color, 2dp weight
11420
+ "outline outline-2 outline-offset-0 outline-secondary",
11421
+ // Effects transition — opacity change must NOT overshoot
11422
+ "transition-opacity duration-spring-standard-fast-effects ease-spring-standard-fast-effects",
11423
+ // Hidden at rest; shown on keyboard/programmatic focus
11424
+ "opacity-0",
11425
+ "group-data-[focus-visible]/handle:opacity-100"
11426
+ ]);
11391
11427
  var bottomSheetHandlePillVariants = classVarianceAuthority.cva([
11428
+ "relative z-10 pointer-events-none",
11392
11429
  "bg-on-surface-variant",
11393
- "opacity-40",
11394
11430
  "rounded-full",
11395
11431
  // Dimensions: 32dp × 4dp per MD3 spec (measurement-derived; permitted exception)
11396
11432
  "w-8",
11397
- // 32dp = 2rem = w-8
11398
- "h-1",
11399
- // 4dp = 0.25rem = h-1
11400
- // Pill itself is decorative; the wrapper handles interaction
11401
- "pointer-events-none"
11433
+ // 32dp = 2rem
11434
+ "h-1"
11435
+ // 4dp = 0.25rem
11402
11436
  ]);
11403
11437
  function BottomSheetHandle({
11404
11438
  className,
11405
11439
  "aria-label": ariaLabelOverride
11406
11440
  }) {
11407
11441
  const { handleProps, isDragging } = useBottomSheetContext();
11408
- return /* @__PURE__ */ jsxRuntime.jsx(
11442
+ const { isHovered, hoverProps } = reactAria.useHover({});
11443
+ const { isFocusVisible, focusProps } = reactAria.useFocusRing();
11444
+ const mergedHandleProps = reactAria.mergeProps(handleProps, hoverProps, focusProps);
11445
+ return /* @__PURE__ */ jsxRuntime.jsxs(
11409
11446
  "div",
11410
11447
  {
11411
- ...handleProps,
11448
+ ...mergedHandleProps,
11412
11449
  ...ariaLabelOverride !== void 0 ? { "aria-label": ariaLabelOverride } : {},
11413
- className: cn(bottomSheetHandleWrapperVariants(), className),
11450
+ className: cn(bottomSheetHandleWrapperVariants(), "group/handle", className),
11414
11451
  "data-testid": "bottom-sheet-handle",
11415
- "data-dragging": isDragging || void 0,
11416
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: bottomSheetHandlePillVariants(), "aria-hidden": "true" })
11452
+ ...getInteractionDataAttributes({
11453
+ isHovered,
11454
+ isFocusVisible,
11455
+ // Treat active drag as pressed — drives the state layer to 10% opacity
11456
+ isPressed: isDragging
11457
+ }),
11458
+ "data-dragging": isDragging ? "" : void 0,
11459
+ children: [
11460
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(bottomSheetHandleStateLayerVariants()), "aria-hidden": "true" }),
11461
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(bottomSheetHandleFocusRingVariants()), "aria-hidden": "true" }),
11462
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(bottomSheetHandlePillVariants()), "aria-hidden": "true" })
11463
+ ]
11417
11464
  }
11418
11465
  );
11419
11466
  }