prime-ui-kit 0.21.1 → 0.21.2

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 (33) hide show
  1. package/dist/chunks/{chunk-S27VR4ON.js → chunk-7JYPFRZ3.js} +77 -23
  2. package/dist/chunks/{chunk-S4H2ZKWJ.js → chunk-ME4VEAQ5.js} +40 -13
  3. package/dist/chunks/{chunk-IVS3J7AS.js → chunk-SQGPQOUO.js} +1 -1
  4. package/dist/chunks/{chunk-QFHE5GKW.js → chunk-T7IEJSTH.js} +569 -556
  5. package/dist/chunks/{chunk-6L7VWEY6.js → chunk-VQ3UCVDV.js} +2 -2
  6. package/dist/color-picker.js +3 -3
  7. package/dist/components/message/Message.d.ts +2 -2
  8. package/dist/components/scroll-container/ScrollContainer.d.ts +1 -1
  9. package/dist/icon-set.d.ts +1 -0
  10. package/dist/icon-set.js +4 -2
  11. package/dist/icons/glyphs/app-window.d.ts +2 -0
  12. package/dist/icons/registry.d.ts +2 -0
  13. package/dist/index.css +1 -1
  14. package/dist/index.js +3 -3
  15. package/dist/internal/overlay/DialogParts.d.ts +1 -1
  16. package/dist/internal/selectionIndicator.d.ts +9 -3
  17. package/dist/skill/api-contract.md +1 -1
  18. package/dist/skill/reference/components/data-table/COMPONENT.md +1 -0
  19. package/dist/skill/reference/components/message/COMPONENT.md +6 -5
  20. package/dist/skill/reference/components/message/examples/bot-actions.tsx +6 -2
  21. package/dist/skill/reference/components/scroll-container/COMPONENT.md +5 -5
  22. package/dist/skill/reference/components/scroll-container/examples/overscroll.tsx +2 -2
  23. package/dist/skill/reference/components/tabs/COMPONENT.md +1 -1
  24. package/dist/skill/reference/icon-set.ts +1 -0
  25. package/dist/skill/reference/layout/app-shell/COMPONENT.md +1 -1
  26. package/package.json +1 -1
  27. package/src/components/data-table/COMPONENT.md +1 -0
  28. package/src/components/message/COMPONENT.md +6 -5
  29. package/src/components/message/examples/bot-actions.tsx +6 -2
  30. package/src/components/scroll-container/COMPONENT.md +5 -5
  31. package/src/components/scroll-container/examples/overscroll.tsx +2 -2
  32. package/src/components/tabs/COMPONENT.md +1 -1
  33. package/src/layout/app-shell/COMPONENT.md +1 -1
