concertina 0.12.0 → 0.13.1
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 +30 -1
- package/dist/accordion.cjs +1 -0
- package/dist/accordion.js +1 -1
- package/dist/{chunk-OGJMPKZX.js → chunk-6UMIJ4S7.js} +1 -0
- package/dist/index.cjs +70 -59
- package/dist/index.d.cts +30 -2
- package/dist/index.d.ts +30 -2
- package/dist/index.js +65 -56
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ It's not a state problem. It's a **structure problem.** The box changed size bec
|
|
|
26
26
|
|
|
27
27
|
Don't swap structures. Swap what's inside them.
|
|
28
28
|
|
|
29
|
-
Concertina gives you
|
|
29
|
+
Concertina gives you four high-level components: **Bellows**, **Hum**, **Overture**, and **Ensemble**. They handle the math so you can focus on the music. CSS is auto-injected on first render. No manual imports needed.
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
32
|
npm install concertina
|
|
@@ -225,6 +225,34 @@ Named after musical **vamping** — repeating a pattern while waiting for a cue.
|
|
|
225
225
|
|
|
226
226
|
---
|
|
227
227
|
|
|
228
|
+
## Overture: temporal stability for arbitrary content
|
|
229
|
+
|
|
230
|
+
A card, table, or page loads from an API. You want shimmer bones during loading, a smooth fade-out when data arrives, and the container must never collapse during the swap. You don't have a flat list — you have complex, nested JSX.
|
|
231
|
+
|
|
232
|
+
Overture composes `Vamp` (ambient loading context) + `Gigbag` (size ratchet) + `useWarmupExit` (exit transition) into a single wrapper. Write one JSX tree for both states. Nested `<Hum>` instances read loading state from the Vamp context automatically.
|
|
233
|
+
|
|
234
|
+
```tsx
|
|
235
|
+
import { Overture, Hum } from "concertina";
|
|
236
|
+
|
|
237
|
+
<Overture loading={isLoading} exitDuration={150}>
|
|
238
|
+
<h2><Hum className="text-xl font-bold">{user?.name}</Hum></h2>
|
|
239
|
+
<p><Hum className="text-sm text-stone">{user?.email}</Hum></p>
|
|
240
|
+
<Button><Hum>Edit Profile</Hum></Button>
|
|
241
|
+
</Overture>
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
During loading, every Hum renders a shimmer sized to its ghost children. When loading finishes, the shimmers fade out, real content appears, and the Gigbag ratchet prevents any height collapse.
|
|
245
|
+
|
|
246
|
+
#### Overture props
|
|
247
|
+
|
|
248
|
+
| Prop | Type | Default | Description |
|
|
249
|
+
|------|------|---------|-------------|
|
|
250
|
+
| `loading` | `boolean` | | Show shimmer (true) or content (false) |
|
|
251
|
+
| `exitDuration` | `number` | | Exit animation duration in ms (match `--concertina-close-duration`) |
|
|
252
|
+
| `as` | `ElementType` | `"div"` | HTML element to render |
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
228
256
|
## Ensemble: temporal stability for collections
|
|
229
257
|
|
|
230
258
|
A list loads from an API. You want shimmer rows while loading, then a smooth transition to real items, and the container must never collapse during the swap.
|
|
@@ -408,6 +436,7 @@ Scrolls an element to the top of its nearest scrollable ancestor. Only touches `
|
|
|
408
436
|
| Two variants swap in one slot | Bellows + Slot |
|
|
409
437
|
| Line of text loading from API | Hum |
|
|
410
438
|
| Many Hum instances share one loading state | Vamp + Hum |
|
|
439
|
+
| Card/table/page loading from API | Overture + Hum |
|
|
411
440
|
| List loading from API | Ensemble |
|
|
412
441
|
| Spinner replaced by loaded content | Gigbag + Warmup |
|
|
413
442
|
| Accordion/table shimmer rows | Stub data + WarmupLine (wrapper-once pattern) |
|
package/dist/accordion.cjs
CHANGED
package/dist/accordion.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -40,6 +40,7 @@ __export(index_exports, {
|
|
|
40
40
|
Header: () => Header,
|
|
41
41
|
Hum: () => Hum,
|
|
42
42
|
Item: () => Item2,
|
|
43
|
+
Overture: () => Overture,
|
|
43
44
|
Root: () => Root3,
|
|
44
45
|
Slot: () => Slot,
|
|
45
46
|
StableCollection: () => Ensemble,
|
|
@@ -1372,6 +1373,7 @@ function injectStyles() {
|
|
|
1372
1373
|
document.head.appendChild(style);
|
|
1373
1374
|
injected = true;
|
|
1374
1375
|
}
|
|
1376
|
+
injectStyles();
|
|
1375
1377
|
|
|
1376
1378
|
// src/accordion/content.tsx
|
|
1377
1379
|
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
@@ -1437,9 +1439,6 @@ var Slot = (0, import_react12.forwardRef)(
|
|
|
1437
1439
|
}
|
|
1438
1440
|
);
|
|
1439
1441
|
|
|
1440
|
-
// src/components/hum.tsx
|
|
1441
|
-
var import_react14 = require("react");
|
|
1442
|
-
|
|
1443
1442
|
// src/components/vamp.tsx
|
|
1444
1443
|
var import_react13 = require("react");
|
|
1445
1444
|
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
@@ -1453,34 +1452,31 @@ function Vamp({ loading, children }) {
|
|
|
1453
1452
|
|
|
1454
1453
|
// src/components/hum.tsx
|
|
1455
1454
|
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
const
|
|
1461
|
-
|
|
1462
|
-
const merged = className ? `concertina-warmup-line ${className}` : "concertina-warmup-line";
|
|
1463
|
-
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Tag, { ref, className: merged, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Tag, { inert: true, children }) });
|
|
1464
|
-
}
|
|
1465
|
-
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Tag, { ref, className, ...props, children });
|
|
1455
|
+
function Hum({ loading, as: Tag = "span", className, children, ...props }) {
|
|
1456
|
+
const vampLoading = useVamp();
|
|
1457
|
+
const isLoading = loading ?? vampLoading;
|
|
1458
|
+
if (isLoading) {
|
|
1459
|
+
const merged = className ? `concertina-warmup-line ${className}` : "concertina-warmup-line";
|
|
1460
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Tag, { className: merged, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Tag, { inert: true, children }) });
|
|
1466
1461
|
}
|
|
1467
|
-
);
|
|
1462
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Tag, { className, ...props, children });
|
|
1463
|
+
}
|
|
1468
1464
|
|
|
1469
|
-
// src/components/
|
|
1470
|
-
var
|
|
1465
|
+
// src/components/overture.tsx
|
|
1466
|
+
var import_react17 = require("react");
|
|
1471
1467
|
|
|
1472
1468
|
// src/components/gigbag.tsx
|
|
1473
|
-
var
|
|
1469
|
+
var import_react15 = require("react");
|
|
1474
1470
|
|
|
1475
1471
|
// src/primitives/use-stable-slot.ts
|
|
1476
|
-
var
|
|
1472
|
+
var import_react14 = require("react");
|
|
1477
1473
|
var RATCHET_FLOOR = -Infinity;
|
|
1478
1474
|
function useStableSlot(options = {}) {
|
|
1479
1475
|
const { axis = "both" } = options;
|
|
1480
|
-
const [style, setStyle] = (0,
|
|
1481
|
-
const maxRef = (0,
|
|
1482
|
-
const observerRef = (0,
|
|
1483
|
-
const ref = (0,
|
|
1476
|
+
const [style, setStyle] = (0, import_react14.useState)({});
|
|
1477
|
+
const maxRef = (0, import_react14.useRef)({ w: RATCHET_FLOOR, h: RATCHET_FLOOR });
|
|
1478
|
+
const observerRef = (0, import_react14.useRef)(null);
|
|
1479
|
+
const ref = (0, import_react14.useCallback)(
|
|
1484
1480
|
(el) => {
|
|
1485
1481
|
if (observerRef.current) {
|
|
1486
1482
|
observerRef.current.disconnect();
|
|
@@ -1528,9 +1524,9 @@ function useStableSlot(options = {}) {
|
|
|
1528
1524
|
|
|
1529
1525
|
// src/components/gigbag.tsx
|
|
1530
1526
|
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
1531
|
-
var Gigbag = (0,
|
|
1527
|
+
var Gigbag = (0, import_react15.forwardRef)(
|
|
1532
1528
|
function Gigbag2({ axis = "height", as: Tag = "div", className, style, children, ...props }, fwdRef) {
|
|
1533
|
-
(0,
|
|
1529
|
+
(0, import_react15.useInsertionEffect)(injectStyles, []);
|
|
1534
1530
|
const { ref: ratchetRef, style: ratchetStyle } = useStableSlot({ axis });
|
|
1535
1531
|
const merged = className ? `concertina-gigbag ${className}` : "concertina-gigbag";
|
|
1536
1532
|
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
@@ -1546,38 +1542,12 @@ var Gigbag = (0, import_react16.forwardRef)(
|
|
|
1546
1542
|
}
|
|
1547
1543
|
);
|
|
1548
1544
|
|
|
1549
|
-
// src/components/warmup.tsx
|
|
1550
|
-
var import_react17 = require("react");
|
|
1551
|
-
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
1552
|
-
var Warmup = (0, import_react17.forwardRef)(
|
|
1553
|
-
function Warmup2({ rows, columns, as: Tag = "div", className, children, ...props }, ref) {
|
|
1554
|
-
(0, import_react17.useInsertionEffect)(injectStyles, []);
|
|
1555
|
-
const merged = className ? `concertina-warmup ${className}` : "concertina-warmup";
|
|
1556
|
-
const count2 = columns ? rows * columns : rows;
|
|
1557
|
-
const cells = Array.from({ length: count2 }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "concertina-warmup-bone", children: [
|
|
1558
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "concertina-warmup-line" }),
|
|
1559
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "concertina-warmup-line" })
|
|
1560
|
-
] }, i));
|
|
1561
|
-
const gridStyle = columns ? { gridTemplateColumns: `repeat(${columns}, auto)`, gridTemplateAreas: `'${"chamber ".repeat(columns).trim()}'` } : { gridTemplateAreas: "'chamber'" };
|
|
1562
|
-
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
1563
|
-
Tag,
|
|
1564
|
-
{
|
|
1565
|
-
ref,
|
|
1566
|
-
className: merged,
|
|
1567
|
-
style: gridStyle,
|
|
1568
|
-
...props,
|
|
1569
|
-
children: cells
|
|
1570
|
-
}
|
|
1571
|
-
);
|
|
1572
|
-
}
|
|
1573
|
-
);
|
|
1574
|
-
|
|
1575
1545
|
// src/primitives/use-warmup-exit.ts
|
|
1576
|
-
var
|
|
1546
|
+
var import_react16 = require("react");
|
|
1577
1547
|
function useWarmupExit(loading, duration) {
|
|
1578
|
-
const [exiting, setExiting] = (0,
|
|
1579
|
-
const prevLoading = (0,
|
|
1580
|
-
(0,
|
|
1548
|
+
const [exiting, setExiting] = (0, import_react16.useState)(false);
|
|
1549
|
+
const prevLoading = (0, import_react16.useRef)(loading);
|
|
1550
|
+
(0, import_react16.useEffect)(() => {
|
|
1581
1551
|
if (prevLoading.current && !loading) {
|
|
1582
1552
|
setExiting(true);
|
|
1583
1553
|
const id = setTimeout(() => setExiting(false), duration);
|
|
@@ -1594,8 +1564,48 @@ function useWarmupExit(loading, duration) {
|
|
|
1594
1564
|
};
|
|
1595
1565
|
}
|
|
1596
1566
|
|
|
1567
|
+
// src/components/overture.tsx
|
|
1568
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
1569
|
+
var Overture = (0, import_react17.forwardRef)(
|
|
1570
|
+
function Overture2({ loading, exitDuration, as: Tag = "div", className, children, ...props }, ref) {
|
|
1571
|
+
(0, import_react17.useInsertionEffect)(injectStyles, []);
|
|
1572
|
+
const { showWarmup, exiting } = useWarmupExit(loading, exitDuration);
|
|
1573
|
+
const merged = exiting ? className ? `concertina-warmup-exiting ${className}` : "concertina-warmup-exiting" : className;
|
|
1574
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Gigbag, { ref, axis: "height", as: Tag, className: merged, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Vamp, { loading: showWarmup, children }) });
|
|
1575
|
+
}
|
|
1576
|
+
);
|
|
1577
|
+
|
|
1597
1578
|
// src/components/ensemble.tsx
|
|
1579
|
+
var import_react19 = require("react");
|
|
1580
|
+
|
|
1581
|
+
// src/components/warmup.tsx
|
|
1582
|
+
var import_react18 = require("react");
|
|
1598
1583
|
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
1584
|
+
var Warmup = (0, import_react18.forwardRef)(
|
|
1585
|
+
function Warmup2({ rows, columns, as: Tag = "div", className, children, ...props }, ref) {
|
|
1586
|
+
(0, import_react18.useInsertionEffect)(injectStyles, []);
|
|
1587
|
+
const merged = className ? `concertina-warmup ${className}` : "concertina-warmup";
|
|
1588
|
+
const count2 = columns ? rows * columns : rows;
|
|
1589
|
+
const cells = Array.from({ length: count2 }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "concertina-warmup-bone", children: [
|
|
1590
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "concertina-warmup-line" }),
|
|
1591
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "concertina-warmup-line" })
|
|
1592
|
+
] }, i));
|
|
1593
|
+
const gridStyle = columns ? { gridTemplateColumns: `repeat(${columns}, auto)`, gridTemplateAreas: `'${"chamber ".repeat(columns).trim()}'` } : { gridTemplateAreas: "'chamber'" };
|
|
1594
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
1595
|
+
Tag,
|
|
1596
|
+
{
|
|
1597
|
+
ref,
|
|
1598
|
+
className: merged,
|
|
1599
|
+
style: gridStyle,
|
|
1600
|
+
...props,
|
|
1601
|
+
children: cells
|
|
1602
|
+
}
|
|
1603
|
+
);
|
|
1604
|
+
}
|
|
1605
|
+
);
|
|
1606
|
+
|
|
1607
|
+
// src/components/ensemble.tsx
|
|
1608
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
1599
1609
|
function EnsembleInner({
|
|
1600
1610
|
items,
|
|
1601
1611
|
loading,
|
|
@@ -1609,18 +1619,18 @@ function EnsembleInner({
|
|
|
1609
1619
|
(0, import_react19.useInsertionEffect)(injectStyles, []);
|
|
1610
1620
|
const { showWarmup, exiting } = useWarmupExit(loading, exitDuration);
|
|
1611
1621
|
const warmupClass = exiting ? className ? `concertina-warmup-exiting ${className}` : "concertina-warmup-exiting" : className;
|
|
1612
|
-
return /* @__PURE__ */ (0,
|
|
1622
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Gigbag, { ref, axis: "height", as: Tag, ...props, children: showWarmup ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Warmup, { rows: stubCount, className: warmupClass }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Tag, { className, children: items.map(renderItem) }) });
|
|
1613
1623
|
}
|
|
1614
1624
|
var Ensemble = (0, import_react19.forwardRef)(EnsembleInner);
|
|
1615
1625
|
|
|
1616
1626
|
// src/components/warmup-line.tsx
|
|
1617
1627
|
var import_react20 = require("react");
|
|
1618
|
-
var
|
|
1628
|
+
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
1619
1629
|
var WarmupLine = (0, import_react20.forwardRef)(
|
|
1620
1630
|
function WarmupLine2({ as: Tag = "div", className, ...props }, ref) {
|
|
1621
1631
|
(0, import_react20.useInsertionEffect)(injectStyles, []);
|
|
1622
1632
|
const merged = className ? `concertina-warmup-line ${className}` : "concertina-warmup-line";
|
|
1623
|
-
return /* @__PURE__ */ (0,
|
|
1633
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Tag, { ref, className: merged, ...props });
|
|
1624
1634
|
}
|
|
1625
1635
|
);
|
|
1626
1636
|
|
|
@@ -1652,7 +1662,7 @@ function usePresence2(show) {
|
|
|
1652
1662
|
}
|
|
1653
1663
|
|
|
1654
1664
|
// src/components/glide.tsx
|
|
1655
|
-
var
|
|
1665
|
+
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
1656
1666
|
var Glide = (0, import_react22.forwardRef)(
|
|
1657
1667
|
function Glide2({ show, as: Tag = "div", className, children, ...props }, ref) {
|
|
1658
1668
|
(0, import_react22.useInsertionEffect)(injectStyles, []);
|
|
@@ -1660,7 +1670,7 @@ var Glide = (0, import_react22.forwardRef)(
|
|
|
1660
1670
|
if (!mounted) return null;
|
|
1661
1671
|
const phaseClass = phase === "entering" ? "concertina-glide-entering" : phase === "exiting" ? "concertina-glide-exiting" : "";
|
|
1662
1672
|
const merged = ["concertina-glide", phaseClass, className].filter(Boolean).join(" ");
|
|
1663
|
-
return /* @__PURE__ */ (0,
|
|
1673
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
1664
1674
|
Tag,
|
|
1665
1675
|
{
|
|
1666
1676
|
ref,
|
|
@@ -1757,6 +1767,7 @@ function useConcertina() {
|
|
|
1757
1767
|
Header,
|
|
1758
1768
|
Hum,
|
|
1759
1769
|
Item,
|
|
1770
|
+
Overture,
|
|
1760
1771
|
Root,
|
|
1761
1772
|
Slot,
|
|
1762
1773
|
StableCollection,
|
package/dist/index.d.cts
CHANGED
|
@@ -65,7 +65,7 @@ interface HumProps extends HTMLAttributes<HTMLElement> {
|
|
|
65
65
|
* nearest `<Vamp>` ancestor. This lets a single provider control
|
|
66
66
|
* shimmer state for an entire subtree.
|
|
67
67
|
*/
|
|
68
|
-
declare
|
|
68
|
+
declare function Hum({ loading, as: Tag, className, children, ...props }: HumProps): react_jsx_runtime.JSX.Element;
|
|
69
69
|
|
|
70
70
|
/** Context carrying the ambient loading state set by Vamp. */
|
|
71
71
|
declare const VampContext: react.Context<boolean>;
|
|
@@ -89,6 +89,34 @@ interface VampProps {
|
|
|
89
89
|
*/
|
|
90
90
|
declare function Vamp({ loading, children }: VampProps): react_jsx_runtime.JSX.Element;
|
|
91
91
|
|
|
92
|
+
interface OvertureProps extends HTMLAttributes<HTMLElement> {
|
|
93
|
+
/** Whether data is loading. Sets Vamp context for all nested Hum instances. */
|
|
94
|
+
loading: boolean;
|
|
95
|
+
/** Exit animation duration in ms. Must match CSS --concertina-close-duration. */
|
|
96
|
+
exitDuration: number;
|
|
97
|
+
/** HTML element to render. Default: "div". */
|
|
98
|
+
as?: ElementType;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Loading-aware subtree wrapper — the opening act before the real content.
|
|
102
|
+
*
|
|
103
|
+
* Composes three behaviors into one component:
|
|
104
|
+
* - **Vamp** context: every nested `<Hum>` reads loading state automatically.
|
|
105
|
+
* - **Gigbag** ratchet: container never shrinks during the shimmer-to-content swap.
|
|
106
|
+
* - **Exit transition**: applies `concertina-warmup-exiting` class during the
|
|
107
|
+
* fade-out so shimmer lines animate before real content mounts.
|
|
108
|
+
*
|
|
109
|
+
* Write one JSX tree for both states. Hum instances handle the visual toggle.
|
|
110
|
+
*
|
|
111
|
+
* ```tsx
|
|
112
|
+
* <Overture loading={isLoading} exitDuration={150}>
|
|
113
|
+
* <h2><Hum className="text-xl">{user?.name}</Hum></h2>
|
|
114
|
+
* <p><Hum className="text-sm">{user?.bio}</Hum></p>
|
|
115
|
+
* </Overture>
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
declare const Overture: react.ForwardRefExoticComponent<OvertureProps & react.RefAttributes<HTMLElement>>;
|
|
119
|
+
|
|
92
120
|
interface EnsembleProps<T> extends Omit<HTMLAttributes<HTMLElement>, "children"> {
|
|
93
121
|
/** Data items to render. */
|
|
94
122
|
items: T[];
|
|
@@ -316,4 +344,4 @@ declare function useWarmupExit(loading: boolean, duration: number): {
|
|
|
316
344
|
exiting: boolean;
|
|
317
345
|
};
|
|
318
346
|
|
|
319
|
-
export { type Axis, Bellows, type BellowsProps, Ensemble, type EnsembleProps, Gigbag, type GigbagProps, Glide, type GlideProps, Hum, type HumProps, type Phase, type Size, Slot, type SlotProps, Ensemble as StableCollection, type EnsembleProps as StableCollectionProps, Bellows as StableSlot, type BellowsProps as StableSlotProps, Hum as StableText, type HumProps as StableTextProps, type UsePresenceReturn, Vamp, VampContext, type VampProps, Warmup, WarmupLine, type WarmupLineProps, type WarmupProps, pinToScrollTop, usePresence, useScrollPin, useSize, useStableSlot, useTransitionLock, useVamp, useWarmupExit };
|
|
347
|
+
export { type Axis, Bellows, type BellowsProps, Ensemble, type EnsembleProps, Gigbag, type GigbagProps, Glide, type GlideProps, Hum, type HumProps, Overture, type OvertureProps, type Phase, type Size, Slot, type SlotProps, Ensemble as StableCollection, type EnsembleProps as StableCollectionProps, Bellows as StableSlot, type BellowsProps as StableSlotProps, Hum as StableText, type HumProps as StableTextProps, type UsePresenceReturn, Vamp, VampContext, type VampProps, Warmup, WarmupLine, type WarmupLineProps, type WarmupProps, pinToScrollTop, usePresence, useScrollPin, useSize, useStableSlot, useTransitionLock, useVamp, useWarmupExit };
|
package/dist/index.d.ts
CHANGED
|
@@ -65,7 +65,7 @@ interface HumProps extends HTMLAttributes<HTMLElement> {
|
|
|
65
65
|
* nearest `<Vamp>` ancestor. This lets a single provider control
|
|
66
66
|
* shimmer state for an entire subtree.
|
|
67
67
|
*/
|
|
68
|
-
declare
|
|
68
|
+
declare function Hum({ loading, as: Tag, className, children, ...props }: HumProps): react_jsx_runtime.JSX.Element;
|
|
69
69
|
|
|
70
70
|
/** Context carrying the ambient loading state set by Vamp. */
|
|
71
71
|
declare const VampContext: react.Context<boolean>;
|
|
@@ -89,6 +89,34 @@ interface VampProps {
|
|
|
89
89
|
*/
|
|
90
90
|
declare function Vamp({ loading, children }: VampProps): react_jsx_runtime.JSX.Element;
|
|
91
91
|
|
|
92
|
+
interface OvertureProps extends HTMLAttributes<HTMLElement> {
|
|
93
|
+
/** Whether data is loading. Sets Vamp context for all nested Hum instances. */
|
|
94
|
+
loading: boolean;
|
|
95
|
+
/** Exit animation duration in ms. Must match CSS --concertina-close-duration. */
|
|
96
|
+
exitDuration: number;
|
|
97
|
+
/** HTML element to render. Default: "div". */
|
|
98
|
+
as?: ElementType;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Loading-aware subtree wrapper — the opening act before the real content.
|
|
102
|
+
*
|
|
103
|
+
* Composes three behaviors into one component:
|
|
104
|
+
* - **Vamp** context: every nested `<Hum>` reads loading state automatically.
|
|
105
|
+
* - **Gigbag** ratchet: container never shrinks during the shimmer-to-content swap.
|
|
106
|
+
* - **Exit transition**: applies `concertina-warmup-exiting` class during the
|
|
107
|
+
* fade-out so shimmer lines animate before real content mounts.
|
|
108
|
+
*
|
|
109
|
+
* Write one JSX tree for both states. Hum instances handle the visual toggle.
|
|
110
|
+
*
|
|
111
|
+
* ```tsx
|
|
112
|
+
* <Overture loading={isLoading} exitDuration={150}>
|
|
113
|
+
* <h2><Hum className="text-xl">{user?.name}</Hum></h2>
|
|
114
|
+
* <p><Hum className="text-sm">{user?.bio}</Hum></p>
|
|
115
|
+
* </Overture>
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
declare const Overture: react.ForwardRefExoticComponent<OvertureProps & react.RefAttributes<HTMLElement>>;
|
|
119
|
+
|
|
92
120
|
interface EnsembleProps<T> extends Omit<HTMLAttributes<HTMLElement>, "children"> {
|
|
93
121
|
/** Data items to render. */
|
|
94
122
|
items: T[];
|
|
@@ -316,4 +344,4 @@ declare function useWarmupExit(loading: boolean, duration: number): {
|
|
|
316
344
|
exiting: boolean;
|
|
317
345
|
};
|
|
318
346
|
|
|
319
|
-
export { type Axis, Bellows, type BellowsProps, Ensemble, type EnsembleProps, Gigbag, type GigbagProps, Glide, type GlideProps, Hum, type HumProps, type Phase, type Size, Slot, type SlotProps, Ensemble as StableCollection, type EnsembleProps as StableCollectionProps, Bellows as StableSlot, type BellowsProps as StableSlotProps, Hum as StableText, type HumProps as StableTextProps, type UsePresenceReturn, Vamp, VampContext, type VampProps, Warmup, WarmupLine, type WarmupLineProps, type WarmupProps, pinToScrollTop, usePresence, useScrollPin, useSize, useStableSlot, useTransitionLock, useVamp, useWarmupExit };
|
|
347
|
+
export { type Axis, Bellows, type BellowsProps, Ensemble, type EnsembleProps, Gigbag, type GigbagProps, Glide, type GlideProps, Hum, type HumProps, Overture, type OvertureProps, type Phase, type Size, Slot, type SlotProps, Ensemble as StableCollection, type EnsembleProps as StableCollectionProps, Bellows as StableSlot, type BellowsProps as StableSlotProps, Hum as StableText, type HumProps as StableTextProps, type UsePresenceReturn, Vamp, VampContext, type VampProps, Warmup, WarmupLine, type WarmupLineProps, type WarmupProps, pinToScrollTop, usePresence, useScrollPin, useSize, useStableSlot, useTransitionLock, useVamp, useWarmupExit };
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
useExpanded,
|
|
14
14
|
useScrollPin,
|
|
15
15
|
useTransitionLock
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-6UMIJ4S7.js";
|
|
17
17
|
|
|
18
18
|
// src/components/bellows.tsx
|
|
19
19
|
import {
|
|
@@ -60,12 +60,6 @@ var Slot = forwardRef2(
|
|
|
60
60
|
}
|
|
61
61
|
);
|
|
62
62
|
|
|
63
|
-
// src/components/hum.tsx
|
|
64
|
-
import {
|
|
65
|
-
forwardRef as forwardRef3,
|
|
66
|
-
useInsertionEffect as useInsertionEffect3
|
|
67
|
-
} from "react";
|
|
68
|
-
|
|
69
63
|
// src/components/vamp.tsx
|
|
70
64
|
import { createContext as createContext2, useContext as useContext2 } from "react";
|
|
71
65
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
@@ -79,27 +73,24 @@ function Vamp({ loading, children }) {
|
|
|
79
73
|
|
|
80
74
|
// src/components/hum.tsx
|
|
81
75
|
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
const merged = className ? `concertina-warmup-line ${className}` : "concertina-warmup-line";
|
|
89
|
-
return /* @__PURE__ */ jsx4(Tag, { ref, className: merged, ...props, children: /* @__PURE__ */ jsx4(Tag, { inert: true, children }) });
|
|
90
|
-
}
|
|
91
|
-
return /* @__PURE__ */ jsx4(Tag, { ref, className, ...props, children });
|
|
76
|
+
function Hum({ loading, as: Tag = "span", className, children, ...props }) {
|
|
77
|
+
const vampLoading = useVamp();
|
|
78
|
+
const isLoading = loading ?? vampLoading;
|
|
79
|
+
if (isLoading) {
|
|
80
|
+
const merged = className ? `concertina-warmup-line ${className}` : "concertina-warmup-line";
|
|
81
|
+
return /* @__PURE__ */ jsx4(Tag, { className: merged, ...props, children: /* @__PURE__ */ jsx4(Tag, { inert: true, children }) });
|
|
92
82
|
}
|
|
93
|
-
);
|
|
83
|
+
return /* @__PURE__ */ jsx4(Tag, { className, ...props, children });
|
|
84
|
+
}
|
|
94
85
|
|
|
95
|
-
// src/components/
|
|
86
|
+
// src/components/overture.tsx
|
|
96
87
|
import {
|
|
97
|
-
forwardRef as
|
|
98
|
-
useInsertionEffect as
|
|
88
|
+
forwardRef as forwardRef4,
|
|
89
|
+
useInsertionEffect as useInsertionEffect4
|
|
99
90
|
} from "react";
|
|
100
91
|
|
|
101
92
|
// src/components/gigbag.tsx
|
|
102
|
-
import { forwardRef as
|
|
93
|
+
import { forwardRef as forwardRef3, useInsertionEffect as useInsertionEffect3 } from "react";
|
|
103
94
|
|
|
104
95
|
// src/primitives/use-stable-slot.ts
|
|
105
96
|
import { useState, useCallback, useRef } from "react";
|
|
@@ -157,9 +148,9 @@ function useStableSlot(options = {}) {
|
|
|
157
148
|
|
|
158
149
|
// src/components/gigbag.tsx
|
|
159
150
|
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
160
|
-
var Gigbag =
|
|
151
|
+
var Gigbag = forwardRef3(
|
|
161
152
|
function Gigbag2({ axis = "height", as: Tag = "div", className, style, children, ...props }, fwdRef) {
|
|
162
|
-
|
|
153
|
+
useInsertionEffect3(injectStyles, []);
|
|
163
154
|
const { ref: ratchetRef, style: ratchetStyle } = useStableSlot({ axis });
|
|
164
155
|
const merged = className ? `concertina-gigbag ${className}` : "concertina-gigbag";
|
|
165
156
|
return /* @__PURE__ */ jsx5(
|
|
@@ -175,32 +166,6 @@ var Gigbag = forwardRef4(
|
|
|
175
166
|
}
|
|
176
167
|
);
|
|
177
168
|
|
|
178
|
-
// src/components/warmup.tsx
|
|
179
|
-
import { forwardRef as forwardRef5, useInsertionEffect as useInsertionEffect5 } from "react";
|
|
180
|
-
import { jsx as jsx6, jsxs } from "react/jsx-runtime";
|
|
181
|
-
var Warmup = forwardRef5(
|
|
182
|
-
function Warmup2({ rows, columns, as: Tag = "div", className, children, ...props }, ref) {
|
|
183
|
-
useInsertionEffect5(injectStyles, []);
|
|
184
|
-
const merged = className ? `concertina-warmup ${className}` : "concertina-warmup";
|
|
185
|
-
const count = columns ? rows * columns : rows;
|
|
186
|
-
const cells = Array.from({ length: count }, (_, i) => /* @__PURE__ */ jsxs("div", { className: "concertina-warmup-bone", children: [
|
|
187
|
-
/* @__PURE__ */ jsx6("div", { className: "concertina-warmup-line" }),
|
|
188
|
-
/* @__PURE__ */ jsx6("div", { className: "concertina-warmup-line" })
|
|
189
|
-
] }, i));
|
|
190
|
-
const gridStyle = columns ? { gridTemplateColumns: `repeat(${columns}, auto)`, gridTemplateAreas: `'${"chamber ".repeat(columns).trim()}'` } : { gridTemplateAreas: "'chamber'" };
|
|
191
|
-
return /* @__PURE__ */ jsx6(
|
|
192
|
-
Tag,
|
|
193
|
-
{
|
|
194
|
-
ref,
|
|
195
|
-
className: merged,
|
|
196
|
-
style: gridStyle,
|
|
197
|
-
...props,
|
|
198
|
-
children: cells
|
|
199
|
-
}
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
);
|
|
203
|
-
|
|
204
169
|
// src/primitives/use-warmup-exit.ts
|
|
205
170
|
import { useState as useState2, useEffect, useRef as useRef2 } from "react";
|
|
206
171
|
function useWarmupExit(loading, duration) {
|
|
@@ -223,8 +188,51 @@ function useWarmupExit(loading, duration) {
|
|
|
223
188
|
};
|
|
224
189
|
}
|
|
225
190
|
|
|
191
|
+
// src/components/overture.tsx
|
|
192
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
193
|
+
var Overture = forwardRef4(
|
|
194
|
+
function Overture2({ loading, exitDuration, as: Tag = "div", className, children, ...props }, ref) {
|
|
195
|
+
useInsertionEffect4(injectStyles, []);
|
|
196
|
+
const { showWarmup, exiting } = useWarmupExit(loading, exitDuration);
|
|
197
|
+
const merged = exiting ? className ? `concertina-warmup-exiting ${className}` : "concertina-warmup-exiting" : className;
|
|
198
|
+
return /* @__PURE__ */ jsx6(Gigbag, { ref, axis: "height", as: Tag, className: merged, ...props, children: /* @__PURE__ */ jsx6(Vamp, { loading: showWarmup, children }) });
|
|
199
|
+
}
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
// src/components/ensemble.tsx
|
|
203
|
+
import {
|
|
204
|
+
forwardRef as forwardRef6,
|
|
205
|
+
useInsertionEffect as useInsertionEffect6
|
|
206
|
+
} from "react";
|
|
207
|
+
|
|
208
|
+
// src/components/warmup.tsx
|
|
209
|
+
import { forwardRef as forwardRef5, useInsertionEffect as useInsertionEffect5 } from "react";
|
|
210
|
+
import { jsx as jsx7, jsxs } from "react/jsx-runtime";
|
|
211
|
+
var Warmup = forwardRef5(
|
|
212
|
+
function Warmup2({ rows, columns, as: Tag = "div", className, children, ...props }, ref) {
|
|
213
|
+
useInsertionEffect5(injectStyles, []);
|
|
214
|
+
const merged = className ? `concertina-warmup ${className}` : "concertina-warmup";
|
|
215
|
+
const count = columns ? rows * columns : rows;
|
|
216
|
+
const cells = Array.from({ length: count }, (_, i) => /* @__PURE__ */ jsxs("div", { className: "concertina-warmup-bone", children: [
|
|
217
|
+
/* @__PURE__ */ jsx7("div", { className: "concertina-warmup-line" }),
|
|
218
|
+
/* @__PURE__ */ jsx7("div", { className: "concertina-warmup-line" })
|
|
219
|
+
] }, i));
|
|
220
|
+
const gridStyle = columns ? { gridTemplateColumns: `repeat(${columns}, auto)`, gridTemplateAreas: `'${"chamber ".repeat(columns).trim()}'` } : { gridTemplateAreas: "'chamber'" };
|
|
221
|
+
return /* @__PURE__ */ jsx7(
|
|
222
|
+
Tag,
|
|
223
|
+
{
|
|
224
|
+
ref,
|
|
225
|
+
className: merged,
|
|
226
|
+
style: gridStyle,
|
|
227
|
+
...props,
|
|
228
|
+
children: cells
|
|
229
|
+
}
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
|
|
226
234
|
// src/components/ensemble.tsx
|
|
227
|
-
import { jsx as
|
|
235
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
228
236
|
function EnsembleInner({
|
|
229
237
|
items,
|
|
230
238
|
loading,
|
|
@@ -238,18 +246,18 @@ function EnsembleInner({
|
|
|
238
246
|
useInsertionEffect6(injectStyles, []);
|
|
239
247
|
const { showWarmup, exiting } = useWarmupExit(loading, exitDuration);
|
|
240
248
|
const warmupClass = exiting ? className ? `concertina-warmup-exiting ${className}` : "concertina-warmup-exiting" : className;
|
|
241
|
-
return /* @__PURE__ */
|
|
249
|
+
return /* @__PURE__ */ jsx8(Gigbag, { ref, axis: "height", as: Tag, ...props, children: showWarmup ? /* @__PURE__ */ jsx8(Warmup, { rows: stubCount, className: warmupClass }) : /* @__PURE__ */ jsx8(Tag, { className, children: items.map(renderItem) }) });
|
|
242
250
|
}
|
|
243
251
|
var Ensemble = forwardRef6(EnsembleInner);
|
|
244
252
|
|
|
245
253
|
// src/components/warmup-line.tsx
|
|
246
254
|
import { forwardRef as forwardRef7, useInsertionEffect as useInsertionEffect7 } from "react";
|
|
247
|
-
import { jsx as
|
|
255
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
248
256
|
var WarmupLine = forwardRef7(
|
|
249
257
|
function WarmupLine2({ as: Tag = "div", className, ...props }, ref) {
|
|
250
258
|
useInsertionEffect7(injectStyles, []);
|
|
251
259
|
const merged = className ? `concertina-warmup-line ${className}` : "concertina-warmup-line";
|
|
252
|
-
return /* @__PURE__ */
|
|
260
|
+
return /* @__PURE__ */ jsx9(Tag, { ref, className: merged, ...props });
|
|
253
261
|
}
|
|
254
262
|
);
|
|
255
263
|
|
|
@@ -288,7 +296,7 @@ function usePresence(show) {
|
|
|
288
296
|
}
|
|
289
297
|
|
|
290
298
|
// src/components/glide.tsx
|
|
291
|
-
import { jsx as
|
|
299
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
292
300
|
var Glide = forwardRef8(
|
|
293
301
|
function Glide2({ show, as: Tag = "div", className, children, ...props }, ref) {
|
|
294
302
|
useInsertionEffect8(injectStyles, []);
|
|
@@ -296,7 +304,7 @@ var Glide = forwardRef8(
|
|
|
296
304
|
if (!mounted) return null;
|
|
297
305
|
const phaseClass = phase === "entering" ? "concertina-glide-entering" : phase === "exiting" ? "concertina-glide-exiting" : "";
|
|
298
306
|
const merged = ["concertina-glide", phaseClass, className].filter(Boolean).join(" ");
|
|
299
|
-
return /* @__PURE__ */
|
|
307
|
+
return /* @__PURE__ */ jsx10(
|
|
300
308
|
Tag,
|
|
301
309
|
{
|
|
302
310
|
ref,
|
|
@@ -353,6 +361,7 @@ export {
|
|
|
353
361
|
Header,
|
|
354
362
|
Hum,
|
|
355
363
|
Item,
|
|
364
|
+
Overture,
|
|
356
365
|
Root,
|
|
357
366
|
Slot,
|
|
358
367
|
Ensemble as StableCollection,
|