concertina 0.12.0 → 0.13.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 +30 -1
- package/dist/index.cjs +78 -62
- package/dist/index.d.cts +29 -1
- package/dist/index.d.ts +29 -1
- package/dist/index.js +62 -44
- 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/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,
|
|
@@ -1466,8 +1467,8 @@ var Hum = (0, import_react14.forwardRef)(
|
|
|
1466
1467
|
}
|
|
1467
1468
|
);
|
|
1468
1469
|
|
|
1469
|
-
// src/components/
|
|
1470
|
-
var
|
|
1470
|
+
// src/components/overture.tsx
|
|
1471
|
+
var import_react18 = require("react");
|
|
1471
1472
|
|
|
1472
1473
|
// src/components/gigbag.tsx
|
|
1473
1474
|
var import_react16 = require("react");
|
|
@@ -1546,38 +1547,12 @@ var Gigbag = (0, import_react16.forwardRef)(
|
|
|
1546
1547
|
}
|
|
1547
1548
|
);
|
|
1548
1549
|
|
|
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
1550
|
// src/primitives/use-warmup-exit.ts
|
|
1576
|
-
var
|
|
1551
|
+
var import_react17 = require("react");
|
|
1577
1552
|
function useWarmupExit(loading, duration) {
|
|
1578
|
-
const [exiting, setExiting] = (0,
|
|
1579
|
-
const prevLoading = (0,
|
|
1580
|
-
(0,
|
|
1553
|
+
const [exiting, setExiting] = (0, import_react17.useState)(false);
|
|
1554
|
+
const prevLoading = (0, import_react17.useRef)(loading);
|
|
1555
|
+
(0, import_react17.useEffect)(() => {
|
|
1581
1556
|
if (prevLoading.current && !loading) {
|
|
1582
1557
|
setExiting(true);
|
|
1583
1558
|
const id = setTimeout(() => setExiting(false), duration);
|
|
@@ -1594,8 +1569,48 @@ function useWarmupExit(loading, duration) {
|
|
|
1594
1569
|
};
|
|
1595
1570
|
}
|
|
1596
1571
|
|
|
1572
|
+
// src/components/overture.tsx
|
|
1573
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
1574
|
+
var Overture = (0, import_react18.forwardRef)(
|
|
1575
|
+
function Overture2({ loading, exitDuration, as: Tag = "div", className, children, ...props }, ref) {
|
|
1576
|
+
(0, import_react18.useInsertionEffect)(injectStyles, []);
|
|
1577
|
+
const { showWarmup, exiting } = useWarmupExit(loading, exitDuration);
|
|
1578
|
+
const merged = exiting ? className ? `concertina-warmup-exiting ${className}` : "concertina-warmup-exiting" : className;
|
|
1579
|
+
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 }) });
|
|
1580
|
+
}
|
|
1581
|
+
);
|
|
1582
|
+
|
|
1597
1583
|
// src/components/ensemble.tsx
|
|
1584
|
+
var import_react20 = require("react");
|
|
1585
|
+
|
|
1586
|
+
// src/components/warmup.tsx
|
|
1587
|
+
var import_react19 = require("react");
|
|
1598
1588
|
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
1589
|
+
var Warmup = (0, import_react19.forwardRef)(
|
|
1590
|
+
function Warmup2({ rows, columns, as: Tag = "div", className, children, ...props }, ref) {
|
|
1591
|
+
(0, import_react19.useInsertionEffect)(injectStyles, []);
|
|
1592
|
+
const merged = className ? `concertina-warmup ${className}` : "concertina-warmup";
|
|
1593
|
+
const count2 = columns ? rows * columns : rows;
|
|
1594
|
+
const cells = Array.from({ length: count2 }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "concertina-warmup-bone", children: [
|
|
1595
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "concertina-warmup-line" }),
|
|
1596
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "concertina-warmup-line" })
|
|
1597
|
+
] }, i));
|
|
1598
|
+
const gridStyle = columns ? { gridTemplateColumns: `repeat(${columns}, auto)`, gridTemplateAreas: `'${"chamber ".repeat(columns).trim()}'` } : { gridTemplateAreas: "'chamber'" };
|
|
1599
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
1600
|
+
Tag,
|
|
1601
|
+
{
|
|
1602
|
+
ref,
|
|
1603
|
+
className: merged,
|
|
1604
|
+
style: gridStyle,
|
|
1605
|
+
...props,
|
|
1606
|
+
children: cells
|
|
1607
|
+
}
|
|
1608
|
+
);
|
|
1609
|
+
}
|
|
1610
|
+
);
|
|
1611
|
+
|
|
1612
|
+
// src/components/ensemble.tsx
|
|
1613
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
1599
1614
|
function EnsembleInner({
|
|
1600
1615
|
items,
|
|
1601
1616
|
loading,
|
|
@@ -1606,33 +1621,33 @@ function EnsembleInner({
|
|
|
1606
1621
|
className,
|
|
1607
1622
|
...props
|
|
1608
1623
|
}, ref) {
|
|
1609
|
-
(0,
|
|
1624
|
+
(0, import_react20.useInsertionEffect)(injectStyles, []);
|
|
1610
1625
|
const { showWarmup, exiting } = useWarmupExit(loading, exitDuration);
|
|
1611
1626
|
const warmupClass = exiting ? className ? `concertina-warmup-exiting ${className}` : "concertina-warmup-exiting" : className;
|
|
1612
|
-
return /* @__PURE__ */ (0,
|
|
1627
|
+
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
1628
|
}
|
|
1614
|
-
var Ensemble = (0,
|
|
1629
|
+
var Ensemble = (0, import_react20.forwardRef)(EnsembleInner);
|
|
1615
1630
|
|
|
1616
1631
|
// src/components/warmup-line.tsx
|
|
1617
|
-
var
|
|
1618
|
-
var
|
|
1619
|
-
var WarmupLine = (0,
|
|
1632
|
+
var import_react21 = require("react");
|
|
1633
|
+
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
1634
|
+
var WarmupLine = (0, import_react21.forwardRef)(
|
|
1620
1635
|
function WarmupLine2({ as: Tag = "div", className, ...props }, ref) {
|
|
1621
|
-
(0,
|
|
1636
|
+
(0, import_react21.useInsertionEffect)(injectStyles, []);
|
|
1622
1637
|
const merged = className ? `concertina-warmup-line ${className}` : "concertina-warmup-line";
|
|
1623
|
-
return /* @__PURE__ */ (0,
|
|
1638
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Tag, { ref, className: merged, ...props });
|
|
1624
1639
|
}
|
|
1625
1640
|
);
|
|
1626
1641
|
|
|
1627
1642
|
// src/components/glide.tsx
|
|
1628
|
-
var
|
|
1643
|
+
var import_react23 = require("react");
|
|
1629
1644
|
|
|
1630
1645
|
// src/primitives/use-presence.ts
|
|
1631
|
-
var
|
|
1646
|
+
var import_react22 = require("react");
|
|
1632
1647
|
function usePresence2(show) {
|
|
1633
|
-
const [mounted, setMounted] = (0,
|
|
1634
|
-
const [phase, setPhase] = (0,
|
|
1635
|
-
(0,
|
|
1648
|
+
const [mounted, setMounted] = (0, import_react22.useState)(show);
|
|
1649
|
+
const [phase, setPhase] = (0, import_react22.useState)(show ? "entered" : "exiting");
|
|
1650
|
+
(0, import_react22.useEffect)(() => {
|
|
1636
1651
|
if (show) {
|
|
1637
1652
|
setMounted(true);
|
|
1638
1653
|
setPhase("entering");
|
|
@@ -1640,7 +1655,7 @@ function usePresence2(show) {
|
|
|
1640
1655
|
setPhase("exiting");
|
|
1641
1656
|
}
|
|
1642
1657
|
}, [show]);
|
|
1643
|
-
const onAnimationEnd = (0,
|
|
1658
|
+
const onAnimationEnd = (0, import_react22.useCallback)(
|
|
1644
1659
|
(e) => {
|
|
1645
1660
|
if (e.target !== e.currentTarget) return;
|
|
1646
1661
|
if (phase === "entering") setPhase("entered");
|
|
@@ -1652,15 +1667,15 @@ function usePresence2(show) {
|
|
|
1652
1667
|
}
|
|
1653
1668
|
|
|
1654
1669
|
// src/components/glide.tsx
|
|
1655
|
-
var
|
|
1656
|
-
var Glide = (0,
|
|
1670
|
+
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
1671
|
+
var Glide = (0, import_react23.forwardRef)(
|
|
1657
1672
|
function Glide2({ show, as: Tag = "div", className, children, ...props }, ref) {
|
|
1658
|
-
(0,
|
|
1673
|
+
(0, import_react23.useInsertionEffect)(injectStyles, []);
|
|
1659
1674
|
const { mounted, phase, onAnimationEnd } = usePresence2(show);
|
|
1660
1675
|
if (!mounted) return null;
|
|
1661
1676
|
const phaseClass = phase === "entering" ? "concertina-glide-entering" : phase === "exiting" ? "concertina-glide-exiting" : "";
|
|
1662
1677
|
const merged = ["concertina-glide", phaseClass, className].filter(Boolean).join(" ");
|
|
1663
|
-
return /* @__PURE__ */ (0,
|
|
1678
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
1664
1679
|
Tag,
|
|
1665
1680
|
{
|
|
1666
1681
|
ref,
|
|
@@ -1674,12 +1689,12 @@ var Glide = (0, import_react22.forwardRef)(
|
|
|
1674
1689
|
);
|
|
1675
1690
|
|
|
1676
1691
|
// src/primitives/use-size.ts
|
|
1677
|
-
var
|
|
1692
|
+
var import_react24 = require("react");
|
|
1678
1693
|
var NO_OBSERVATION = { width: Number.NaN, height: Number.NaN };
|
|
1679
1694
|
function useSize() {
|
|
1680
|
-
const [size, setSize] = (0,
|
|
1681
|
-
const observerRef = (0,
|
|
1682
|
-
const ref = (0,
|
|
1695
|
+
const [size, setSize] = (0, import_react24.useState)(NO_OBSERVATION);
|
|
1696
|
+
const observerRef = (0, import_react24.useRef)(null);
|
|
1697
|
+
const ref = (0, import_react24.useCallback)((el) => {
|
|
1683
1698
|
if (observerRef.current) {
|
|
1684
1699
|
observerRef.current.disconnect();
|
|
1685
1700
|
observerRef.current = null;
|
|
@@ -1708,12 +1723,12 @@ function useSize() {
|
|
|
1708
1723
|
}
|
|
1709
1724
|
|
|
1710
1725
|
// src/accordion/use-concertina.ts
|
|
1711
|
-
var
|
|
1726
|
+
var import_react25 = require("react");
|
|
1712
1727
|
function useConcertina() {
|
|
1713
|
-
const [value, setValue] = (0,
|
|
1714
|
-
const [switching, setSwitching] = (0,
|
|
1715
|
-
const itemRefs = (0,
|
|
1716
|
-
const onValueChange = (0,
|
|
1728
|
+
const [value, setValue] = (0, import_react25.useState)("");
|
|
1729
|
+
const [switching, setSwitching] = (0, import_react25.useState)(false);
|
|
1730
|
+
const itemRefs = (0, import_react25.useRef)({});
|
|
1731
|
+
const onValueChange = (0, import_react25.useCallback)(
|
|
1717
1732
|
(newValue) => {
|
|
1718
1733
|
if (!newValue) {
|
|
1719
1734
|
setSwitching(false);
|
|
@@ -1725,14 +1740,14 @@ function useConcertina() {
|
|
|
1725
1740
|
},
|
|
1726
1741
|
[value]
|
|
1727
1742
|
);
|
|
1728
|
-
(0,
|
|
1743
|
+
(0, import_react25.useLayoutEffect)(() => {
|
|
1729
1744
|
if (!value) return;
|
|
1730
1745
|
pinToScrollTop(itemRefs.current[value]);
|
|
1731
1746
|
}, [value]);
|
|
1732
|
-
(0,
|
|
1747
|
+
(0, import_react25.useEffect)(() => {
|
|
1733
1748
|
if (switching) setSwitching(false);
|
|
1734
1749
|
}, [switching]);
|
|
1735
|
-
const getItemRef = (0,
|
|
1750
|
+
const getItemRef = (0, import_react25.useCallback)(
|
|
1736
1751
|
(id) => (el) => {
|
|
1737
1752
|
itemRefs.current[id] = el;
|
|
1738
1753
|
},
|
|
@@ -1757,6 +1772,7 @@ function useConcertina() {
|
|
|
1757
1772
|
Header,
|
|
1758
1773
|
Hum,
|
|
1759
1774
|
Item,
|
|
1775
|
+
Overture,
|
|
1760
1776
|
Root,
|
|
1761
1777
|
Slot,
|
|
1762
1778
|
StableCollection,
|
package/dist/index.d.cts
CHANGED
|
@@ -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
|
@@ -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
|
@@ -92,10 +92,10 @@ var Hum = forwardRef3(
|
|
|
92
92
|
}
|
|
93
93
|
);
|
|
94
94
|
|
|
95
|
-
// src/components/
|
|
95
|
+
// src/components/overture.tsx
|
|
96
96
|
import {
|
|
97
|
-
forwardRef as
|
|
98
|
-
useInsertionEffect as
|
|
97
|
+
forwardRef as forwardRef5,
|
|
98
|
+
useInsertionEffect as useInsertionEffect5
|
|
99
99
|
} from "react";
|
|
100
100
|
|
|
101
101
|
// src/components/gigbag.tsx
|
|
@@ -175,32 +175,6 @@ var Gigbag = forwardRef4(
|
|
|
175
175
|
}
|
|
176
176
|
);
|
|
177
177
|
|
|
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
178
|
// src/primitives/use-warmup-exit.ts
|
|
205
179
|
import { useState as useState2, useEffect, useRef as useRef2 } from "react";
|
|
206
180
|
function useWarmupExit(loading, duration) {
|
|
@@ -223,8 +197,51 @@ function useWarmupExit(loading, duration) {
|
|
|
223
197
|
};
|
|
224
198
|
}
|
|
225
199
|
|
|
200
|
+
// src/components/overture.tsx
|
|
201
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
202
|
+
var Overture = forwardRef5(
|
|
203
|
+
function Overture2({ loading, exitDuration, as: Tag = "div", className, children, ...props }, ref) {
|
|
204
|
+
useInsertionEffect5(injectStyles, []);
|
|
205
|
+
const { showWarmup, exiting } = useWarmupExit(loading, exitDuration);
|
|
206
|
+
const merged = exiting ? className ? `concertina-warmup-exiting ${className}` : "concertina-warmup-exiting" : className;
|
|
207
|
+
return /* @__PURE__ */ jsx6(Gigbag, { ref, axis: "height", as: Tag, className: merged, ...props, children: /* @__PURE__ */ jsx6(Vamp, { loading: showWarmup, children }) });
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
// src/components/ensemble.tsx
|
|
212
|
+
import {
|
|
213
|
+
forwardRef as forwardRef7,
|
|
214
|
+
useInsertionEffect as useInsertionEffect7
|
|
215
|
+
} from "react";
|
|
216
|
+
|
|
217
|
+
// src/components/warmup.tsx
|
|
218
|
+
import { forwardRef as forwardRef6, useInsertionEffect as useInsertionEffect6 } from "react";
|
|
219
|
+
import { jsx as jsx7, jsxs } from "react/jsx-runtime";
|
|
220
|
+
var Warmup = forwardRef6(
|
|
221
|
+
function Warmup2({ rows, columns, as: Tag = "div", className, children, ...props }, ref) {
|
|
222
|
+
useInsertionEffect6(injectStyles, []);
|
|
223
|
+
const merged = className ? `concertina-warmup ${className}` : "concertina-warmup";
|
|
224
|
+
const count = columns ? rows * columns : rows;
|
|
225
|
+
const cells = Array.from({ length: count }, (_, i) => /* @__PURE__ */ jsxs("div", { className: "concertina-warmup-bone", children: [
|
|
226
|
+
/* @__PURE__ */ jsx7("div", { className: "concertina-warmup-line" }),
|
|
227
|
+
/* @__PURE__ */ jsx7("div", { className: "concertina-warmup-line" })
|
|
228
|
+
] }, i));
|
|
229
|
+
const gridStyle = columns ? { gridTemplateColumns: `repeat(${columns}, auto)`, gridTemplateAreas: `'${"chamber ".repeat(columns).trim()}'` } : { gridTemplateAreas: "'chamber'" };
|
|
230
|
+
return /* @__PURE__ */ jsx7(
|
|
231
|
+
Tag,
|
|
232
|
+
{
|
|
233
|
+
ref,
|
|
234
|
+
className: merged,
|
|
235
|
+
style: gridStyle,
|
|
236
|
+
...props,
|
|
237
|
+
children: cells
|
|
238
|
+
}
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
);
|
|
242
|
+
|
|
226
243
|
// src/components/ensemble.tsx
|
|
227
|
-
import { jsx as
|
|
244
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
228
245
|
function EnsembleInner({
|
|
229
246
|
items,
|
|
230
247
|
loading,
|
|
@@ -235,28 +252,28 @@ function EnsembleInner({
|
|
|
235
252
|
className,
|
|
236
253
|
...props
|
|
237
254
|
}, ref) {
|
|
238
|
-
|
|
255
|
+
useInsertionEffect7(injectStyles, []);
|
|
239
256
|
const { showWarmup, exiting } = useWarmupExit(loading, exitDuration);
|
|
240
257
|
const warmupClass = exiting ? className ? `concertina-warmup-exiting ${className}` : "concertina-warmup-exiting" : className;
|
|
241
|
-
return /* @__PURE__ */
|
|
258
|
+
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
259
|
}
|
|
243
|
-
var Ensemble =
|
|
260
|
+
var Ensemble = forwardRef7(EnsembleInner);
|
|
244
261
|
|
|
245
262
|
// src/components/warmup-line.tsx
|
|
246
|
-
import { forwardRef as
|
|
247
|
-
import { jsx as
|
|
248
|
-
var WarmupLine =
|
|
263
|
+
import { forwardRef as forwardRef8, useInsertionEffect as useInsertionEffect8 } from "react";
|
|
264
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
265
|
+
var WarmupLine = forwardRef8(
|
|
249
266
|
function WarmupLine2({ as: Tag = "div", className, ...props }, ref) {
|
|
250
|
-
|
|
267
|
+
useInsertionEffect8(injectStyles, []);
|
|
251
268
|
const merged = className ? `concertina-warmup-line ${className}` : "concertina-warmup-line";
|
|
252
|
-
return /* @__PURE__ */
|
|
269
|
+
return /* @__PURE__ */ jsx9(Tag, { ref, className: merged, ...props });
|
|
253
270
|
}
|
|
254
271
|
);
|
|
255
272
|
|
|
256
273
|
// src/components/glide.tsx
|
|
257
274
|
import {
|
|
258
|
-
forwardRef as
|
|
259
|
-
useInsertionEffect as
|
|
275
|
+
forwardRef as forwardRef9,
|
|
276
|
+
useInsertionEffect as useInsertionEffect9
|
|
260
277
|
} from "react";
|
|
261
278
|
|
|
262
279
|
// src/primitives/use-presence.ts
|
|
@@ -288,15 +305,15 @@ function usePresence(show) {
|
|
|
288
305
|
}
|
|
289
306
|
|
|
290
307
|
// src/components/glide.tsx
|
|
291
|
-
import { jsx as
|
|
292
|
-
var Glide =
|
|
308
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
309
|
+
var Glide = forwardRef9(
|
|
293
310
|
function Glide2({ show, as: Tag = "div", className, children, ...props }, ref) {
|
|
294
|
-
|
|
311
|
+
useInsertionEffect9(injectStyles, []);
|
|
295
312
|
const { mounted, phase, onAnimationEnd } = usePresence(show);
|
|
296
313
|
if (!mounted) return null;
|
|
297
314
|
const phaseClass = phase === "entering" ? "concertina-glide-entering" : phase === "exiting" ? "concertina-glide-exiting" : "";
|
|
298
315
|
const merged = ["concertina-glide", phaseClass, className].filter(Boolean).join(" ");
|
|
299
|
-
return /* @__PURE__ */
|
|
316
|
+
return /* @__PURE__ */ jsx10(
|
|
300
317
|
Tag,
|
|
301
318
|
{
|
|
302
319
|
ref,
|
|
@@ -353,6 +370,7 @@ export {
|
|
|
353
370
|
Header,
|
|
354
371
|
Hum,
|
|
355
372
|
Item,
|
|
373
|
+
Overture,
|
|
356
374
|
Root,
|
|
357
375
|
Slot,
|
|
358
376
|
Ensemble as StableCollection,
|