package/dist/index.js CHANGED
@@ -88,7 +88,7 @@ import {
88
88
  useSidebar,
89
89
  useTokenValue,
90
90
  useUploadQueue
91
- } from "./chunks/chunk-S4H2ZKWJ.js";
91
+ } from "./chunks/chunk-ME4VEAQ5.js";
92
92
  import {
93
93
  Avatar,
94
94
  Badge,
@@ -123,11 +123,11 @@ import {
123
123
  tierMappings,
124
124
  tones,
125
125
  variants
126
- } from "./chunks/chunk-S27VR4ON.js";
126
+ } from "./chunks/chunk-7JYPFRZ3.js";
127
127
  import {
128
128
  ControlSizeProvider,
129
129
  useOptionalControlSize
130
- } from "./chunks/chunk-QFHE5GKW.js";
130
+ } from "./chunks/chunk-T7IEJSTH.js";
131
131
  export {
132
132
  Accordion,
133
133
  AppShell,
@@ -214,7 +214,7 @@ export declare namespace DialogActions {
214
214
  export type DialogBodyProps = React.HTMLAttributes<HTMLDivElement> & {
215
215
  ref?: React.Ref<HTMLDivElement>;
216
216
  };
217
- /** The only scrolling zone; header and footer stay put. */
217
+ /** The only scrolling zone; header and footer stay put. An open layer: its end never scrolls the page. */
218
218
  export declare function DialogBody({ className, ...rest }: DialogBodyProps): import("react/jsx-runtime").JSX.Element;
219
219
  export declare namespace DialogBody {
220
220
  var displayName: string;
@@ -12,11 +12,17 @@ export declare function setFlag(element: HTMLElement, name: `data-${string}`, on
12
12
  /**
13
13
  * Places the indicator over `target` — its offset box in the parent they share — straight in the
14
14
  * DOM, so a resize or a font swap never re-renders the host. With `animate` (a person's choice) a
15
- * move of a visible indicator glides; every other placement snaps. Without a target the
16
- * indicator fades out.
15
+ * move of a visible indicator glides, and an indicator that appears or leaves fades
16
+ * (`data-fade`); every other placement snaps, and its first one shows it at once: a fade on mount
17
+ * would leave the selected item without its fill while the host opens (a Drawer sliding in), and
18
+ * an opacity transition makes the indicator a composited layer that browsers paint over the
19
+ * item's text inside the row's edge-fade mask until it ends.
17
20
  */
18
21
  export declare function placeIndicator(indicator: HTMLElement, target: HTMLElement | null, animate: boolean): void;
19
- /** `onTransitionEnd` of the indicator: the glide is over, so the next layout change snaps. */
22
+ /**
23
+ * `onTransitionEnd` of the indicator: the glide or the fade is over, so the next layout change
24
+ * snaps.
25
+ */
20
26
  export declare function endIndicatorGlide(event: Pick<React.TransitionEvent<HTMLElement>, "target" | "currentTarget" | "propertyName">): void;
21
27
  /**
22
28
  * Scrolls a sideways scroller (never the page: `scrollIntoView` would scroll it too) so `item` is
@@ -377,7 +377,7 @@ crumb of your own.
377
377
  <!-- icons:start -->
378
378
  `<Icon name="…" />` names:
379
379
  - `nav.*`: `chevronDown`, `chevronLeft`, `chevronRight`, `chevronUp`, `chevronsLeft`, `chevronsUpDown`, `dashboard`, `home`, `itemDot`, `layoutGrid`, `menu`, `sidebarCollapse`, `sidebarExpand`;
380
- - `action.*`: `add`, `addContact`, `attach`, `check`, `close`, `copy`, `delete`, `download`, `drag`, `edit`, `externalLink`, `eyedropper`, `filter`, `forward`, `login`, `logout`, `more`, `pause`, `pin`, `play`, `react`, `refresh`, `report`, `remove`, `reply`, `search`, `send`, `settings`, `transcribe`, `upload`, `video`, `voice`, `zoomIn`, `zoomOut`;
380
+ - `action.*`: `add`, `addContact`, `attach`, `check`, `close`, `copy`, `delete`, `download`, `drag`, `edit`, `externalLink`, `eyedropper`, `filter`, `forward`, `login`, `logout`, `more`, `openApp`, `pause`, `pin`, `play`, `react`, `refresh`, `report`, `remove`, `reply`, `search`, `send`, `settings`, `transcribe`, `upload`, `video`, `voice`, `zoomIn`, `zoomOut`;
381
381
  - `field.*`: `calendar`, `email`, `password.show`, `password.hide`;
382
382
  - `format.*`: `bold`, `code`, `italic`, `link`, `list`, `quote`, `spoiler`, `strikethrough`, `underline`;
383
383
  - `object.*`: `activity`, `bell`, `bike`, `book`, `cart`, `chart`, `crown`, `disc`, `document`, `file`, `fuel`, `funnel`, `globe`, `headset`, `image`, `inbox`, `key`, `mapPin`, `megaphone`, `message`, `motorbike`, `package`, `phone`, `receipt`, `rocket`, `scale`, `storage`, `tasks`, `trophy`, `truck`, `user`, `users`, `wallet`;
@@ -305,6 +305,7 @@ Root attributes: `data-size`, `data-row-dividers`, `data-column-dividers`, `data
305
305
 
306
306
  ## Layout & spacing
307
307
  - The root is a layer of the surface ladder (`data-depth`, one above its host): it fills its container on `--prime-color-layer-current`, radius 12, and clips full-bleed head and rows; zebra rows and the sticky column are one ladder step off it, the head two. On a card or in a modal it is the next layer by itself — never wrap it in a Card.
308
+ - The table scrolls inside its own viewport (a ScrollContainer, `axis="both"`): at its top or bottom — or when it fits and does not scroll — the wheel and the trackpad go on to the page; sideways it stops at its edge without a bounce, so the sticky column stays on its rows and the browser never swipes back.
308
309
  - A filter stands next to what it filters. `toolbar` is the table's own tools row, always — on a page with other blocks (KPI cards, a chart) and on a page that is the table: its search and filter (`SmartFilter.Toolbar`, chips in `toolbarChips`), `DataTableColumnsMenu`, a table / cards view. Actions on the selected rows go to `renderSelectionActions`, never into `toolbar`: while rows are selected the table adds a «Действия · N» menu button last in the tool row — the search, filter and chooser stay, the selection never replaces the row. The page's bar keeps what acts on the whole page — the title with its line of data, sections or a view switch, a period that drives several blocks, the actions (`Page.Actions`). One control never gets a row of its own.
309
310
  - The tools row is the one tool row of the kit (`Page.Tools` lays out the same way): controls in the order written, a search or a line of text (`Typography`) takes the free room, switchers, pickers and buttons keep their content width at the end edge. Under 36rem of table width the search takes a line of its own and a `SegmentedControl` (`fullWidth` unless set) fills the line it shares with the buttons; a search never parts from its filter button (`SmartFilter.Toolbar` keeps both). Wrap the table in `SmartFilter.Root` so the toolbar and the chips share one state.
310
311
  - Pass the tools as direct children of `toolbar` or a fragment: the row lays out each element as a cell. A wrapper component that returns several controls is one cell — no gaps between them, no growing search.
@@ -233,7 +233,7 @@ Content parts in short: `Message.Media` — a [Gallery](../gallery/COMPONENT.md)
233
233
  | `…rest` | `Omit<HTMLAttributes<HTMLDivElement>, "children">` | — | `className` and the other attributes. |
234
234
 
235
235
  ### Message.Actions
236
- `ref` → `HTMLDivElement`. A row of a bot's inline keyboard under the bubble: `Button.Root` keys (`soft` `neutral` by default) share one line equally, long labels ellipsize. A link key gets the link mark; a key whose `onClick` returns a promise loads until it settles.
236
+ `ref` → `HTMLDivElement`. A bot's inline keyboard row under the bubble: `Button.Root` keys (`soft` `neutral` by default) share one line, long labels ellipsize. A link key gets a corner mark: ↗ out of the product, a window into it; a promise from `onClick` keeps a key loading.
237
237
 
238
238
  | Prop | Type | Default | Description |
239
239
  |---|---|---|---|
@@ -363,7 +363,7 @@ The list-level settings a message reads — the group chat's author header and a
363
363
  - `Message.Text` wraps anywhere; the meta stands at the end of its last line.
364
364
  - Round controls (download, cancel, retry, play) are a Button `shape="circle"`: hover, press, focus ring and the touch area follow the circle. A file's action is its round tile (48): the transfer disk (`internal/transferDisk`, `xl`) with its ring, the retry or the download stand on it — never a separate button beside the name. Every transfer, either way (media, file, video note, voice), is that one disk.
365
365
  - `Message.Typing` is the last row of a list; a chat puts it there itself (`Chat.Root typing`).
366
- - **A bot's inline keyboard** (`Message.Actions`): each part is a row under the bubble, `--prime-space-1` below it and between rows and keys, on the chat's own surface (outside the bubble's fill). The keyboard is as wide as the message: the column takes the wider of the bubble and the keyboard (up to the bubble's cap) and the bubble widens to it, so their edges line up. A row is one line, as in Telegram: its keys share it equally (`flex: 1 1 0`, each key `fullWidth`), and a label longer than its share ellipsizes. Keys keep the control radius; the last row's outer bottom corners take the bubble's (`--prime-message-radius`), so the keyboard closes the message. A key is `Button.Root` `variant="soft" tone="neutral"` at the host tier unless it sets its own: Telegram's button `style` maps onto `tone` — `primary` → `accent`, `success`, `danger` (`variant="solid"` for a filled key). A custom emoji before the label is a `Button.Icon`. A link key (`asChild` around an `<a href>`) carries the link mark (`action.externalLink`, `xs`) at its top-end corner, out of the label's flow. A callback key whose `onClick` returns a promise is `loading` until it settles: the spinner takes its leading icon's place or covers the label, the width never changes. A message with a keyboard has no tail: the keyboard closes it.
366
+ - **A bot's inline keyboard** (`Message.Actions`): each part is a row under the bubble, `--prime-space-1` below it and between rows and keys, on the chat's own surface (outside the bubble's fill). The keyboard is as wide as the message: the column takes the wider of the bubble and the keyboard (up to the bubble's cap) and the bubble widens to it, so their edges line up. A row is one line, as in Telegram: its keys share it equally (`flex: 1 1 0`, each key `fullWidth`), and a label longer than its share ellipsizes. Keys keep the control radius; the last row's outer bottom corners take the bubble's (`--prime-message-radius`), so the keyboard closes the message. A key is `Button.Root` `variant="soft" tone="neutral"` at the host tier unless it sets its own: Telegram's button `style` maps onto `tone` — `primary` → `accent`, `success`, `danger` (`variant="solid"` for a filled key). A custom emoji before the label is a `Button.Icon`. A link key (`asChild` around an element with `href`, or a router link's `to`) carries one mark (`xs`) at its top-end corner, out of the label's flow, chosen by where the address leads, as Telegram tells a url key from a web app key: `action.externalLink` when it leaves the product (another origin, or another scheme — `mailto:`, `tel:`, an app link — which opens another program), `action.openApp` when it opens a page of the product (a relative path, a hash, an absolute address on this origin; on the server, without `location`, every address that is not absolute). A callback key whose `onClick` returns a promise is `loading` until it settles: the spinner takes its leading icon's place or covers the label, the width never changes. A message with a keyboard has no tail: the keyboard closes it.
367
367
 
368
368
  ## Accessibility
369
369
 
@@ -390,7 +390,7 @@ The list-level settings a message reads — the group chat's author header and a
390
390
  - Reactions are a group of buttons with `aria-pressed`; a chip is named «{emoji}: {count}», with `users` «{emoji} {count}: {users}» («❤️ 3: Анна, Пётр, Мария», «ещё N» when not everyone is listed); its avatars are `aria-hidden`; without `onReactionToggle` the chips are `disabled`. The reaction bar is a `toolbar` of toggle buttons named by the reaction («Нравится», «Огонь»…); its «⌄» is `aria-expanded`. The quick reaction is announced in the list's polite live region.
391
391
  - `VideoNote` and `VoicePlayer` carry their own ARIA (see their pages); inside a message their transcript is a plain paragraph, selectable and marked by the chat's search.
392
392
  - Media tiles are buttons «Открыть: {alt}» that open the album in MediaViewer, the focus returning to the tile on close; a transfer is the round cancel, retry or download button, the share its accessible description; a refused retry or cancel is announced politely; a done transfer's disk is `inert`.
393
- - `Message.Actions` are plain buttons and links (`Button.Root`, `asChild`) with visible labels, in row order; in a chat's selection mode they take no presses.
393
+ - `Message.Actions` are plain buttons and links (`Button.Root`, `asChild`) with visible labels, in row order; the corner mark of a link key is decorative (`aria-hidden`), the link is named by its label; in a chat's selection mode they take no presses.
394
394
  - Color is never the only signal: the status is named, the own reaction is `aria-pressed`, a selected message is `aria-selected` with a check, the author is text.
395
395
 
396
396
  ### Labels
@@ -506,7 +506,7 @@ The list-level settings a message reads — the group chat's author header and a
506
506
  | [location.tsx](examples/location.tsx) | A place on a map picture from your map service with a pin, the place and its address, and a live location of a courier on its way — `Message.Location`, `href`, `live`. |
507
507
  | [contact.tsx](examples/contact.tsx) | A shared contact: initials or a photo, name and phone, with the actions to write or save — `Message.Contact`, `phone`. |
508
508
  | [link-preview.tsx](examples/link-preview.tsx) | A message with a link and the card of the page under it: site, title, description and picture — `Message.LinkPreview`, `site`, `image`. |
509
- | [bot-actions.tsx](examples/bot-actions.tsx) | A bot's message with an inline keyboard: one-line rows whose keys share the row equally, a coloured key with an icon that loads while its promise runs, a link with its mark — `Message.Actions`, `tone`, `Button.Icon`, `asChild`. |
509
+ | [bot-actions.tsx](examples/bot-actions.tsx) | A bot's message with an inline keyboard: one-line rows whose keys share the row equally, a coloured key with an icon that loads while its promise runs, links marked by where they lead: a page of the product or another site — `Message.Actions`, `tone`, `Button.Icon`, `asChild`. |
510
510
  | [sticker.tsx](examples/sticker.tsx) | Stickers in a conversation: a picture by its address and a video sticker in your own player, no bubble, the time on a pill over the corner — `Message.Sticker`, `src`, `loading`. |
511
511
  | [preview.tsx](examples/preview.tsx) | A bot's welcome message as its settings page shows it: the same bubble with its keyboard, nothing in it pressed or opened, the time in the zone of the profile — `preview`, `timeZone`. |
512
512
  | [narrow.tsx](examples/narrow.tsx) | At phone width bubbles take up to 85% of the chat, long words and links wrap inside, an album keeps its rows — `Message.Root`, `Message.Media`. |
@@ -532,7 +532,8 @@ The list-level settings a message reads — the group chat's author header and a
532
532
  - A transcript as a separate `Message.Text`, or a height animation of your own around it → `transcript` / `onTranscribe` on `VoicePlayer` and `VideoNote`: the text opens inside the same message, in one stroke with its height.
533
533
  - A list, dates, search or selection built around messages → [Chat](../chat/COMPONENT.md).
534
534
  - A bot's buttons placed inside `Message.Text`, or a row of buttons of your own under a message → `Message.Actions` with `Button.Root` children: one row per part, at the message's width.
535
+ - An arrow icon of your own in a link key (`Button.Icon` with `action.externalLink`) → leave the label alone: `Message.Actions` draws the key's one corner mark itself, ↗ for another site and a window for a page of the product; a second arrow doubles it.
535
536
 
536
537
  ## Related
537
- - **Built from:** [Dropdown](../dropdown/COMPONENT.md) (`Message.Menu`), [ScrollContainer](../scroll-container/COMPONENT.md) (the floating panel of the reaction bar), [Gallery](../gallery/COMPONENT.md) (`Message.Media`), [Avatar](../avatar/COMPONENT.md) (`Avatar.Group` + `Avatar.Overflow` in reaction chips), [Badge](../badge/COMPONENT.md) (the «Трансляция» mark of a live location), [Thumbnail](../thumbnail/COMPONENT.md) (the map, the link preview picture, a file's preview), [Button](../button/COMPONENT.md) (retry, the transfer disk, `shape="circle"`, the buttons of `Message.Actions`), [Checkbox](../checkbox/COMPONENT.md) (`Checkbox.Indicator` in selection mode), [Spinner](../spinner/COMPONENT.md) (pending transfers and transcripts), [Skeleton](../skeleton/COMPONENT.md) (a sticker while it loads), Icon (status ticks, file glyphs, `action.forward`, `action.refresh`, `action.externalLink` — the link mark of a key); `internal/messagePart` (the contract of content parts), `internal/messageList` (the contract with a chat's list), `internal/palette` (`hueOf`), `internal/Disclosure` (the reactions row, transcripts), `bubbleMorph` (every size change as one stroke), `bubbleHug` (a bubble of text as wide as its widest line), `internal/transferDisk` (the transfer of a file), `internal/mediaPill` (the meta, the author's pill), `internal/HighlightMatch` (search marks), `internal/richText` (Markdown), [VisuallyHidden](../visually-hidden/COMPONENT.md)
538
+ - **Built from:** [Dropdown](../dropdown/COMPONENT.md) (`Message.Menu`), [ScrollContainer](../scroll-container/COMPONENT.md) (the floating panel of the reaction bar), [Gallery](../gallery/COMPONENT.md) (`Message.Media`), [Avatar](../avatar/COMPONENT.md) (`Avatar.Group` + `Avatar.Overflow` in reaction chips), [Badge](../badge/COMPONENT.md) (the «Трансляция» mark of a live location), [Thumbnail](../thumbnail/COMPONENT.md) (the map, the link preview picture, a file's preview), [Button](../button/COMPONENT.md) (retry, the transfer disk, `shape="circle"`, the buttons of `Message.Actions`), [Checkbox](../checkbox/COMPONENT.md) (`Checkbox.Indicator` in selection mode), [Spinner](../spinner/COMPONENT.md) (pending transfers and transcripts), [Skeleton](../skeleton/COMPONENT.md) (a sticker while it loads), Icon (status ticks, file glyphs, `action.forward`, `action.refresh`, `action.externalLink` and `action.openApp` — the marks of a link key); `internal/messagePart` (the contract of content parts), `internal/messageList` (the contract with a chat's list), `internal/palette` (`hueOf`), `internal/Disclosure` (the reactions row, transcripts), `bubbleMorph` (every size change as one stroke), `bubbleHug` (a bubble of text as wide as its widest line), `internal/transferDisk` (the transfer of a file), `internal/mediaPill` (the meta, the author's pill), `internal/HighlightMatch` (search marks), `internal/richText` (Markdown), [VisuallyHidden](../visually-hidden/COMPONENT.md)
538
539
  - **See also:** [Chat](../chat/COMPONENT.md) — the conversation the messages stand in; [Gallery](../gallery/COMPONENT.md) — the album of `Message.Media`; [VideoNote](../video-note/COMPONENT.md) — the round video message; [VoicePlayer](../voice-player/COMPONENT.md) — the content of a voice message; [Composer](../composer/COMPONENT.md) — the input under the list; [RichText](../rich-text/COMPONENT.md) — the text of `Message.Text` outside a message; [Timeline](../timeline/COMPONENT.md)
@@ -1,10 +1,11 @@
1
- /** A bot's message with an inline keyboard: one-line rows whose keys share the row equally, a coloured key with an icon that loads while its promise runs, a link with its mark — `Message.Actions`, `tone`, `Button.Icon`, `asChild`. */
1
+ /** A bot's message with an inline keyboard: one-line rows whose keys share the row equally, a coloured key with an icon that loads while its promise runs, links marked by where they lead: a page of the product or another site — `Message.Actions`, `tone`, `Button.Icon`, `asChild`. */
2
2
  import { Button, Icon, Message } from "prime-ui-kit";
3
3
  import * as React from "react";
4
4
 
5
5
  import styles from "./examples.module.css";
6
6
 
7
7
  const INVOICE_URL = "https://vector.example/invoices/4821";
8
+ const CATALOG_PATH = "#catalog";
8
9
  const CONFIRM_DELAY_MS = 1200;
9
10
 
10
11
  export default function MessageBotActionsExample() {
@@ -58,9 +59,12 @@ export default function MessageBotActionsExample() {
58
59
  </Message.Actions>
59
60
  ) : null}
60
61
  <Message.Actions>
62
+ <Button.Root asChild>
63
+ <a href={CATALOG_PATH}>Каталог</a>
64
+ </Button.Root>
61
65
  <Button.Root asChild>
62
66
  <a href={INVOICE_URL} target="_blank" rel="noreferrer">
63
- Открыть счёт
67
+ Счёт
64
68
  </a>
65
69
  </Button.Root>
66
70
  </Message.Actions>
@@ -48,7 +48,7 @@ ScrollContainer <div> (or the `as` tag); thin scrollbar, soft hidden edges
48
48
  | `anchor` | `"start" \| "end"` | `"start"` | `end` — a chat or a log: opens at the end and follows new content while the person is at the end; scrolled away, the view holds still. |
49
49
  | `onReachStart` | `() => void \| Promise<void>` | — | Fires within 120 px of the start, once per approach: load older content. A returned promise blocks repeats and sets `aria-busy`; content added above keeps the view. |
50
50
  | `onReachEnd` | `() => void \| Promise<void>` | — | The scroll position came within 120 px of the end: load the next page. Same once-per-approach and promise rules as `onReachStart`. |
51
- | `overscrollBehavior` | `"auto" \| "contain" \| "none"` | `"contain"` | CSS `overscroll-behavior` along the region's own axes; `contain` stops scroll chaining into the page, a scroll across them still reaches the region around. |
51
+ | `overscrollBehavior` | `"auto" \| "contain" \| "none"` | `"auto"` | At the region's end: `auto` hands the scroll on to the page (sideways it stops); `contain` / `none` hold both axes — an open layer, where the page behind must not move. |
52
52
  | `as` | `"div" \| "main" \| "aside" \| "section" \| "nav" \| "article" \| "pre"` | `"div"` | Root element; a landmark tag when the region is one, `pre` for preformatted text (CodeBlock). |
53
53
  | `…rest` | `HTMLAttributes<HTMLElement>` | — | `children`, `className` (height, padding), `aria-label`, `tabIndex`, `role`, event handlers and the other attributes. |
54
54
 
@@ -64,9 +64,9 @@ ScrollContainer <div> (or the `as` tag); thin scrollbar, soft hidden edges
64
64
  ### overscrollBehavior
65
65
  | Value | Looks like | Use when | Default |
66
66
  |---|---|---|---|
67
- | `contain` | Reaching the end does not scroll the page behind | Nested panels, lists inside a page | yes |
68
- | `auto` | Browser default: scroll chains to the parent | The region is the page's main scroller and should hand off | |
69
- | `none` | No chaining and no overscroll glow / bounce | Full-screen app regions | |
67
+ | `auto` | At its end up or down the scroll goes on to the page (or the region around); a region that does not scroll never holds the wheel. Sideways it stops without a bounce: a page never scrolls sideways, and a sideways chain would swipe the browser's history | every region on a page: lists, tables, boards, chats, code | yes |
68
+ | `contain` | Reaching the end on either axis does not scroll what is behind | an open layer — Modal / Drawer / Sheet body, a floating panel, the CommandMenu list set it themselves; a panel of your own that floats over the page | |
69
+ | `none` | `contain` without the overscroll glow / bounce | a full-screen layer of your own | |
70
70
 
71
71
  ### edges
72
72
  | Value | Looks like | Use when | Default |
@@ -165,7 +165,7 @@ No `labels`.
165
165
  | [snap.tsx](examples/snap.tsx) | A strip of warehouse cards that always comes to rest on a card's start, scrolled by touch, a trackpad or the mouse wheel — `snap`, `axis`. |
166
166
  | [lock.tsx](examples/lock.tsx) | While an order is opened for review, the queue behind it holds still: no wheel, touch, key or drag moves it — `lock`. |
167
167
  | [auto-scroll.tsx](examples/auto-scroll.tsx) | A task dragged to the top or bottom edge of a scrolling list scrolls it, faster the closer it comes — `ScrollContainer`, `Dnd.Sortable`. |
168
- | [overscroll.tsx](examples/overscroll.tsx) | At the end of the list the scroll passes on to the page instead of stopping — `overscrollBehavior`. |
168
+ | [overscroll.tsx](examples/overscroll.tsx) | A panel that keeps its scroll: at the end of the list the page behind stays put — `overscrollBehavior`. |
169
169
  | [log.tsx](examples/log.tsx) | A live exchange log pinned to its last entry: a new entry keeps it at the end, scrolling up loads older ones without moving what is on screen — `anchor`, `onReachStart`. |
170
170
  | [load-more.tsx](examples/load-more.tsx) | An order log that loads the next page as the reader nears its end, with loading lines in the rows' place — `onReachEnd`. |
171
171
 
@@ -1,4 +1,4 @@
1
- /** At the end of the list the scroll passes on to the page instead of stopping — `overscrollBehavior`. */
1
+ /** A panel that keeps its scroll: at the end of the list the page behind stays put — `overscrollBehavior`. */
2
2
  import { Card, ScrollContainer, Typography } from "prime-ui-kit";
3
3
 
4
4
  import styles from "./examples.module.css";
@@ -11,7 +11,7 @@ export default function ScrollContainerOverscrollExample() {
11
11
  <Card.Header>
12
12
  <Card.Title>Журнал выгрузок</Card.Title>
13
13
  </Card.Header>
14
- <ScrollContainer overscrollBehavior="auto" tabIndex={0} aria-label="Журнал выгрузок">
14
+ <ScrollContainer overscrollBehavior="contain" tabIndex={0} aria-label="Журнал выгрузок">
15
15
  <Card.List>
16
16
  {HOURS.map((hour) => (
17
17
  <Card.ListItem key={hour}>
@@ -216,7 +216,7 @@ Other attributes: Root `data-orientation`, `data-size`, `data-tone`, `data-full-
216
216
  ```
217
217
  - The folder paints the host's `layer-current`, so the active tab is always the colour of the surface it stands on; controls in a panel read the host's field and fill tokens.
218
218
  - Vertical: the column is as wide as its widest section (at least four control heights, at most 40% of the root) and as tall as its sections; the panel sits beside it, one gap away.
219
- - The list is a switcher (foundation §9): it never wraps — it collapses, then scrolls horizontally with faded edges (hidden scrollbar, `overscroll-behavior: contain`). Give the list's container a width (`min-width: 0` on flex children).
219
+ - The list is a switcher (foundation §9): it never wraps — it collapses, then scrolls horizontally with faded edges (hidden scrollbar; at its sideways edge it stops, a vertical wheel goes on to the page). Give the list's container a width (`min-width: 0` on flex children).
220
220
  - Without `size` the tabs take the tier of their host (a Card, a Drawer, a form), else `m`.
221
221
  - A horizontal tab is never narrower than its content (`min-width: max-content`; the tab bounds that content between `minItemWidth` and `maxItemWidth`), so a label is never cut to make room. Full width, tabs tend to equal widths, not widths in proportion to their labels (`flex: 1 1 0`): a long label keeps its width, the short ones share the rest.
222
222
  - A vertical Tabs.Root is a size container: give it a width (it measures itself to switch to a row).
@@ -20,6 +20,7 @@ export { AlignVerticalSpaceAround as AlignVerticalSpaceAroundIcon } from "./icon
20
20
  export { Ambulance as AmbulanceIcon } from "./icons/glyphs/ambulance";
21
21
  export { Angry as AngryIcon } from "./icons/glyphs/angry";
22
22
  export { Annoyed as AnnoyedIcon } from "./icons/glyphs/annoyed";
23
+ export { AppWindow as AppWindowIcon } from "./icons/glyphs/app-window";
23
24
  export { Archive as ArchiveIcon } from "./icons/glyphs/archive";
24
25
  export { ArrowBigDown as ArrowBigDownIcon } from "./icons/glyphs/arrow-big-down";
25
26
  export { ArrowBigDownDash as ArrowBigDownDashIcon } from "./icons/glyphs/arrow-big-down-dash";
@@ -128,7 +128,7 @@ The navigation of the nearest `AppShell.Root` for a trigger outside the rail (a
128
128
  - Two full-height planes edge to edge: no inset, radius, shadow or border — the boundary is the fill change (the Sidebar rail is layer 1, the panel the page: white rail beside a gray page in light, a rail one step lighter than the near-black page in dark).
129
129
  - Main hands the page its edge air: `--page-gutter` is the page gutter `--prime-layout-gutter` — `--prime-layout-gutter-s` (16) → `-m` (24) from 640px → `-l` (32) from 1024px; `--page-top` is `--prime-space-6` (24) → `--prime-space-8` (32) from 1024px. `Page.Root` pads itself with them (its top bar steps out to the panel's edges). Main itself pads only the bottom: `--prime-space-16`, the scroll tail of a long page. Inside Main the page gutter is the small one again, so a frame nested in the page starts from its own gutter, not the shell's.
130
130
  - A page that fills the panel height (`Page.Root fill`): Main's bottom padding drops to the side gutter, so no long scroll tail stays under it — beside the Sidebar rail too.
131
- - Main scrolls with `overscroll-behavior: contain`, so its scroll never chains to the document.
131
+ - Main hands its scroll on at its end (ScrollContainer `overscrollBehavior="auto"`): without `fillViewport` it is not a scroller and the document scrolls under the pointer.
132
132
  - Nav column: sticky at the top, viewport high, `z-index: --prime-z-sticky` (above the panel, so the compact Sidebar toggle can ride the rail edge); with `fillViewport` it fills the shell height instead.
133
133
  - Footer: no padding of its own (BottomNav keeps the bottom safe-area inset), sticky at the bottom, `z-index: --prime-z-sticky`.
134
134
  - A floating BottomNav (`floating`) in the footer: the footer takes no height, the capsule overlays the page, and Main keeps extra bottom room (the capsule height and the safe-area inset) so its last line never ends up under it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prime-ui-kit",
3
- "version": "0.21.1",
3
+ "version": "0.21.2",
4
4
  "packageManager": "bun@1.3.13",
5
5
  "description": "React 19 UI kit for product interfaces: 79 composable components on one design contract — forms, selects, tables, overlays, navigation, app shell. CSS Modules, --prime-* tokens, light and dark themes, TypeScript, a11y, Next.js App Router ready. Ships COMPONENT.md docs, copyable examples and an agent skill.",
6
6
  "bin": {
@@ -305,6 +305,7 @@ Root attributes: `data-size`, `data-row-dividers`, `data-column-dividers`, `data
305
305
 
306
306
  ## Layout & spacing
307
307
  - The root is a layer of the surface ladder (`data-depth`, one above its host): it fills its container on `--prime-color-layer-current`, radius 12, and clips full-bleed head and rows; zebra rows and the sticky column are one ladder step off it, the head two. On a card or in a modal it is the next layer by itself — never wrap it in a Card.
308
+ - The table scrolls inside its own viewport (a ScrollContainer, `axis="both"`): at its top or bottom — or when it fits and does not scroll — the wheel and the trackpad go on to the page; sideways it stops at its edge without a bounce, so the sticky column stays on its rows and the browser never swipes back.
308
309
  - A filter stands next to what it filters. `toolbar` is the table's own tools row, always — on a page with other blocks (KPI cards, a chart) and on a page that is the table: its search and filter (`SmartFilter.Toolbar`, chips in `toolbarChips`), `DataTableColumnsMenu`, a table / cards view. Actions on the selected rows go to `renderSelectionActions`, never into `toolbar`: while rows are selected the table adds a «Действия · N» menu button last in the tool row — the search, filter and chooser stay, the selection never replaces the row. The page's bar keeps what acts on the whole page — the title with its line of data, sections or a view switch, a period that drives several blocks, the actions (`Page.Actions`). One control never gets a row of its own.
309
310
  - The tools row is the one tool row of the kit (`Page.Tools` lays out the same way): controls in the order written, a search or a line of text (`Typography`) takes the free room, switchers, pickers and buttons keep their content width at the end edge. Under 36rem of table width the search takes a line of its own and a `SegmentedControl` (`fullWidth` unless set) fills the line it shares with the buttons; a search never parts from its filter button (`SmartFilter.Toolbar` keeps both). Wrap the table in `SmartFilter.Root` so the toolbar and the chips share one state.
310
311
  - Pass the tools as direct children of `toolbar` or a fragment: the row lays out each element as a cell. A wrapper component that returns several controls is one cell — no gaps between them, no growing search.
@@ -233,7 +233,7 @@ Content parts in short: `Message.Media` — a [Gallery](../gallery/COMPONENT.md)
233
233
  | `…rest` | `Omit<HTMLAttributes<HTMLDivElement>, "children">` | — | `className` and the other attributes. |
234
234
 
235
235
  ### Message.Actions
236
- `ref` → `HTMLDivElement`. A row of a bot's inline keyboard under the bubble: `Button.Root` keys (`soft` `neutral` by default) share one line equally, long labels ellipsize. A link key gets the link mark; a key whose `onClick` returns a promise loads until it settles.
236
+ `ref` → `HTMLDivElement`. A bot's inline keyboard row under the bubble: `Button.Root` keys (`soft` `neutral` by default) share one line, long labels ellipsize. A link key gets a corner mark: ↗ out of the product, a window into it; a promise from `onClick` keeps a key loading.
237
237
 
238
238
  | Prop | Type | Default | Description |
239
239
  |---|---|---|---|
@@ -363,7 +363,7 @@ The list-level settings a message reads — the group chat's author header and a
363
363
  - `Message.Text` wraps anywhere; the meta stands at the end of its last line.
364
364
  - Round controls (download, cancel, retry, play) are a Button `shape="circle"`: hover, press, focus ring and the touch area follow the circle. A file's action is its round tile (48): the transfer disk (`internal/transferDisk`, `xl`) with its ring, the retry or the download stand on it — never a separate button beside the name. Every transfer, either way (media, file, video note, voice), is that one disk.
365
365
  - `Message.Typing` is the last row of a list; a chat puts it there itself (`Chat.Root typing`).
366
- - **A bot's inline keyboard** (`Message.Actions`): each part is a row under the bubble, `--prime-space-1` below it and between rows and keys, on the chat's own surface (outside the bubble's fill). The keyboard is as wide as the message: the column takes the wider of the bubble and the keyboard (up to the bubble's cap) and the bubble widens to it, so their edges line up. A row is one line, as in Telegram: its keys share it equally (`flex: 1 1 0`, each key `fullWidth`), and a label longer than its share ellipsizes. Keys keep the control radius; the last row's outer bottom corners take the bubble's (`--prime-message-radius`), so the keyboard closes the message. A key is `Button.Root` `variant="soft" tone="neutral"` at the host tier unless it sets its own: Telegram's button `style` maps onto `tone` — `primary` → `accent`, `success`, `danger` (`variant="solid"` for a filled key). A custom emoji before the label is a `Button.Icon`. A link key (`asChild` around an `<a href>`) carries the link mark (`action.externalLink`, `xs`) at its top-end corner, out of the label's flow. A callback key whose `onClick` returns a promise is `loading` until it settles: the spinner takes its leading icon's place or covers the label, the width never changes. A message with a keyboard has no tail: the keyboard closes it.
366
+ - **A bot's inline keyboard** (`Message.Actions`): each part is a row under the bubble, `--prime-space-1` below it and between rows and keys, on the chat's own surface (outside the bubble's fill). The keyboard is as wide as the message: the column takes the wider of the bubble and the keyboard (up to the bubble's cap) and the bubble widens to it, so their edges line up. A row is one line, as in Telegram: its keys share it equally (`flex: 1 1 0`, each key `fullWidth`), and a label longer than its share ellipsizes. Keys keep the control radius; the last row's outer bottom corners take the bubble's (`--prime-message-radius`), so the keyboard closes the message. A key is `Button.Root` `variant="soft" tone="neutral"` at the host tier unless it sets its own: Telegram's button `style` maps onto `tone` — `primary` → `accent`, `success`, `danger` (`variant="solid"` for a filled key). A custom emoji before the label is a `Button.Icon`. A link key (`asChild` around an element with `href`, or a router link's `to`) carries one mark (`xs`) at its top-end corner, out of the label's flow, chosen by where the address leads, as Telegram tells a url key from a web app key: `action.externalLink` when it leaves the product (another origin, or another scheme — `mailto:`, `tel:`, an app link — which opens another program), `action.openApp` when it opens a page of the product (a relative path, a hash, an absolute address on this origin; on the server, without `location`, every address that is not absolute). A callback key whose `onClick` returns a promise is `loading` until it settles: the spinner takes its leading icon's place or covers the label, the width never changes. A message with a keyboard has no tail: the keyboard closes it.
367
367
 
368
368
  ## Accessibility
369
369
 
@@ -390,7 +390,7 @@ The list-level settings a message reads — the group chat's author header and a
390
390
  - Reactions are a group of buttons with `aria-pressed`; a chip is named «{emoji}: {count}», with `users` «{emoji} {count}: {users}» («❤️ 3: Анна, Пётр, Мария», «ещё N» when not everyone is listed); its avatars are `aria-hidden`; without `onReactionToggle` the chips are `disabled`. The reaction bar is a `toolbar` of toggle buttons named by the reaction («Нравится», «Огонь»…); its «⌄» is `aria-expanded`. The quick reaction is announced in the list's polite live region.
391
391
  - `VideoNote` and `VoicePlayer` carry their own ARIA (see their pages); inside a message their transcript is a plain paragraph, selectable and marked by the chat's search.
392
392
  - Media tiles are buttons «Открыть: {alt}» that open the album in MediaViewer, the focus returning to the tile on close; a transfer is the round cancel, retry or download button, the share its accessible description; a refused retry or cancel is announced politely; a done transfer's disk is `inert`.
393
- - `Message.Actions` are plain buttons and links (`Button.Root`, `asChild`) with visible labels, in row order; in a chat's selection mode they take no presses.
393
+ - `Message.Actions` are plain buttons and links (`Button.Root`, `asChild`) with visible labels, in row order; the corner mark of a link key is decorative (`aria-hidden`), the link is named by its label; in a chat's selection mode they take no presses.
394
394
  - Color is never the only signal: the status is named, the own reaction is `aria-pressed`, a selected message is `aria-selected` with a check, the author is text.
395
395
 
396
396
  ### Labels
@@ -506,7 +506,7 @@ The list-level settings a message reads — the group chat's author header and a
506
506
  | [location.tsx](examples/location.tsx) | A place on a map picture from your map service with a pin, the place and its address, and a live location of a courier on its way — `Message.Location`, `href`, `live`. |
507
507
  | [contact.tsx](examples/contact.tsx) | A shared contact: initials or a photo, name and phone, with the actions to write or save — `Message.Contact`, `phone`. |
508
508
  | [link-preview.tsx](examples/link-preview.tsx) | A message with a link and the card of the page under it: site, title, description and picture — `Message.LinkPreview`, `site`, `image`. |
509
- | [bot-actions.tsx](examples/bot-actions.tsx) | A bot's message with an inline keyboard: one-line rows whose keys share the row equally, a coloured key with an icon that loads while its promise runs, a link with its mark — `Message.Actions`, `tone`, `Button.Icon`, `asChild`. |
509
+ | [bot-actions.tsx](examples/bot-actions.tsx) | A bot's message with an inline keyboard: one-line rows whose keys share the row equally, a coloured key with an icon that loads while its promise runs, links marked by where they lead: a page of the product or another site — `Message.Actions`, `tone`, `Button.Icon`, `asChild`. |
510
510
  | [sticker.tsx](examples/sticker.tsx) | Stickers in a conversation: a picture by its address and a video sticker in your own player, no bubble, the time on a pill over the corner — `Message.Sticker`, `src`, `loading`. |
511
511
  | [preview.tsx](examples/preview.tsx) | A bot's welcome message as its settings page shows it: the same bubble with its keyboard, nothing in it pressed or opened, the time in the zone of the profile — `preview`, `timeZone`. |
512
512
  | [narrow.tsx](examples/narrow.tsx) | At phone width bubbles take up to 85% of the chat, long words and links wrap inside, an album keeps its rows — `Message.Root`, `Message.Media`. |
@@ -532,7 +532,8 @@ The list-level settings a message reads — the group chat's author header and a
532
532
  - A transcript as a separate `Message.Text`, or a height animation of your own around it → `transcript` / `onTranscribe` on `VoicePlayer` and `VideoNote`: the text opens inside the same message, in one stroke with its height.
533
533
  - A list, dates, search or selection built around messages → [Chat](../chat/COMPONENT.md).
534
534
  - A bot's buttons placed inside `Message.Text`, or a row of buttons of your own under a message → `Message.Actions` with `Button.Root` children: one row per part, at the message's width.
535
+ - An arrow icon of your own in a link key (`Button.Icon` with `action.externalLink`) → leave the label alone: `Message.Actions` draws the key's one corner mark itself, ↗ for another site and a window for a page of the product; a second arrow doubles it.
535
536
 
536
537
  ## Related
537
- - **Built from:** [Dropdown](../dropdown/COMPONENT.md) (`Message.Menu`), [ScrollContainer](../scroll-container/COMPONENT.md) (the floating panel of the reaction bar), [Gallery](../gallery/COMPONENT.md) (`Message.Media`), [Avatar](../avatar/COMPONENT.md) (`Avatar.Group` + `Avatar.Overflow` in reaction chips), [Badge](../badge/COMPONENT.md) (the «Трансляция» mark of a live location), [Thumbnail](../thumbnail/COMPONENT.md) (the map, the link preview picture, a file's preview), [Button](../button/COMPONENT.md) (retry, the transfer disk, `shape="circle"`, the buttons of `Message.Actions`), [Checkbox](../checkbox/COMPONENT.md) (`Checkbox.Indicator` in selection mode), [Spinner](../spinner/COMPONENT.md) (pending transfers and transcripts), [Skeleton](../skeleton/COMPONENT.md) (a sticker while it loads), Icon (status ticks, file glyphs, `action.forward`, `action.refresh`, `action.externalLink` — the link mark of a key); `internal/messagePart` (the contract of content parts), `internal/messageList` (the contract with a chat's list), `internal/palette` (`hueOf`), `internal/Disclosure` (the reactions row, transcripts), `bubbleMorph` (every size change as one stroke), `bubbleHug` (a bubble of text as wide as its widest line), `internal/transferDisk` (the transfer of a file), `internal/mediaPill` (the meta, the author's pill), `internal/HighlightMatch` (search marks), `internal/richText` (Markdown), [VisuallyHidden](../visually-hidden/COMPONENT.md)
538
+ - **Built from:** [Dropdown](../dropdown/COMPONENT.md) (`Message.Menu`), [ScrollContainer](../scroll-container/COMPONENT.md) (the floating panel of the reaction bar), [Gallery](../gallery/COMPONENT.md) (`Message.Media`), [Avatar](../avatar/COMPONENT.md) (`Avatar.Group` + `Avatar.Overflow` in reaction chips), [Badge](../badge/COMPONENT.md) (the «Трансляция» mark of a live location), [Thumbnail](../thumbnail/COMPONENT.md) (the map, the link preview picture, a file's preview), [Button](../button/COMPONENT.md) (retry, the transfer disk, `shape="circle"`, the buttons of `Message.Actions`), [Checkbox](../checkbox/COMPONENT.md) (`Checkbox.Indicator` in selection mode), [Spinner](../spinner/COMPONENT.md) (pending transfers and transcripts), [Skeleton](../skeleton/COMPONENT.md) (a sticker while it loads), Icon (status ticks, file glyphs, `action.forward`, `action.refresh`, `action.externalLink` and `action.openApp` — the marks of a link key); `internal/messagePart` (the contract of content parts), `internal/messageList` (the contract with a chat's list), `internal/palette` (`hueOf`), `internal/Disclosure` (the reactions row, transcripts), `bubbleMorph` (every size change as one stroke), `bubbleHug` (a bubble of text as wide as its widest line), `internal/transferDisk` (the transfer of a file), `internal/mediaPill` (the meta, the author's pill), `internal/HighlightMatch` (search marks), `internal/richText` (Markdown), [VisuallyHidden](../visually-hidden/COMPONENT.md)
538
539
  - **See also:** [Chat](../chat/COMPONENT.md) — the conversation the messages stand in; [Gallery](../gallery/COMPONENT.md) — the album of `Message.Media`; [VideoNote](../video-note/COMPONENT.md) — the round video message; [VoicePlayer](../voice-player/COMPONENT.md) — the content of a voice message; [Composer](../composer/COMPONENT.md) — the input under the list; [RichText](../rich-text/COMPONENT.md) — the text of `Message.Text` outside a message; [Timeline](../timeline/COMPONENT.md)
@@ -1,10 +1,11 @@
1
- /** A bot's message with an inline keyboard: one-line rows whose keys share the row equally, a coloured key with an icon that loads while its promise runs, a link with its mark — `Message.Actions`, `tone`, `Button.Icon`, `asChild`. */
1
+ /** A bot's message with an inline keyboard: one-line rows whose keys share the row equally, a coloured key with an icon that loads while its promise runs, links marked by where they lead: a page of the product or another site — `Message.Actions`, `tone`, `Button.Icon`, `asChild`. */
2
2
  import { Button, Icon, Message } from "prime-ui-kit";
3
3
  import * as React from "react";
4
4
 
5
5
  import styles from "./examples.module.css";
6
6
 
7
7
  const INVOICE_URL = "https://vector.example/invoices/4821";
8
+ const CATALOG_PATH = "#catalog";
8
9
  const CONFIRM_DELAY_MS = 1200;
9
10
 
10
11
  export default function MessageBotActionsExample() {
@@ -58,9 +59,12 @@ export default function MessageBotActionsExample() {
58
59
  </Message.Actions>
59
60
  ) : null}
60
61
  <Message.Actions>
62
+ <Button.Root asChild>
63
+ <a href={CATALOG_PATH}>Каталог</a>
64
+ </Button.Root>
61
65
  <Button.Root asChild>
62
66
  <a href={INVOICE_URL} target="_blank" rel="noreferrer">
63
- Открыть счёт
67
+ Счёт
64
68
  </a>
65
69
  </Button.Root>
66
70
  </Message.Actions>
@@ -48,7 +48,7 @@ ScrollContainer <div> (or the `as` tag); thin scrollbar, soft hidden edges
48
48
  | `anchor` | `"start" \| "end"` | `"start"` | `end` — a chat or a log: opens at the end and follows new content while the person is at the end; scrolled away, the view holds still. |
49
49
  | `onReachStart` | `() => void \| Promise<void>` | — | Fires within 120 px of the start, once per approach: load older content. A returned promise blocks repeats and sets `aria-busy`; content added above keeps the view. |
50
50
  | `onReachEnd` | `() => void \| Promise<void>` | — | The scroll position came within 120 px of the end: load the next page. Same once-per-approach and promise rules as `onReachStart`. |
51
- | `overscrollBehavior` | `"auto" \| "contain" \| "none"` | `"contain"` | CSS `overscroll-behavior` along the region's own axes; `contain` stops scroll chaining into the page, a scroll across them still reaches the region around. |
51
+ | `overscrollBehavior` | `"auto" \| "contain" \| "none"` | `"auto"` | At the region's end: `auto` hands the scroll on to the page (sideways it stops); `contain` / `none` hold both axes — an open layer, where the page behind must not move. |
52
52
  | `as` | `"div" \| "main" \| "aside" \| "section" \| "nav" \| "article" \| "pre"` | `"div"` | Root element; a landmark tag when the region is one, `pre` for preformatted text (CodeBlock). |
53
53
  | `…rest` | `HTMLAttributes<HTMLElement>` | — | `children`, `className` (height, padding), `aria-label`, `tabIndex`, `role`, event handlers and the other attributes. |
54
54
 
@@ -64,9 +64,9 @@ ScrollContainer <div> (or the `as` tag); thin scrollbar, soft hidden edges
64
64
  ### overscrollBehavior
65
65
  | Value | Looks like | Use when | Default |
66
66
  |---|---|---|---|
67
- | `contain` | Reaching the end does not scroll the page behind | Nested panels, lists inside a page | yes |
68
- | `auto` | Browser default: scroll chains to the parent | The region is the page's main scroller and should hand off | |
69
- | `none` | No chaining and no overscroll glow / bounce | Full-screen app regions | |
67
+ | `auto` | At its end up or down the scroll goes on to the page (or the region around); a region that does not scroll never holds the wheel. Sideways it stops without a bounce: a page never scrolls sideways, and a sideways chain would swipe the browser's history | every region on a page: lists, tables, boards, chats, code | yes |
68
+ | `contain` | Reaching the end on either axis does not scroll what is behind | an open layer — Modal / Drawer / Sheet body, a floating panel, the CommandMenu list set it themselves; a panel of your own that floats over the page | |
69
+ | `none` | `contain` without the overscroll glow / bounce | a full-screen layer of your own | |
70
70
 
71
71
  ### edges
72
72
  | Value | Looks like | Use when | Default |
@@ -165,7 +165,7 @@ No `labels`.
165
165
  | [snap.tsx](examples/snap.tsx) | A strip of warehouse cards that always comes to rest on a card's start, scrolled by touch, a trackpad or the mouse wheel — `snap`, `axis`. |
166
166
  | [lock.tsx](examples/lock.tsx) | While an order is opened for review, the queue behind it holds still: no wheel, touch, key or drag moves it — `lock`. |
167
167
  | [auto-scroll.tsx](examples/auto-scroll.tsx) | A task dragged to the top or bottom edge of a scrolling list scrolls it, faster the closer it comes — `ScrollContainer`, `Dnd.Sortable`. |
168
- | [overscroll.tsx](examples/overscroll.tsx) | At the end of the list the scroll passes on to the page instead of stopping — `overscrollBehavior`. |
168
+ | [overscroll.tsx](examples/overscroll.tsx) | A panel that keeps its scroll: at the end of the list the page behind stays put — `overscrollBehavior`. |
169
169
  | [log.tsx](examples/log.tsx) | A live exchange log pinned to its last entry: a new entry keeps it at the end, scrolling up loads older ones without moving what is on screen — `anchor`, `onReachStart`. |
170
170
  | [load-more.tsx](examples/load-more.tsx) | An order log that loads the next page as the reader nears its end, with loading lines in the rows' place — `onReachEnd`. |
171
171
 
@@ -1,4 +1,4 @@
1
- /** At the end of the list the scroll passes on to the page instead of stopping — `overscrollBehavior`. */
1
+ /** A panel that keeps its scroll: at the end of the list the page behind stays put — `overscrollBehavior`. */
2
2
  import { Card, ScrollContainer, Typography } from "prime-ui-kit";
3
3
 
4
4
  import styles from "./examples.module.css";
@@ -11,7 +11,7 @@ export default function ScrollContainerOverscrollExample() {
11
11
  <Card.Header>
12
12
  <Card.Title>Журнал выгрузок</Card.Title>
13
13
  </Card.Header>
14
- <ScrollContainer overscrollBehavior="auto" tabIndex={0} aria-label="Журнал выгрузок">
14
+ <ScrollContainer overscrollBehavior="contain" tabIndex={0} aria-label="Журнал выгрузок">
15
15
  <Card.List>
16
16
  {HOURS.map((hour) => (
17
17
  <Card.ListItem key={hour}>
@@ -216,7 +216,7 @@ Other attributes: Root `data-orientation`, `data-size`, `data-tone`, `data-full-
216
216
  ```
217
217
  - The folder paints the host's `layer-current`, so the active tab is always the colour of the surface it stands on; controls in a panel read the host's field and fill tokens.
218
218
  - Vertical: the column is as wide as its widest section (at least four control heights, at most 40% of the root) and as tall as its sections; the panel sits beside it, one gap away.
219
- - The list is a switcher (foundation §9): it never wraps — it collapses, then scrolls horizontally with faded edges (hidden scrollbar, `overscroll-behavior: contain`). Give the list's container a width (`min-width: 0` on flex children).
219
+ - The list is a switcher (foundation §9): it never wraps — it collapses, then scrolls horizontally with faded edges (hidden scrollbar; at its sideways edge it stops, a vertical wheel goes on to the page). Give the list's container a width (`min-width: 0` on flex children).
220
220
  - Without `size` the tabs take the tier of their host (a Card, a Drawer, a form), else `m`.
221
221
  - A horizontal tab is never narrower than its content (`min-width: max-content`; the tab bounds that content between `minItemWidth` and `maxItemWidth`), so a label is never cut to make room. Full width, tabs tend to equal widths, not widths in proportion to their labels (`flex: 1 1 0`): a long label keeps its width, the short ones share the rest.
222
222
  - A vertical Tabs.Root is a size container: give it a width (it measures itself to switch to a row).
@@ -128,7 +128,7 @@ The navigation of the nearest `AppShell.Root` for a trigger outside the rail (a
128
128
  - Two full-height planes edge to edge: no inset, radius, shadow or border — the boundary is the fill change (the Sidebar rail is layer 1, the panel the page: white rail beside a gray page in light, a rail one step lighter than the near-black page in dark).
129
129
  - Main hands the page its edge air: `--page-gutter` is the page gutter `--prime-layout-gutter` — `--prime-layout-gutter-s` (16) → `-m` (24) from 640px → `-l` (32) from 1024px; `--page-top` is `--prime-space-6` (24) → `--prime-space-8` (32) from 1024px. `Page.Root` pads itself with them (its top bar steps out to the panel's edges). Main itself pads only the bottom: `--prime-space-16`, the scroll tail of a long page. Inside Main the page gutter is the small one again, so a frame nested in the page starts from its own gutter, not the shell's.
130
130
  - A page that fills the panel height (`Page.Root fill`): Main's bottom padding drops to the side gutter, so no long scroll tail stays under it — beside the Sidebar rail too.
131
- - Main scrolls with `overscroll-behavior: contain`, so its scroll never chains to the document.
131
+ - Main hands its scroll on at its end (ScrollContainer `overscrollBehavior="auto"`): without `fillViewport` it is not a scroller and the document scrolls under the pointer.
132
132
  - Nav column: sticky at the top, viewport high, `z-index: --prime-z-sticky` (above the panel, so the compact Sidebar toggle can ride the rail edge); with `fillViewport` it fills the shell height instead.
133
133
  - Footer: no padding of its own (BottomNav keeps the bottom safe-area inset), sticky at the bottom, `z-index: --prime-z-sticky`.
134
134
  - A floating BottomNav (`floating`) in the footer: the footer takes no height, the capsule overlays the page, and Main keeps extra bottom room (the capsule height and the safe-area inset) so its last line never ends up under it.