@ahrowe/ui 0.16.8 → 0.17.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.
@@ -1,2 +1,2 @@
1
1
  import { StickyProps } from './sticky.types';
2
- export default function Sticky({ children, offsetTop, zIndex, disabled, onStuckChange, className, style, classNames, styles: slotStyles, ...rest }: StickyProps): import("react/jsx-runtime").JSX.Element;
2
+ export default function Sticky({ children, offsetTop, offsetElement, stickToScrollParent, zIndex, disabled, onStuckChange, className, style, classNames, styles: slotStyles, ...rest }: StickyProps): import("react/jsx-runtime").JSX.Element;
@@ -3,8 +3,12 @@ import { SlotClassNames, SlotStyles, HtmlProps } from '../types/slots.types';
3
3
  export type StickySlots = 'root' | 'placeholder' | 'content';
4
4
  export interface StickyProps extends HtmlProps {
5
5
  children?: React.ReactNode;
6
- /** Distance (px) from the top of the viewport the content sticks to. Default 0. */
6
+ /** Distance (px) from the top of the viewport the content sticks to. Default 0. Ignored if `offsetElement` is set. */
7
7
  offsetTop?: number;
8
+ /** Element (e.g. a pinned header) to stick below instead of a fixed `offsetTop` — its live height is used as the offset. */
9
+ offsetElement?: React.RefObject<HTMLElement | null>;
10
+ /** Stick to the top of the nearest scrollable ancestor instead of the browser viewport. Falls back to the viewport if no scrollable ancestor is found. */
11
+ stickToScrollParent?: boolean;
8
12
  /** z-index applied to the content while stuck. Default 20. */
9
13
  zIndex?: number;
10
14
  /** Disable sticking entirely — content stays in normal flow. */
@@ -10,6 +10,11 @@ export interface StickyStackMetrics {
10
10
  left: number;
11
11
  width: number;
12
12
  contentHeight: number;
13
+ /** The element this entry sticks relative to (its scroll parent), or null for the viewport.
14
+ * Stacking only considers overlap between entries sharing the same container — two Stickies
15
+ * in unrelated scroll panels should never push each other down just because they happen to
16
+ * share horizontal DOM coordinates. */
17
+ container: HTMLElement | null;
13
18
  }
14
19
  export interface StickyStackEntry {
15
20
  node: HTMLElement;
package/docs/Sticky.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **When to use:** Pin a piece of content to the top of the viewport once the page scrolls past it — section headers, toolbars, filter bars, table headers. The content sticks while its original position is above the fold and releases the moment that position scrolls back into view. A placeholder preserves the original space, so nothing below jumps when it detaches.
4
4
 
5
- **Keywords:** sticky header, pinned toolbar
5
+ **Keywords:** sticky header, pinned toolbar, scroll container, app shell, scrollable panel
6
6
 
7
7
  **Import:** `import { Sticky } from '@ahrowe/ui'`
8
8
 
@@ -19,6 +19,23 @@ import { Sticky } from '@ahrowe/ui';
19
19
  <FilterBar />
20
20
  </Sticky>
21
21
 
22
+ // Stick below a header that isn't a Sticky itself — offset tracks its live height
23
+ const headerRef = useRef<HTMLElement>(null);
24
+ <header ref={headerRef}>...</header>
25
+ <Sticky offsetElement={headerRef}>
26
+ <FilterBar />
27
+ </Sticky>
28
+
29
+ // App-shell layout: a separately-scrollable content pane below a header that's
30
+ // outside that scroll container. Stick to the pane's own top edge instead of the
31
+ // browser viewport's, so it naturally stops below the header with no offset needed
32
+ <div style={{ height: '100vh', overflowY: 'auto' }}>
33
+ <Sticky stickToScrollParent offsetTop={0}>
34
+ <FilterBar />
35
+ </Sticky>
36
+ <Content />
37
+ </div>
38
+
22
39
  // React to the stuck state (e.g. add a shadow)
23
40
  <Sticky onStuckChange={(stuck) => setElevated(stuck)}>
24
41
  <Toolbar />
@@ -39,7 +56,9 @@ import { Sticky } from '@ahrowe/ui';
39
56
  </Sticky>
40
57
  ```
41
58
 
42
- **How it works:** while stuck, the content switches to `position: fixed` (measured to keep the same width and horizontal position) and a placeholder of the same height takes its place in the flow. It tracks scroll on the window *and* any ancestor scroll container, so it works inside scrollable panels too — it always pins to the top of the viewport (offset by `offsetTop`).
59
+ **How it works:** while stuck, the content switches to `position: fixed` (measured to keep the same width and horizontal position) and a placeholder of the same height takes its place in the flow. It tracks scroll on the window *and* any ancestor scroll container, so it works inside scrollable panels too. By default it pins to the top of the browser viewport (offset by `offsetTop`), regardless of any scroll container it's nested in — set `stickToScrollParent` to pin to that container's own top edge instead (see below).
60
+
61
+ **`stickToScrollParent`:** by default `Sticky` always pins relative to the browser viewport, which can overlap other fixed/pinned chrome (e.g. an app header) that sits outside the element's own scroll container. Set `stickToScrollParent` to pin relative to the nearest ancestor with its own scrollbar (`overflow-y: auto`/`scroll`/`overlay`) instead — found once on mount by walking up from the root. Falls back to the viewport if no scrollable ancestor is found. Combine with `offsetTop` for extra spacing from that container's top (e.g. its own internal header).
43
62
 
44
63
  **Stacking:** every mounted `Sticky` coordinates automatically, no extra markup needed. When more than one is stuck at the same time, a later one (in DOM order) that horizontally overlaps an earlier stuck one pins below it — `offsetTop` plus the overlapping ones' heights — instead of both landing on the same spot. Stickies that don't overlap horizontally (e.g. two half-width ones side by side under a full-width one) land at the same `top`, next to each other, rather than pushing each other down.
45
64
 
@@ -48,7 +67,9 @@ import { Sticky } from '@ahrowe/ui';
48
67
  | Prop | Type | Description |
49
68
  |------|------|-------------|
50
69
  | `children` | `ReactNode` | The content to make sticky |
51
- | `offsetTop` | `number` | Distance in px from the top of the viewport the content sticks to (default `0`) |
70
+ | `offsetTop` | `number` | Distance in px from the top of the viewport the content sticks to (default `0`). Ignored if `offsetElement` is set |
71
+ | `offsetElement` | `RefObject<HTMLElement \| null>` | Element to stick below (e.g. a fixed/pinned header outside this library) — its live height is used as the offset instead of a fixed `offsetTop` |
72
+ | `stickToScrollParent` | `boolean` | Stick to the top of the nearest scrollable ancestor instead of the browser viewport (default `false`). Falls back to the viewport if no scrollable ancestor is found |
52
73
  | `zIndex` | `number` | z-index applied while stuck (default `20`) |
53
74
  | `disabled` | `boolean` | Disable sticking — content stays in normal flow |
54
75
  | `onStuckChange` | `(isStuck: boolean) => void` | Fired whenever the stuck state flips |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ahrowe/ui",
3
- "version": "0.16.8",
3
+ "version": "0.17.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },