neba 1.11.0 → 1.12.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.
Files changed (161) hide show
  1. package/README.md +4 -4
  2. package/dist/components/animate-appear/AnimateAppear.d.ts +2 -13
  3. package/dist/components/animate-appear/AnimateAppear.js +1 -1
  4. package/dist/components/animate-blink/AnimateBlink.d.ts +2 -2
  5. package/dist/components/animate-blink/AnimateBlink.js +1 -1
  6. package/dist/components/animate-counter/AnimateCounter.d.ts +45 -0
  7. package/dist/components/animate-counter/AnimateCounter.js +1 -0
  8. package/dist/components/animate-counter/index.d.ts +1 -0
  9. package/dist/components/animate-counter/index.js +1 -0
  10. package/dist/components/animate-fade/AnimateFade.d.ts +2 -2
  11. package/dist/components/animate-fade/AnimateFade.js +1 -1
  12. package/dist/components/animate-float/AnimateFloat.d.ts +35 -0
  13. package/dist/components/animate-float/AnimateFloat.js +1 -0
  14. package/dist/components/animate-float/index.d.ts +1 -0
  15. package/dist/components/animate-float/index.js +1 -0
  16. package/dist/components/animate-grow/AnimateGrow.d.ts +2 -2
  17. package/dist/components/animate-grow/AnimateGrow.js +1 -1
  18. package/dist/components/animate-reveal/AnimateReveal.d.ts +41 -0
  19. package/dist/components/animate-reveal/AnimateReveal.js +1 -0
  20. package/dist/components/animate-reveal/index.d.ts +1 -0
  21. package/dist/components/animate-reveal/index.js +1 -0
  22. package/dist/components/animate-rotate/AnimateRotate.d.ts +2 -2
  23. package/dist/components/animate-rotate/AnimateRotate.js +1 -1
  24. package/dist/components/animate-scramble/AnimateScramble.d.ts +48 -0
  25. package/dist/components/animate-scramble/AnimateScramble.js +1 -0
  26. package/dist/components/animate-scramble/index.d.ts +1 -0
  27. package/dist/components/animate-scramble/index.js +1 -0
  28. package/dist/components/animate-shake/AnimateShake.d.ts +39 -0
  29. package/dist/components/animate-shake/AnimateShake.js +1 -0
  30. package/dist/components/animate-shake/index.d.ts +1 -0
  31. package/dist/components/animate-shake/index.js +1 -0
  32. package/dist/components/animate-slide/AnimateSlide.d.ts +2 -2
  33. package/dist/components/animate-slide/AnimateSlide.js +1 -1
  34. package/dist/components/animate-split/AnimateSplit.d.ts +61 -0
  35. package/dist/components/animate-split/AnimateSplit.js +1 -0
  36. package/dist/components/animate-split/index.d.ts +1 -0
  37. package/dist/components/animate-split/index.js +1 -0
  38. package/dist/components/animate-typing/AnimateTyping.js +1 -1
  39. package/dist/components/animate-zoom/AnimateZoom.d.ts +2 -2
  40. package/dist/components/animate-zoom/AnimateZoom.js +1 -1
  41. package/dist/components/avatar/Avatar.js +1 -1
  42. package/dist/components/bar-chart/BarChart.js +1 -1
  43. package/dist/components/checkbox/Checkbox.js +1 -1
  44. package/dist/components/combobox/Combobox.js +1 -1
  45. package/dist/components/command-palette/CommandPalette.js +1 -1
  46. package/dist/components/container/Container.d.ts +12 -4
  47. package/dist/components/container/Container.js +1 -1
  48. package/dist/components/data-table/DataTable.js +1 -1
  49. package/dist/components/dialog/Dialog.js +1 -1
  50. package/dist/components/drawer/Drawer.js +1 -1
  51. package/dist/components/flex/Flex.d.ts +93 -0
  52. package/dist/components/flex/Flex.js +1 -0
  53. package/dist/components/flex/index.d.ts +2 -0
  54. package/dist/components/flex/index.js +1 -0
  55. package/dist/components/floating-action-button/FloatingActionButton.js +1 -1
  56. package/dist/components/footer/Footer.d.ts +4 -3
  57. package/dist/components/footer/Footer.js +1 -1
  58. package/dist/components/gallery/Gallery.d.ts +150 -0
  59. package/dist/components/gallery/Gallery.js +1 -0
  60. package/dist/components/gallery/GalleryViewer.d.ts +30 -0
  61. package/dist/components/gallery/GalleryViewer.js +1 -0
  62. package/dist/components/gallery/index.d.ts +2 -0
  63. package/dist/components/gallery/index.js +1 -0
  64. package/dist/components/gauge-chart/GaugeChart.js +1 -1
  65. package/dist/components/grid/Grid.js +1 -1
  66. package/dist/components/grid-container/GridContainer.js +1 -1
  67. package/dist/components/header/Header.d.ts +5 -4
  68. package/dist/components/header/Header.js +1 -1
  69. package/dist/components/heatmap-chart/HeatmapChart.js +1 -1
  70. package/dist/components/hover-card/HoverCard.js +1 -1
  71. package/dist/components/how-to-steps/HowToSteps.js +1 -1
  72. package/dist/components/image/Image.d.ts +123 -3
  73. package/dist/components/image/Image.js +1 -1
  74. package/dist/components/image/index.d.ts +1 -1
  75. package/dist/components/menu/Menu.js +1 -1
  76. package/dist/components/navigation-menu/NavigationMenu.js +1 -1
  77. package/dist/components/overlay/Overlay.js +1 -1
  78. package/dist/components/pie-chart/PieChart.js +1 -1
  79. package/dist/components/popover/Popover.js +1 -1
  80. package/dist/components/radio-group/RadioGroup.js +1 -1
  81. package/dist/components/rating/Rating.js +1 -1
  82. package/dist/components/scatter-chart/ScatterChart.js +1 -1
  83. package/dist/components/scroll-zone/ScrollZone.d.ts +9 -4
  84. package/dist/components/scroll-zone/ScrollZone.js +1 -1
  85. package/dist/components/select/Select.js +1 -1
  86. package/dist/components/show/Show.d.ts +60 -0
  87. package/dist/components/show/Show.js +1 -0
  88. package/dist/components/show/index.d.ts +2 -0
  89. package/dist/components/show/index.js +1 -0
  90. package/dist/components/sidebar/Sidebar.js +1 -1
  91. package/dist/components/spoiler/Spoiler.js +1 -1
  92. package/dist/components/stack/Stack.d.ts +126 -0
  93. package/dist/components/stack/Stack.js +1 -0
  94. package/dist/components/stack/index.d.ts +1 -0
  95. package/dist/components/stack/index.js +1 -0
  96. package/dist/components/statistic/Statistic.d.ts +8 -3
  97. package/dist/components/tabs/Tabs.d.ts +28 -0
  98. package/dist/components/tabs/Tabs.js +1 -1
  99. package/dist/components/timeline-chart/TimelineChart.js +1 -1
  100. package/dist/components/toast/Toast.js +1 -1
  101. package/dist/components/toggle/Toggle.js +1 -1
  102. package/dist/components/tooltip/Tooltip.js +1 -1
  103. package/dist/components/tour/Tour.js +1 -1
  104. package/dist/components/transfer/Transfer.js +1 -1
  105. package/dist/components/tree-view/TreeView.js +1 -1
  106. package/dist/hooks/index.d.ts +1 -1
  107. package/dist/hooks/index.js +1 -1
  108. package/dist/hooks/useMediaQuery.d.ts +35 -1
  109. package/dist/hooks/useMediaQuery.js +1 -1
  110. package/dist/index.d.ts +10 -1
  111. package/dist/index.js +1 -1
  112. package/dist/internal/animate.d.ts +65 -2
  113. package/dist/internal/animate.js +1 -1
  114. package/dist/internal/chart-frame.d.ts +32 -0
  115. package/dist/internal/chart-frame.js +1 -1
  116. package/dist/internal/chart-line.js +1 -1
  117. package/dist/internal/chart.d.ts +16 -0
  118. package/dist/internal/chart.js +1 -1
  119. package/dist/internal/grid.d.ts +13 -27
  120. package/dist/internal/grid.js +1 -1
  121. package/dist/internal/i18n.d.ts +20 -0
  122. package/dist/internal/i18n.js +1 -1
  123. package/dist/internal/media.d.ts +9 -2
  124. package/dist/internal/media.js +1 -1
  125. package/dist/internal/page-layout.d.ts +11 -9
  126. package/dist/internal/page-layout.js +1 -1
  127. package/dist/internal/picker.js +1 -1
  128. package/dist/internal/responsive.d.ts +97 -0
  129. package/dist/internal/responsive.js +1 -0
  130. package/dist/internal/styles.d.ts +68 -0
  131. package/dist/internal/styles.js +1 -1
  132. package/dist/internal/text.d.ts +42 -0
  133. package/dist/internal/text.js +1 -0
  134. package/dist/locales/ar.js +1 -1
  135. package/dist/locales/de.js +1 -1
  136. package/dist/locales/es.js +1 -1
  137. package/dist/locales/fr.js +1 -1
  138. package/dist/locales/hi.js +1 -1
  139. package/dist/locales/id.js +1 -1
  140. package/dist/locales/it.js +1 -1
  141. package/dist/locales/ja.js +1 -1
  142. package/dist/locales/ko.js +1 -1
  143. package/dist/locales/nl.js +1 -1
  144. package/dist/locales/pl.js +1 -1
  145. package/dist/locales/pt.js +1 -1
  146. package/dist/locales/ru.js +1 -1
  147. package/dist/locales/th.js +1 -1
  148. package/dist/locales/tr.js +1 -1
  149. package/dist/locales/vi.js +1 -1
  150. package/dist/locales/zh-hans.js +1 -1
  151. package/dist/locales/zh-hant.js +1 -1
  152. package/dist/styles.css +1 -1
  153. package/dist/tailwind.css +280 -24
  154. package/dist/types.d.ts +119 -6
  155. package/package.json +18 -17
  156. package/dist/components/avatar-group/AvatarGroup.d.ts +0 -47
  157. package/dist/components/avatar-group/AvatarGroup.js +0 -1
  158. package/dist/components/avatar-group/index.d.ts +0 -2
  159. package/dist/components/avatar-group/index.js +0 -1
  160. package/dist/internal/avatar-group.d.ts +0 -22
  161. package/dist/internal/avatar-group.js +0 -1
package/dist/tailwind.css CHANGED
@@ -1391,7 +1391,57 @@
1391
1391
  }
1392
1392
 
1393
1393
  /* ---------------------------------------------------------------------------
1394
- * Grid
1394
+ * Breakpoints
1395
+ *
1396
+ * The five widths the whole library branches on, taken from Tailwind's theme
1397
+ * rather than written out — `theme()` resolves at build time, in whichever
1398
+ * Tailwind build compiles this file, so a consumer who redeclares
1399
+ * `--breakpoint-md` in their own `@theme` moves the media queries below, the
1400
+ * `md:` variants the components spell out, and the JavaScript that reads the
1401
+ * tokens here, all at once. That is the whole mechanism: a breakpoint is a
1402
+ * build-time decision, because `@media` cannot read a custom property and no
1403
+ * amount of runtime configuration can change that.
1404
+ *
1405
+ * `xs` has no token because its floor is `0`: it is the value with no media
1406
+ * query around it, and there is nothing for JavaScript to look up.
1407
+ *
1408
+ * Tailwind does not emit `--breakpoint-*` into `:root` — they are compile-time
1409
+ * values to it — so the four are copied under a name of the library's own,
1410
+ * which is what `internal/media.ts` reads to build its `matchMedia` queries.
1411
+ * Declared once, on `:root` alone: a width is not a theme-dependent value, so
1412
+ * the rule about repeating derived tokens per theme root does not apply.
1413
+ * ------------------------------------------------------------------------- */
1414
+
1415
+ :root {
1416
+ --neba-breakpoint-sm: theme(--breakpoint-sm);
1417
+ --neba-breakpoint-md: theme(--breakpoint-md);
1418
+ --neba-breakpoint-lg: theme(--breakpoint-lg);
1419
+ --neba-breakpoint-xl: theme(--breakpoint-xl);
1420
+ }
1421
+
1422
+ /* ---------------------------------------------------------------------------
1423
+ * Responsive slots
1424
+ *
1425
+ * Every value in the library that changes at a breakpoint resolves here: one
1426
+ * cascade per slot name, each breakpoint falling back through the ones below
1427
+ * it, so `span={{ md: 6 }}` sets one custom property and not five. A media
1428
+ * query can change an inherited custom property without React hearing about
1429
+ * it, which is the whole reason this is CSS — the alternative is re-rendering
1430
+ * the tree at every breakpoint to say the same thing.
1431
+ *
1432
+ * Nothing here *applies* a value except the grid's own width arithmetic. The
1433
+ * components read the resolved slot through an ordinary Tailwind utility —
1434
+ * `gap-x-(--n-gap-x)`, `max-w-(--n-max-w)` — so a caller's own class fights
1435
+ * that utility on equal terms, exactly as it did before the value was
1436
+ * responsive. A real declaration in this file would win that fight, because
1437
+ * this sheet is loaded after the utilities.
1438
+ *
1439
+ * The chains get long at `xl`; that is the cost of keeping the DOM lean, and it
1440
+ * is paid once here rather than on every grid item in a consumer's app. It is
1441
+ * also why the list of responsive axes is deliberately short — four media
1442
+ * blocks per slot cannot be generated, so each new one is a decision.
1443
+ *
1444
+ * The grid ------------------------------------------------------------------
1395
1445
  *
1396
1446
  * A column is `(100% + gap) * span / columns - gap` wide, and an offset shifts
1397
1447
  * it by `(100% + gap) * offset / columns`. Both are exact rather than
@@ -1409,21 +1459,45 @@
1409
1459
  * `min(span, columns)` is the clamp: a `span={99}` fills its row instead of
1410
1460
  * overflowing the page, which is what the caller meant.
1411
1461
  *
1412
- * Every slot cascades upward — each breakpoint falls back through the ones
1413
- * below it — so `span={{ md: 6 }}` sets one custom property and not five. The
1414
- * chains get long at `xl`; that is the cost of keeping the DOM lean, and it is
1415
- * paid once here rather than on every grid item in a consumer's app.
1416
- *
1417
- * The widths are Tailwind's own breakpoints, so a Neba grid and a `md:` utility
1418
- * change at the same moment.
1462
+ * The widths are read from Tailwind's theme, so a Neba grid and a `md:` utility
1463
+ * change at the same moment — including when a consumer has moved them.
1419
1464
  * ------------------------------------------------------------------------- */
