@tinybigui/react 0.9.0 → 0.10.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.d.cts CHANGED
@@ -598,6 +598,9 @@ interface AppBarProps {
598
598
  * Renders a `<header role="banner">` and manages scroll elevation state.
599
599
  * Use this for full visual control when the styled `AppBar` is not sufficient.
600
600
  *
601
+ * Extends `React.HTMLAttributes<HTMLElement>` so all standard HTML attributes
602
+ * (including `data-*` attributes) are forwarded to the underlying `<header>`.
603
+ *
601
604
  * @example
602
605
  * ```tsx
603
606
  * <AppBarHeadless
@@ -609,11 +612,7 @@ interface AppBarProps {
609
612
  * </AppBarHeadless>
610
613
  * ```
611
614
  */
612
- interface AppBarHeadlessProps {
613
- /**
614
- * Additional CSS classes
615
- */
616
- className?: string;
615
+ interface AppBarHeadlessProps extends React__default.HTMLAttributes<HTMLElement> {
617
616
  /**
618
617
  * The content to render inside the header
619
618
  */
@@ -630,26 +629,41 @@ interface AppBarHeadlessProps {
630
629
  }
631
630
 
632
631
  /**
633
- * Material Design 3 Top App Bar Component
632
+ * Material Design 3 Top App Bar Component (M3 Expressive Flexible)
634
633
  *
635
634
  * Provides context and actions for the current screen. Supports four size variants,
636
- * a navigation icon slot, title, and trailing action icon slots. Implements
637
- * scroll-triggered elevation changes per MD3 specification.
635
+ * a navigation icon slot, title, optional subtitle, and trailing action icon slots.
636
+ * Implements scroll-triggered elevation changes per MD3 specification.
638
637
  *
639
638
  * **Architecture:**
640
- * - Layer 3 (this file): MD3 styled, CVA variants, layout composition
639
+ * - Layer 3 (this file): MD3 styled, CVA slot variants, layout composition
641
640
  * - Layer 2: `AppBarHeadless` — `<header role="banner">`, scroll state
642
641
  * - Layer 1: React Aria via `<IconButton>` in consumer slots
643
642
  *
643
+ * **Slot-based styling:**
644
+ * All layout and state styling follows the Variants vs States pattern:
645
+ * - `variant` prop drives design-time choices (height, type scale, alignment)
646
+ * - Scroll elevation state is emitted as `data-scrolled=""` on the root and
647
+ * consumed by `group-data-[scrolled]/appbar:*` selectors (presence-based)
648
+ * - Subtitle presence is emitted as `data-with-subtitle=""` on the root and
649
+ * used to grow medium/large bar heights (group-data-[with-subtitle]/appbar:*)
650
+ *
644
651
  * **Key Features:**
645
652
  * - 4 MD3 variants: small, center-aligned, medium, large
646
- * - Optional subtitle (per M3 Expressive spec) with per-variant typography
653
+ * - M3 Expressive flexible: medium and large grow vertically with a subtitle
654
+ * (136dp / 152dp respectively), per the M3 Expressive flexible spec
647
655
  * - Composable API: pass `<IconButton>` nodes into navigation and action slots
648
- * - Scroll elevation: flat (bg-surface) at rest → bg-surface-container + shadow-elevation-2 on scroll
656
+ * - Scroll elevation: bg-surface at rest → bg-surface-container + shadow-elevation-2
649
657
  * - Controlled and uncontrolled scroll state
658
+ * - MD3 motion: background-color + box-shadow use standard effects spring pair
650
659
  * - WCAG 2.1 AA: `role="banner"` landmark, keyboard accessible slots
651
660
  * - Dark mode via existing token system
652
661
  *
662
+ * **M3 Expressive Flexible subtitle type scales:**
663
+ * - small / center-aligned: label-medium, on-surface-variant
664
+ * - medium expanded: label-large, on-surface-variant
665
+ * - large expanded: title-medium, on-surface-variant
666
+ *
653
667
  * **MD3 Accessibility (m3.material.io/components/app-bars/accessibility):**
654
668
  * - Focus lands on the leading navigation button first (first interactive element in DOM)
655
669
  * - Tab navigates: leading icon → trailing action icons (left to right)
@@ -682,7 +696,7 @@ interface AppBarHeadlessProps {
682
696
  * onScrollStateChange={setIsScrolled}
683
697
  * />
684
698
  *
685
- * // Medium with expanded title and subtitle
699
+ * // Medium with expanded title and subtitle (grows to 136dp with subtitle)
686
700
  * <AppBar
687
701
  * variant="medium"
688
702
  * title="Article Title"
package/dist/index.d.ts CHANGED
@@ -598,6 +598,9 @@ interface AppBarProps {
598
598
  * Renders a `<header role="banner">` and manages scroll elevation state.
599
599
  * Use this for full visual control when the styled `AppBar` is not sufficient.
600
600
  *
601
+ * Extends `React.HTMLAttributes<HTMLElement>` so all standard HTML attributes
602
+ * (including `data-*` attributes) are forwarded to the underlying `<header>`.
603
+ *
601
604
  * @example
602
605
  * ```tsx
603
606
  * <AppBarHeadless
@@ -609,11 +612,7 @@ interface AppBarProps {
609
612
  * </AppBarHeadless>
610
613
  * ```
611
614
  */
612
- interface AppBarHeadlessProps {
613
- /**
614
- * Additional CSS classes
615
- */
616
- className?: string;
615
+ interface AppBarHeadlessProps extends React__default.HTMLAttributes<HTMLElement> {
617
616
  /**
618
617
  * The content to render inside the header
619
618
  */
@@ -630,26 +629,41 @@ interface AppBarHeadlessProps {
630
629
  }
631
630
 
632
631
  /**
633
- * Material Design 3 Top App Bar Component
632
+ * Material Design 3 Top App Bar Component (M3 Expressive Flexible)
634
633
  *
635
634
  * Provides context and actions for the current screen. Supports four size variants,
636
- * a navigation icon slot, title, and trailing action icon slots. Implements
637
- * scroll-triggered elevation changes per MD3 specification.
635
+ * a navigation icon slot, title, optional subtitle, and trailing action icon slots.
636
+ * Implements scroll-triggered elevation changes per MD3 specification.
638
637
  *
639
638
  * **Architecture:**
640
- * - Layer 3 (this file): MD3 styled, CVA variants, layout composition
639
+ * - Layer 3 (this file): MD3 styled, CVA slot variants, layout composition
641
640
  * - Layer 2: `AppBarHeadless` — `<header role="banner">`, scroll state
642
641
  * - Layer 1: React Aria via `<IconButton>` in consumer slots
643
642
  *
643
+ * **Slot-based styling:**
644
+ * All layout and state styling follows the Variants vs States pattern:
645
+ * - `variant` prop drives design-time choices (height, type scale, alignment)
646
+ * - Scroll elevation state is emitted as `data-scrolled=""` on the root and
647
+ * consumed by `group-data-[scrolled]/appbar:*` selectors (presence-based)
648
+ * - Subtitle presence is emitted as `data-with-subtitle=""` on the root and
649
+ * used to grow medium/large bar heights (group-data-[with-subtitle]/appbar:*)
650
+ *
644
651
  * **Key Features:**
645
652
  * - 4 MD3 variants: small, center-aligned, medium, large
646
- * - Optional subtitle (per M3 Expressive spec) with per-variant typography
653
+ * - M3 Expressive flexible: medium and large grow vertically with a subtitle
654
+ * (136dp / 152dp respectively), per the M3 Expressive flexible spec
647
655
  * - Composable API: pass `<IconButton>` nodes into navigation and action slots
648
- * - Scroll elevation: flat (bg-surface) at rest → bg-surface-container + shadow-elevation-2 on scroll
656
+ * - Scroll elevation: bg-surface at rest → bg-surface-container + shadow-elevation-2
649
657
  * - Controlled and uncontrolled scroll state
658
+ * - MD3 motion: background-color + box-shadow use standard effects spring pair
650
659
  * - WCAG 2.1 AA: `role="banner"` landmark, keyboard accessible slots
651
660
  * - Dark mode via existing token system
652
661
  *
662
+ * **M3 Expressive Flexible subtitle type scales:**
663
+ * - small / center-aligned: label-medium, on-surface-variant
664
+ * - medium expanded: label-large, on-surface-variant
665
+ * - large expanded: title-medium, on-surface-variant
666
+ *
653
667
  * **MD3 Accessibility (m3.material.io/components/app-bars/accessibility):**
654
668
  * - Focus lands on the leading navigation button first (first interactive element in DOM)
655
669
  * - Tab navigates: leading icon → trailing action icons (left to right)
@@ -682,7 +696,7 @@ interface AppBarHeadlessProps {
682
696
  * onScrollStateChange={setIsScrolled}
683
697
  * />
684
698
  *
685
- * // Medium with expanded title and subtitle
699
+ * // Medium with expanded title and subtitle (grows to 136dp with subtitle)
686
700
  * <AppBar
687
701
  * variant="medium"
688
702
  * title="Article Title"
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
+ ]
250
271
  }
251
272
  },
252
273
  defaultVariants: {
253
- variant: "small",
254
- scrolled: false
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: ""
301
+ }
302
+ },
303
+ defaultVariants: {
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
  );