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