1420
1465
 
1421
- .neba-grid {
1422
- --n-cols: var(--n-cols-xs, 12);
1466
+ /* The two gutters, shared: a GridContainer and a Flex ask the same question
1467
+ and `spacing` has to mean the same number on both.
1468
+
1469
+ `--n-cols` is shared with a Gallery one rule down for that same reason: how
1470
+ many columns a row is cut into is one question, and a second cascade for it
1471
+ would be four more media blocks saying the thing this one already says. The
1472
+ `12` fallback is a grid's; a Gallery always emits its own `xs`, so it never
1473
+ reaches. */
1474
+ .neba-grid,
1475
+ .neba-flex {
1423
1476
  --n-gap-x: var(--n-gap-x-xs, 0px);
1424
1477
  --n-gap-y: var(--n-gap-y-xs, 0px);
1425
1478
  }
1426
1479
 
1480
+ .neba-grid,
1481
+ .neba-gallery {
1482
+ --n-cols: var(--n-cols-xs, 12);
1483
+ }
1484
+
1485
+ /* A Flex, whose other responsive value is which way the row runs. `row` is the
1486
+ fallback so a `<Flex>` with no direction at all is still a row. */
1487
+ .neba-flex {
1488
+ --n-flex-dir: var(--n-flex-dir-xs, row);
1489
+ }
1490
+
1491
+ /* A Container, a Header and a Footer, whose one responsive value is how wide
1492
+ the content on them is allowed to get. The slot is applied by a
1493
+ `max-w-(--n-max-w)` utility on the component rather than by a rule here, so
1494
+ the component's measure stays exactly as overridable by a caller's own
1495
+ `max-w-*` as it was when it was a `max-w-[48rem]` — a custom property is
1496
+ invisible to that fight, and a real class in this file would have won it. */
1497
+ .neba-measure {
1498
+ --n-max-w: var(--n-max-w-xs, none);
1499
+ }
1500
+
1427
1501
  .neba-grid-item {
1428
1502
  --n-span: var(--n-span-xs, var(--n-cols, 12));
1429
1503
  --n-offset: var(--n-offset-xs, 0);
@@ -1442,39 +1516,81 @@
1442
1516
  margin-inline-start: calc(var(--n-track) * min(var(--n-offset), var(--n-cols, 12)));
1443
1517
  }
1444
1518
 
1445
- @media (width >= 40rem) {
1446
- .neba-grid {
1447
- --n-cols: var(--n-cols-sm, var(--n-cols-xs, 12));
1519
+ @media (width >= theme(--breakpoint-sm)) {
1520
+ .neba-measure {
1521
+ --n-max-w: var(--n-max-w-sm, var(--n-max-w-xs, none));
1522
+ }
1523
+
1524
+ .neba-grid,
1525
+ .neba-flex {
1448
1526
  --n-gap-x: var(--n-gap-x-sm, var(--n-gap-x-xs, 0px));
1449
1527
  --n-gap-y: var(--n-gap-y-sm, var(--n-gap-y-xs, 0px));
1450
1528
  }
1451
1529
 
1530
+ .neba-grid,
1531
+ .neba-gallery {
1532
+ --n-cols: var(--n-cols-sm, var(--n-cols-xs, 12));
1533
+ }
1534
+
1535
+ .neba-flex {
1536
+ --n-flex-dir: var(--n-flex-dir-sm, var(--n-flex-dir-xs, row));
1537
+ }
1538
+
1452
1539
  .neba-grid-item {
1453
1540
  --n-span: var(--n-span-sm, var(--n-span-xs, var(--n-cols, 12)));
1454
1541
  --n-offset: var(--n-offset-sm, var(--n-offset-xs, 0));
1455
1542
  }
1456
1543
  }
1457
1544
 
1458
- @media (width >= 48rem) {
1459
- .neba-grid {
1460
- --n-cols: var(--n-cols-md, var(--n-cols-sm, var(--n-cols-xs, 12)));
1545
+ @media (width >= theme(--breakpoint-md)) {
1546
+ .neba-measure {
1547
+ --n-max-w: var(--n-max-w-md, var(--n-max-w-sm, var(--n-max-w-xs, none)));
1548
+ }
1549
+
1550
+ .neba-grid,
1551
+ .neba-flex {
1461
1552
  --n-gap-x: var(--n-gap-x-md, var(--n-gap-x-sm, var(--n-gap-x-xs, 0px)));
1462
1553
  --n-gap-y: var(--n-gap-y-md, var(--n-gap-y-sm, var(--n-gap-y-xs, 0px)));
1463
1554
  }
1464
1555
 
1556
+ .neba-grid,
1557
+ .neba-gallery {
1558
+ --n-cols: var(--n-cols-md, var(--n-cols-sm, var(--n-cols-xs, 12)));
1559
+ }
1560
+
1561
+ .neba-flex {
1562
+ --n-flex-dir: var(--n-flex-dir-md, var(--n-flex-dir-sm, var(--n-flex-dir-xs, row)));
1563
+ }
1564
+
1465
1565
  .neba-grid-item {
1466
1566
  --n-span: var(--n-span-md, var(--n-span-sm, var(--n-span-xs, var(--n-cols, 12))));
1467
1567
  --n-offset: var(--n-offset-md, var(--n-offset-sm, var(--n-offset-xs, 0)));
1468
1568
  }
1469
1569
  }
1470
1570
 
1471
- @media (width >= 64rem) {
1472
- .neba-grid {
1473
- --n-cols: var(--n-cols-lg, var(--n-cols-md, var(--n-cols-sm, var(--n-cols-xs, 12))));
1571
+ @media (width >= theme(--breakpoint-lg)) {
1572
+ .neba-measure {
1573
+ --n-max-w: var(--n-max-w-lg, var(--n-max-w-md, var(--n-max-w-sm, var(--n-max-w-xs, none))));
1574
+ }
1575
+
1576
+ .neba-grid,
1577
+ .neba-flex {
1474
1578
  --n-gap-x: var(--n-gap-x-lg, var(--n-gap-x-md, var(--n-gap-x-sm, var(--n-gap-x-xs, 0px))));
1475
1579
  --n-gap-y: var(--n-gap-y-lg, var(--n-gap-y-md, var(--n-gap-y-sm, var(--n-gap-y-xs, 0px))));
1476
1580
  }
1477
1581
 
1582
+ .neba-grid,
1583
+ .neba-gallery {
1584
+ --n-cols: var(--n-cols-lg, var(--n-cols-md, var(--n-cols-sm, var(--n-cols-xs, 12))));
1585
+ }
1586
+
1587
+ .neba-flex {
1588
+ --n-flex-dir: var(
1589
+ --n-flex-dir-lg,
1590
+ var(--n-flex-dir-md, var(--n-flex-dir-sm, var(--n-flex-dir-xs, row)))
1591
+ );
1592
+ }
1593
+
1478
1594
  .neba-grid-item {
1479
1595
  --n-span: var(
1480
1596
  --n-span-lg,
@@ -1484,12 +1600,16 @@
1484
1600
  }
1485
1601
  }
1486
1602
 
1487
- @media (width >= 80rem) {
1488
- .neba-grid {
1489
- --n-cols: var(
1490
- --n-cols-xl,
1491
- var(--n-cols-lg, var(--n-cols-md, var(--n-cols-sm, var(--n-cols-xs, 12))))
1603
+ @media (width >= theme(--breakpoint-xl)) {
1604
+ .neba-measure {
1605
+ --n-max-w: var(
1606
+ --n-max-w-xl,
1607
+ var(--n-max-w-lg, var(--n-max-w-md, var(--n-max-w-sm, var(--n-max-w-xs, none))))
1492
1608
  );
1609
+ }
1610
+
1611
+ .neba-grid,
1612
+ .neba-flex {
1493
1613
  --n-gap-x: var(
1494
1614
  --n-gap-x-xl,
1495
1615
  var(--n-gap-x-lg, var(--n-gap-x-md, var(--n-gap-x-sm, var(--n-gap-x-xs, 0px))))
@@ -1500,6 +1620,21 @@
1500
1620
  );
1501
1621
  }
1502
1622
 
1623
+ .neba-grid,
1624
+ .neba-gallery {
1625
+ --n-cols: var(
1626
+ --n-cols-xl,
1627
+ var(--n-cols-lg, var(--n-cols-md, var(--n-cols-sm, var(--n-cols-xs, 12))))
1628
+ );
1629
+ }
1630
+
1631
+ .neba-flex {
1632
+ --n-flex-dir: var(
1633
+ --n-flex-dir-xl,
1634
+ var(--n-flex-dir-lg, var(--n-flex-dir-md, var(--n-flex-dir-sm, var(--n-flex-dir-xs, row))))
1635
+ );
1636
+ }
1637
+
1503
1638
  .neba-grid-item {
1504
1639
  --n-span: var(
1505
1640
  --n-span-xl,
@@ -1512,6 +1647,31 @@
1512
1647
  }
1513
1648
  }
1514
1649
 
1650
+ /* ---------------------------------------------------------------------------
1651
+ * A justified gallery's last row
1652
+ *
1653
+ * `layout="justified"` is a wrapping flex row where every tile grows in
1654
+ * proportion to its own aspect ratio, which is what makes each row fill the
1655
+ * width exactly and every tile in it come out the same height. The arithmetic
1656
+ * is the browser's; nothing is measured and nothing re-renders on a resize.
1657
+ *
1658
+ * The one thing it gets wrong on its own is the last row, which has fewer tiles
1659
+ * to share the same width and blows them up to fill it — five photographs
1660
+ * ending in one the size of a poster. A generated box with an absurd
1661
+ * `flex-grow` takes that leftover instead, so the last row keeps the height the
1662
+ * rows above it have.
1663
+ *
1664
+ * It is `::after` rather than an element because the gallery is a `<ul>`, and a
1665
+ * spacer `<span>` between the `<li>`s is markup that says a list item is
1666
+ * something it is not.
1667
+ * ------------------------------------------------------------------------- */
1668
+
1669
+ .neba-gallery-justified::after {
1670
+ content: '';
1671
+ flex-grow: 1000000000;
1672
+ min-inline-size: 0;
1673
+ }
1674
+
1515
1675
  /* ---------------------------------------------------------------------------
1516
1676
  * Tree guides
1517
1677
  *
@@ -1858,6 +2018,27 @@
1858
2018
  animation-play-state: var(--n-anim-state, running);
1859
2019
  }
1860
2020
 
2021
+ /*
2022
+ * The same eleven effects, driven by where the element is rather than by when.
2023
+ *
2024
+ * Two declarations and nothing else, which is the whole reason this is a prop
2025
+ * and not a component: every keyframe already runs from a start state to the
2026
+ * element's own, so pointing it at a scroll timeline instead of the clock needs
2027
+ * no new keyframe and no JavaScript. A `timeline` of `view` fills the slot; the
2028
+ * default `auto` is the clock, and the three declarations above go on meaning
2029
+ * what they meant.
2030
+ *
2031
+ * Guarded, because a browser without `animation-timeline` must not be handed an
2032
+ * animation with no clock to run on: unsupported, the whole block is skipped and
2033
+ * the effect plays once on the timeline it always had. Degraded, never blank.
2034
+ */
2035
+ @supports (animation-timeline: view()) {
2036
+ .neba-anim {
2037
+ animation-timeline: var(--n-anim-timeline, auto);
2038
+ animation-range: var(--n-anim-range, normal);
2039
+ }
2040
+ }
2041
+
1861
2042
  @keyframes neba-anim-fade {
1862
2043
  from {
1863
2044
  opacity: var(--n-anim-opacity, 0);
@@ -1936,6 +2117,81 @@
1936
2117
  animation-name: neba-anim-blink;
1937
2118
  }
1938
2119
 
2120
+ /*
2121
+ * Reveal: an edge travelling across something that is already there.
2122
+ *
2123
+ * `clip-path` rather than a mask, and rather than a wrapper with `overflow`.
2124
+ * A mask would need a gradient whose direction is a fourth thing to keep in
2125
+ * step with the side; a wrapper would be an element in the layout that the
2126
+ * effect had no other reason to add. An inset is one property, it is animatable,
2127
+ * and `inset(0)` — the resting state — is exactly "nothing clipped".
2128
+ *
2129
+ * The opacity slot defaults to `1` here rather than to `0`: a reveal is not a
2130
+ * fade, and something wiped in while also fading has had the wipe wasted on it.
2131
+ * Pass `fade` and it is both.
2132
+ */
2133
+ @keyframes neba-anim-reveal {
2134
+ from {
2135
+ opacity: var(--n-anim-opacity, 1);
2136
+ clip-path: var(--n-anim-clip, inset(0 100% 0 0));
2137
+ }
2138
+ to {
2139
+ opacity: 1;
2140
+ clip-path: inset(0);
2141
+ }
2142
+ }
2143
+
2144
+ .neba-anim-reveal {
2145
+ animation-name: neba-anim-reveal;
2146
+ }
2147
+
2148
+ /* Float: a slow drift with nowhere to get to. Asymmetric on purpose — it starts
2149
+ where the element sits, so an element whose animation is interrupted is left
2150
+ where the layout put it rather than half a centimetre above it. */
2151
+ @keyframes neba-anim-float {
2152
+ from {
2153
+ translate: 0 0;
2154
+ }
2155
+ to {
2156
+ translate: var(--n-anim-x, 0px) var(--n-anim-y, -0.5rem);
2157
+ }
2158
+ }
2159
+
2160
+ .neba-anim-float {
2161
+ animation-name: neba-anim-float;
2162
+ }
2163
+
2164
+ /*
2165
+ * Shake: the one effect in the set that says *no*.
2166
+ *
2167
+ * Five stops rather than two, and the last two are smaller than the first two,
2168
+ * because a shake that ends as hard as it began reads as a loop that was cut
2169
+ * off. It starts and ends at zero for the reason `blink` is symmetric: an
2170
+ * interrupted run must not leave the element off its own mark.
2171
+ */
2172
+ @keyframes neba-anim-shake {
2173
+ 0%,
2174
+ 100% {
2175
+ translate: 0 0;
2176
+ }
2177
+ 20% {
2178
+ translate: calc(var(--n-anim-x, 6px) * -1) 0;
2179
+ }
2180
+ 40% {
2181
+ translate: var(--n-anim-x, 6px) 0;
2182
+ }
2183
+ 60% {
2184
+ translate: calc(var(--n-anim-x, 6px) * -0.55) 0;
2185
+ }
2186
+ 80% {
2187
+ translate: calc(var(--n-anim-x, 6px) * 0.55) 0;
2188
+ }
2189
+ }
2190
+
2191
+ .neba-anim-shake {
2192
+ animation-name: neba-anim-shake;
2193
+ }
2194
+
1939
2195
  /*
1940
2196
  * Lighting: a light travelling around the outside of something.
1941
2197
  *
package/dist/types.d.ts CHANGED
@@ -101,6 +101,23 @@ export type NebaDateGranularity = 'day' | 'month' | 'year';
101
101
  * `xs` is 0: it is the value with no media query around it.
102
102
  */
103
103
  export type NebaBreakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
104
+ /**
105
+ * How wide content is allowed to get.
106
+ *
107
+ * A step of the measure ladder — `xs` 30rem, `sm` 40rem, `md` 48rem, `lg`
108
+ * 64rem, `xl` 80rem — `none` for no limit, or any CSS length the caller writes:
109
+ * `'60ch'`, `'min(90vw, 72rem)'`, or a number, which is pixels.
110
+ *
111
+ * The ladder shares its five names with `NebaBreakpoint` and four of its five
112
+ * values, which is deliberate: `maxWidth="md"` holds the content to the width
113
+ * at which a `md:` variant starts, so the two ladders line up where a layout
114
+ * needs them to. `xs` is the one step that is not a breakpoint floor, because a
115
+ * measure of zero is not a thing.
116
+ *
117
+ * The `string & {}` is what keeps the five names in a caller's autocomplete: a
118
+ * bare `| string` would swallow the literals and offer nothing.
119
+ */
120
+ export type NebaMeasure = NebaSize | 'none' | number | (string & {});
104
121
  /**
105
122
  * A value that may differ per breakpoint.
106
123
  *
@@ -145,19 +162,43 @@ export type NebaVariant = 'solid' | 'outline' | 'text';
145
162
  */
146
163
  export type NebaElevation = 0 | 1 | 2 | 3;
147
164
  /**
148
- * The six effects the `Animate*` components are built out of, and the six
149
- * values the `transition` prop takes.
165
+ * The seven effects the `transition` prop takes, which is the **entrance**
166
+ * vocabulary: everything a component can be given to arrive on.
150
167
  *
151
168
  * They are named after what a reader sees rather than after the CSS property
152
169
  * underneath: `zoom` and `grow` are both a change of scale, and they are two
153
170
  * words because they are two *gestures* — one arrives from the middle of where
154
171
  * it will end up, the other unfolds from nothing.
155
172
  *
156
- * Everything past these six is a component rather than a value. Typing, a
157
- * marquee and a headline reel need to know what their children *are*, and a
158
- * prop that only sets class names cannot.
173
+ * Two `Animate*` components are deliberately **not** in it, and not only
174
+ * because neither is an entrance. This union is two `Record`s in
175
+ * `internal/animate.tsx`, an object literal cannot be tree-shaken per key, and
176
+ * those two tables are a fixed cost on every component that offers a
177
+ * `transition` — so `AnimateFloat`'s drift, which has no arrival in it, and
178
+ * `AnimateShake`'s reply to a failed form would be paid for by every Chip on
179
+ * every page that draws one. They bring their own keyframe instead.
180
+ *
181
+ * Everything past a keyframe is a component rather than a value. A typewriter,
182
+ * a marquee, a headline reel, a split line, a settling scramble and a counted
183
+ * number all need to know what their children *are*, and a prop that only sets
184
+ * class names cannot.
185
+ */
186
+ export type NebaAnimation = 'fade' | 'grow' | 'slide' | 'zoom' | 'rotate' | 'blink' | 'reveal';
187
+ /**
188
+ * What drives an animation's progress.
189
+ *
190
+ * - `time` — the clock. The default, and what every other prop on an animation
191
+ * is written against.
192
+ * - `view` — how far the element has travelled through the viewport. The
193
+ * animation has no duration of its own any more: scrolling forwards plays it
194
+ * forwards, scrolling back plays it back, and stopping halfway holds it
195
+ * halfway. `duration`, `delay`, `repeat` and every `trigger` are ignored,
196
+ * because the scroll position *is* the trigger.
197
+ *
198
+ * Where `animation-timeline` is not supported the effect falls back to `time`
199
+ * and runs on mount, which is the same content delivered a plainer way.
159
200
  */
160
- export type NebaAnimation = 'fade' | 'grow' | 'slide' | 'zoom' | 'rotate' | 'blink';
201
+ export type NebaAnimateTimeline = 'time' | 'view';
161
202
  /**
162
203
  * What makes an animation run.
163
204
  *
@@ -187,6 +228,43 @@ export type NebaAnimateRepeat = number | 'infinite';
187
228
  * whose type is `string` invites `'0.4s'`, and then two components in the same
188
229
  * screen are written in two units.
189
230
  */
231
+ /**
232
+ * The three props that turn one effect on a box into the same effect on the
233
+ * things inside it, one after another.
234
+ *
235
+ * They are their own bundle rather than part of `NebaAnimateProps` because only
236
+ * some of the eleven can take them. An effect that already has to understand its
237
+ * children — a marquee that duplicates them, a headline that swaps between them,
238
+ * a typewriter that counts characters — has no second axis to spend on order.
239
+ *
240
+ * `delay` and `duration` stay what they were: what *every* child gets. These
241
+ * three are the difference between them.
242
+ */
243
+ export interface NebaStaggerProps {
244
+ /**
245
+ * How long after one child the next one starts, in milliseconds.
246
+ *
247
+ * `0` — the default everywhere but `AnimateAppear` — animates the box itself
248
+ * and leaves the children alone, which is the plain single-element effect.
249
+ * Anything above it moves the animation onto each child in turn.
250
+ * @default 0
251
+ */
252
+ stagger?: number;
253
+ /**
254
+ * How much longer each successive child takes than the one before it, in
255
+ * milliseconds. Negative to speed them up down the list.
256
+ *
257
+ * A duration never goes below zero however far the list runs.
258
+ * @default 0
259
+ */
260
+ durationStep?: number;
261
+ /**
262
+ * Runs the children from the last to the first. Only the *order* reverses —
263
+ * each child still plays forwards.
264
+ * @default false
265
+ */
266
+ reverse?: boolean;
267
+ }
190
268
  export interface NebaAnimateProps {
191
269
  /** How long one run takes, in milliseconds. */
192
270
  duration?: number;
@@ -217,6 +295,35 @@ export interface NebaAnimateProps {
217
295
  */
218
296
  threshold?: number;
219
297
  }
298
+ /**
299
+ * The two props that swap the clock for the scrollbar.
300
+ *
301
+ * Their own bundle rather than part of `NebaAnimateProps`, for the reason
302
+ * `NebaStaggerProps` is: only the effects whose motion is one `@keyframes` on
303
+ * the element itself can be pointed at another timeline. A marquee that measures
304
+ * its own track, a typewriter counting characters and a light travelling round a
305
+ * pseudo-element are motion written in other places, and offering them a prop
306
+ * that quietly did nothing would be worse than not offering it.
307
+ */
308
+ export interface NebaTimelineProps {
309
+ /**
310
+ * What drives the progress: the clock, or how far the element has travelled
311
+ * through the viewport.
312
+ *
313
+ * `view` costs `duration`, `delay`, `repeat` and `trigger` — a scroll-driven
314
+ * animation has no time in it, and the scroll position is what starts it. It
315
+ * falls back to `time` where the browser has no `animation-timeline`, which
316
+ * plays the effect once on mount.
317
+ * @default 'time'
318
+ */
319
+ timeline?: NebaAnimateTimeline;
320
+ /**
321
+ * The scroll range a `view` timeline is mapped over, written the way CSS
322
+ * `animation-range` is: `entry 0% cover 45%` is the default, which plays the
323
+ * effect out over the first part of the element's journey up the screen.
324
+ */
325
+ range?: string;
326
+ }
220
327
  /** The options a `transition` prop takes when a bare effect name is not enough. */
221
328
  export interface NebaTransitionOptions {
222
329
  /** Which effect. */
@@ -239,6 +346,12 @@ export interface NebaTransitionOptions {
239
346
  scale?: number;
240
347
  /** How far a `rotate` turns from, in degrees. */
241
348
  angle?: number;
349
+ /** Which edge a `reveal` is wiped from. @default 'left' */
350
+ side?: NebaSide;
351
+ /** What drives the progress. @default 'time' */
352
+ timeline?: NebaAnimateTimeline;
353
+ /** The scroll range a `view` timeline is mapped over, as CSS writes it. */
354
+ range?: string;
242
355
  }
243
356
  /**
244
357
  * An entrance animation on a component that displays something.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neba",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "description": "A comprehensive React UI component library — buttons, fields, menus, dialogs, tables and much more — accessible and themeable, sharing one prop vocabulary and a translucent acrylic design language. ESM only, types included, dark mode built in.",
5
5
  "type": "module",
6
6
  "types": "dist/index.d.ts",
@@ -18,7 +18,8 @@
18
18
  "url": "https://github.com/jooy2/neba/issues"
19
19
  },
20
20
  "scripts": {
21
- "build": "npm run format:fix && tsc --project tsconfig.prod.json && node scripts/annotate-pure.mjs && npm run minify && node scripts/build-styles.mjs",
21
+ "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
22
+ "build": "npm run clean && npm run format:fix && tsc --project tsconfig.prod.json && node scripts/annotate-pure.mjs && npm run minify && node scripts/build-styles.mjs",
22
23
  "lint": "eslint . --report-unused-disable-directives",
23
24
  "lint:fix": "eslint . --report-unused-disable-directives --fix",
24
25
  "minify": "terser-glob 'dist/**/*.js' --config-file terser.config.json",
@@ -29,9 +30,9 @@
29
30
  "format:fix": "prettier . --write",
30
31
  "docs:changelog": "node scripts/copy-changelog.mjs",
31
32
  "docs:dev": "npm run docs:changelog && vitepress dev docs",
32
- "docs:build": "npm run docs:changelog && vitepress build docs",
33
+ "docs:build": "npm run docs:changelog && node --max-old-space-size=8192 ./node_modules/vitepress/bin/vitepress.js build docs",
33
34
  "docs:serve": "npm run docs:build && vitepress serve docs",
34
- "test": "vitest run",
35
+ "test": "node scripts/run-tests.mjs",
35
36
  "test:watch": "vitest",
36
37
  "typecheck": "tsc --project tsconfig.prod.json --noEmit && tsc --project test/tsconfig.json && tsc --project docs/tsconfig.json"
37
38
  },
@@ -138,21 +139,21 @@
138
139
  "devDependencies": {
139
140
  "@eslint/js": "^9.39.1",
140
141
  "@tailwindcss/postcss": "^4.3.3",
141
- "@types/node": "^26.4.0",
142
+ "@types/node": "^26.4.1",
142
143
  "@types/react": "^19.2.18",
143
- "@types/react-dom": "^19.2.5",
144
- "@typescript-eslint/parser": "^8.68.0",
144
+ "@types/react-dom": "^19.2.7",
145
+ "@typescript-eslint/parser": "^8.69.0",
145
146
  "@vitejs/plugin-react": "^6.1.1",
146
- "@vitest/browser": "^4.1.11",
147
- "@vitest/browser-playwright": "^4.1.11",
147
+ "@vitest/browser": "^5.0.0",
148
+ "@vitest/browser-playwright": "^5.0.0",
148
149
  "eslint": "^9.39.1",
149
150
  "eslint-config-prettier": "^10.1.8",
150
151
  "eslint-plugin-n": "^18.3.0",
151
152
  "eslint-plugin-react-hooks": "^7.1.1",
152
- "globals": "^17.11.0",
153
+ "globals": "^17.12.0",
153
154
  "jiti": "^2.7.0",
154
- "playwright": "^1.62.1",
155
- "postcss": "^8.5.26",
155
+ "playwright": "^1.63.0",
156
+ "postcss": "^8.5.28",
156
157
  "prettier": "^3.9.6",
157
158
  "prettier-plugin-tailwindcss": "^0.8.1",
158
159
  "react": "^19.2.8",
@@ -161,16 +162,16 @@
161
162
  "terser-glob": "^1.2.1",
162
163
  "tsx": "^4.23.13",
163
164
  "typescript": "^6.0.3",
164
- "typescript-eslint": "^8.68.0",
165
+ "typescript-eslint": "^8.69.0",
165
166
  "vite": "^8.2.2",
166
167
  "vitepress": "^1.6.4",
167
168
  "vitepress-i18n": "^1.4.0",
168
- "vitepress-sidebar": "^1.39.0",
169
- "vitest": "^4.1.11",
170
- "vitest-browser-react": "^2.2.0"
169
+ "vitepress-sidebar": "^1.40.0",
170
+ "vitest": "^5.0.0",
171
+ "vitest-browser-react": "^2.3.0"
171
172
  },
172
173
  "dependencies": {
173
- "@base-ui/react": "^1.7.0",
174
+ "@base-ui/react": "^1.8.0",
174
175
  "highlight.js": "^11.12.0"
175
176
  },
176
177
  "peerDependencies": {
@@ -1,47 +0,0 @@
1
- import * as React from 'react';
2
- import { type AvatarShape } from '../avatar/Avatar.js';
3
- import type { NebaColor, NebaElevation, NebaSize, NebaVariant } from '../../types.js';
4
- export interface AvatarGroupProps extends Omit<React.ComponentPropsWithoutRef<'div'>, 'color'> {
5
- /**
6
- * How many avatars are drawn before the rest become a count. Left out, every
7
- * one of them is drawn.
8
- */
9
- max?: number;
10
- /**
11
- * How many there are altogether, when the group was handed only the first
12
- * few. Without it the count is worked out from the children, which is right
13
- * only when all of them were passed.
14
- */
15
- total?: number;
16
- /**
17
- * How far each avatar sits under the one before it — a CSS length, or a
18
- * number of pixels. Left out it is a fraction of `size`, which keeps the
19
- * overlap looking the same at every step.
20
- */
21
- overlap?: number | string;
22
- /** Passed to every avatar in the group. @default 'md' */
23
- size?: NebaSize;
24
- /** Passed to every avatar in the group. @default 'circle' */
25
- shape?: AvatarShape;
26
- /** Passed to every avatar in the group. @default 'text' */
27
- variant?: NebaVariant;
28
- /** Passed to every avatar in the group. @default 'primary' */
29
- color?: NebaColor;
30
- /** Passed to every avatar in the group. @default 0 */
31
- elevation?: NebaElevation;
32
- /** The avatars. */
33
- children?: React.ReactNode;
34
- }
35
- /**
36
- * A stack of avatars, overlapping, with the ones that did not fit as a count.
37
- *
38
- * `size`, `shape`, `variant`, `color` and `elevation` are set once here rather
39
- * than on every avatar — a stack whose fourth face is a size out is not a stack
40
- * — and an avatar's own prop still wins, which is what lets one of them be
41
- * marked out from the rest.
42
- *
43
- * The order is the DOM order, and the first avatar is on top: a stack read
44
- * left to right is read front to back, so the one the group is *about* comes
45
- * first rather than last.
46
- */
47
- export declare const AvatarGroup: React.ForwardRefExoticComponent<AvatarGroupProps & React.RefAttributes<HTMLDivElement>>;