@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/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.10.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,13 +149,13 @@ 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
 
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
+ ]
255
276
  }
256
277
  },
257
278
  defaultVariants: {
258
- variant: "small",
259
- scrolled: false
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: ""
306
+ }
307
+ },
308
+ defaultVariants: {
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
  );