@estiva-app/ui 0.36.0 → 0.37.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,47 @@
1
+ import type { ReactNode, Ref, UIEventHandler } from 'react';
2
+ /**
3
+ * A panel of a page: its name in a `ContainerHeader`, a body under it that
4
+ * scrolls whenever it is taller than the room it has, and, when there is one,
5
+ * a row fixed at the bottom that never scrolls — a reply box.
6
+ *
7
+ * Made so that scrolling comes from the package, not from each app remembering
8
+ * it (Katerina, 26 September 2026). The apps built their panels from the
9
+ * package's header and a box of their own, and a box without a `ScrollArea`
10
+ * simply cut off what did not fit: Peek's widget panel on a Ship file lost the
11
+ * end of a long description and the status control under it, with no bar and
12
+ * nothing to scroll. Here the body is always a `ScrollArea`, so the bar shows
13
+ * as soon as there is more, and an empty panel or a short one has none.
14
+ *
15
+ * It takes the room it is given, never its content's height, so the body is
16
+ * what scrolls, not the page: the rest of a flex column (`flex-1 min-h-0`), or
17
+ * the whole of a plain box with a height (`h-full`). The second was missing in
18
+ * 0.37.0, and a panel in a plain box grew past it: Peek's thread story pushed
19
+ * its reply box out of its frame. Two panels in one column share it; a
20
+ * `max-h-*` in `className` caps one of them.
21
+ *
22
+ * It keeps a crash inside itself, as ListColumn does: if its body, its actions
23
+ * or its footer break, the panel keeps its title and shows `ErrorBoundary`'s
24
+ * message in the room the body had.
25
+ */
26
+ export interface PanelProps {
27
+ /** The panel's name, in its header. A node for a title that is more than words. */
28
+ title: ReactNode;
29
+ /** A chevron after the title, as `ContainerHeader` draws it. */
30
+ chevron?: boolean;
31
+ /** The panel's own actions, at the header's right — a `Toolbar` of `ToolbarButton`s, or one `IconButton`. */
32
+ actions?: ReactNode;
33
+ /** The body. It scrolls when it is taller than the panel. */
34
+ children: ReactNode;
35
+ /** A row fixed under the body that never scrolls — a reply box. */
36
+ footer?: ReactNode;
37
+ /** The body's own box: its padding, gap and layout — `px-4 pt-3 pb-4`, `flex flex-col gap-2`. */
38
+ bodyClassName?: string;
39
+ /** The body's scrolling box, for a panel that reads or drives its scroll: a thread that opens at its newest reply. */
40
+ bodyRef?: Ref<HTMLDivElement>;
41
+ /** Fires as the body scrolls. */
42
+ onBodyScroll?: UIEventHandler<HTMLDivElement>;
43
+ /** Placement only: a cap such as `max-h-[60%]` when it shares a column. */
44
+ className?: string;
45
+ }
46
+ export declare function Panel(props: PanelProps): import("react").JSX.Element;
47
+ //# sourceMappingURL=Panel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Panel.d.ts","sourceRoot":"","sources":["../src/Panel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,cAAc,EAAE,MAAM,OAAO,CAAA;AAM3D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,WAAW,UAAU;IACzB,mFAAmF;IACnF,KAAK,EAAE,SAAS,CAAA;IAChB,gEAAgE;IAChE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,6GAA6G;IAC7G,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,6DAA6D;IAC7D,QAAQ,EAAE,SAAS,CAAA;IACnB,mEAAmE;IACnE,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,iGAAiG;IACjG,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,sHAAsH;IACtH,OAAO,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,CAAA;IAC7B,iCAAiC;IACjC,YAAY,CAAC,EAAE,cAAc,CAAC,cAAc,CAAC,CAAA;IAC7C,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAmBD,wBAAgB,KAAK,CAAC,KAAK,EAAE,UAAU,+BActC"}
@@ -7,8 +7,13 @@ import type { ReactNode, Ref, UIEventHandler } from 'react';
7
7
  * right-hand padding looks wider than the left. Katerina saw it in every
8
8
  * scrolling surface of both apps (2026-09-08). Base UI's `ScrollArea` hides
9
9
  * the native bar and draws its own over the content, so nothing moves when
10
- * it appears; the bar shows while the pointer is over the region or the
11
- * content is moving, and fades otherwise.
10
+ * it appears.
11
+ *
12
+ * The bar shows whenever the region has more to scroll to, at rest too
13
+ * (Katerina, 26 September: a long folder list in Peek's side column looked
14
+ * complete, because the bar waited for the pointer). Base UI writes
15
+ * `data-has-overflow-y` / `-x` on the bar while that axis overflows; a region
16
+ * with nothing more to show keeps no bar at all.
12
17
  *
13
18
  * Three boxes, and each class prop lands on one:
14
19
  *
@@ -1 +1 @@
1
- {"version":3,"file":"ScrollArea.d.ts","sourceRoot":"","sources":["../src/ScrollArea.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,cAAc,EAAE,MAAM,OAAO,CAAA;AAI3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,EAAE,UAAU,GAAG,YAAY,GAAG,MAAM,CAAA;IAChD,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,8DAA8D;IAC9D,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,yEAAyE;IACzE,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,CAAA;IACjC,uFAAuF;IACvF,QAAQ,CAAC,EAAE,cAAc,CAAC,cAAc,CAAC,CAAA;IACzC,QAAQ,EAAE,SAAS,CAAA;CACpB;AAKD,wBAAgB,UAAU,CAAC,EAAE,WAAwB,EAAE,SAAS,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,eAAe,+BA4CxJ"}
1
+ {"version":3,"file":"ScrollArea.d.ts","sourceRoot":"","sources":["../src/ScrollArea.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,cAAc,EAAE,MAAM,OAAO,CAAA;AAI3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,EAAE,UAAU,GAAG,YAAY,GAAG,MAAM,CAAA;IAChD,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,8DAA8D;IAC9D,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,yEAAyE;IACzE,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,CAAA;IACjC,uFAAuF;IACvF,QAAQ,CAAC,EAAE,cAAc,CAAC,cAAc,CAAC,CAAA;IACzC,QAAQ,EAAE,SAAS,CAAA;CACpB;AAKD,wBAAgB,UAAU,CAAC,EAAE,WAAwB,EAAE,SAAS,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,eAAe,+BA4CxJ"}
@@ -991,15 +991,28 @@ var INTERACTIVE = ["onClick", "href", "role", "onKeyDown", "onPointerDown"];
991
991
  var noHandmadeHeader = {
992
992
  meta: {
993
993
  type: "problem",
994
- docs: { description: "A column's header bar drawn by hand where ContainerHeader is the part" },
994
+ docs: { description: "A column's header bar drawn by hand, or the package's header on its own in an app, where Panel is the part" },
995
995
  schema: [],
996
996
  messages: {
997
- handmade: "A column's header bar drawn by hand. Use `ContainerHeader` from @estiva-app/ui: 48px, the title, the column's buttons at the right edge (`actions`), a hairline under it.",
997
+ handmade: "A column's header bar drawn by hand. Use `Panel` from @estiva-app/ui: its header (48px, the title, the column's buttons at the right edge in `actions`, a hairline under it) and a body under it that scrolls. For a list beside the page, `ListColumn`.",
998
+ bare: "A panel put together by hand: `ContainerHeader` over a box of your own, which cuts off whatever does not fit. Use `Panel` from @estiva-app/ui: the same header (`title`, `actions`), a body that scrolls whenever it is taller, and a `footer` that stays put. For a list beside the page, `ListColumn`.",
998
999
  ...ESCAPE_MESSAGES
999
1000
  }
1000
1001
  },
1001
1002
  create(context) {
1003
+ const bare2 = /* @__PURE__ */ new Set();
1002
1004
  return {
1005
+ ImportDeclaration(node) {
1006
+ const declaration = node;
1007
+ if (declaration.source.value !== "@estiva-app/ui") return;
1008
+ for (const specifier of declaration.specifiers) if (specifier.type === "ImportSpecifier" && specifier.imported?.name === "ContainerHeader") bare2.add(specifier.local.name);
1009
+ },
1010
+ JSXOpeningElement(node) {
1011
+ const element = node;
1012
+ if (element.name.type !== "JSXIdentifier" || !bare2.has(element.name.name ?? "")) return;
1013
+ if (isEscaped(context, element)) return;
1014
+ context.report({ loc: element.loc, messageId: "bare" });
1015
+ },
1003
1016
  JSXElement(node) {
1004
1017
  const column = node;
1005
1018
  const kids = meaningful(column);
@@ -1389,7 +1402,7 @@ var noCopiedLook = {
1389
1402
  };
1390
1403
 
1391
1404
  // src/eslint/no-native-title.ts
1392
- var PARTS_WITH_A_TITLE = ["CollapsibleSection", "ConfirmDialog", "ContainerHeader", "DialogShell", "ListColumn", "SectionHeader"];
1405
+ var PARTS_WITH_A_TITLE = ["CollapsibleSection", "ConfirmDialog", "ContainerHeader", "DialogShell", "ListColumn", "Panel", "SectionHeader"];
1393
1406
  var child3 = (node, key) => node[key];
1394
1407
  var nameOf4 = (node) => node?.type === "JSXIdentifier" || node?.type === "Identifier" ? node.name : void 0;
1395
1408
  var noNativeTitle = {