@tooee/layout 0.1.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 ADDED
@@ -0,0 +1,5 @@
1
+ # @tooee/layout
2
+
3
+ Standard app chrome (title bar, status bar) for Tooee.
4
+
5
+ Part of the [Tooee](https://github.com/gingerhendrix/tooee) monorepo. See the main repo for documentation.
@@ -0,0 +1,27 @@
1
+ import type { ReactNode, RefObject } from "react";
2
+ import type { ScrollBoxRenderable } from "@opentui/core";
3
+ import type { StatusBarItem } from "./StatusBar.jsx";
4
+ import type { SearchBarProps } from "./SearchBar.jsx";
5
+ export interface AppLayoutSearchBar extends SearchBarProps {
6
+ active: boolean;
7
+ }
8
+ export interface AppLayoutProps {
9
+ titleBar?: {
10
+ title: string;
11
+ subtitle?: string;
12
+ };
13
+ statusBar: {
14
+ items: StatusBarItem[];
15
+ };
16
+ scrollRef?: RefObject<ScrollBoxRenderable | null>;
17
+ scrollProps?: {
18
+ stickyScroll?: boolean;
19
+ stickyStart?: "bottom" | "top";
20
+ focused?: boolean;
21
+ };
22
+ searchBar?: AppLayoutSearchBar;
23
+ overlay?: ReactNode;
24
+ children: ReactNode;
25
+ }
26
+ export declare function AppLayout({ titleBar, statusBar, scrollRef, scrollProps, searchBar, overlay, children, }: AppLayoutProps): ReactNode;
27
+ //# sourceMappingURL=AppLayout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AppLayout.d.ts","sourceRoot":"","sources":["../src/AppLayout.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACjD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AAGxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAEpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAIrD,MAAM,WAAW,kBAAmB,SAAQ,cAAc;IACxD,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC/C,SAAS,EAAE;QAAE,KAAK,EAAE,aAAa,EAAE,CAAA;KAAE,CAAA;IACrC,SAAS,CAAC,EAAE,SAAS,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAA;IACjD,WAAW,CAAC,EAAE;QACZ,YAAY,CAAC,EAAE,OAAO,CAAA;QACtB,WAAW,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAA;QAC9B,OAAO,CAAC,EAAE,OAAO,CAAA;KAClB,CAAA;IACD,SAAS,CAAC,EAAE,kBAAkB,CAAA;IAC9B,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED,wBAAgB,SAAS,CAAC,EACxB,QAAQ,EACR,SAAS,EACT,SAAS,EACT,WAAW,EACX,SAAS,EACT,OAAO,EACP,QAAQ,GACT,EAAE,cAAc,aAqChB"}
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
2
+ import { TitleBar } from "./TitleBar.jsx";
3
+ import { StatusBar } from "./StatusBar.jsx";
4
+ import { SearchBar } from "./SearchBar.jsx";
5
+ import { useTheme } from "@tooee/themes";
6
+ import { useCurrentOverlay } from "@tooee/overlays";
7
+ export function AppLayout({ titleBar, statusBar, scrollRef, scrollProps, searchBar, overlay, children, }) {
8
+ const { theme } = useTheme();
9
+ const contextOverlay = useCurrentOverlay();
10
+ const activeOverlay = overlay ?? contextOverlay;
11
+ return (_jsxs("box", { flexDirection: "column", width: "100%", height: "100%", backgroundColor: theme.background, children: [titleBar && _jsx(TitleBar, { title: titleBar.title, subtitle: titleBar.subtitle }), _jsxs("box", { style: { flexGrow: 1, position: "relative" }, children: [_jsx("scrollbox", { ref: scrollRef, style: { flexGrow: 1 }, stickyScroll: scrollProps?.stickyScroll, stickyStart: scrollProps?.stickyStart, focused: scrollProps?.focused ?? true, children: children }), activeOverlay && (_jsx("box", { position: "absolute", left: 0, top: 0, width: "100%", height: "100%", children: activeOverlay }))] }), searchBar?.active ? (_jsx(SearchBar, { query: searchBar.query, onQueryChange: searchBar.onQueryChange, onSubmit: searchBar.onSubmit, onCancel: searchBar.onCancel, matchCount: searchBar.matchCount, currentMatch: searchBar.currentMatch })) : (_jsx(StatusBar, { items: statusBar.items }))] }));
12
+ }
13
+ //# sourceMappingURL=AppLayout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AppLayout.js","sourceRoot":"","sources":["../src/AppLayout.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAE3C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAE3C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAoBnD,MAAM,UAAU,SAAS,CAAC,EACxB,QAAQ,EACR,SAAS,EACT,SAAS,EACT,WAAW,EACX,SAAS,EACT,OAAO,EACP,QAAQ,GACO;IACf,MAAM,EAAE,KAAK,EAAE,GAAG,QAAQ,EAAE,CAAA;IAC5B,MAAM,cAAc,GAAG,iBAAiB,EAAE,CAAA;IAC1C,MAAM,aAAa,GAAG,OAAO,IAAI,cAAc,CAAA;IAC/C,OAAO,CACL,eAAK,aAAa,EAAC,QAAQ,EAAC,KAAK,EAAC,MAAM,EAAC,MAAM,EAAC,MAAM,EAAC,eAAe,EAAE,KAAK,CAAC,UAAU,aACrF,QAAQ,IAAI,KAAC,QAAQ,IAAC,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,GAAI,EAC7E,eAAK,KAAK,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,aAC/C,oBACE,GAAG,EAAE,SAAS,EACd,KAAK,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,EACtB,YAAY,EAAE,WAAW,EAAE,YAAY,EACvC,WAAW,EAAE,WAAW,EAAE,WAAW,EACrC,OAAO,EAAE,WAAW,EAAE,OAAO,IAAI,IAAI,YAEpC,QAAQ,GACC,EACX,aAAa,IAAI,CAChB,cAAK,QAAQ,EAAC,UAAU,EAAC,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAC,MAAM,EAAC,MAAM,EAAC,MAAM,YACjE,aAAa,GACV,CACP,IACG,EACL,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,CACnB,KAAC,SAAS,IACR,KAAK,EAAE,SAAS,CAAC,KAAK,EACtB,aAAa,EAAE,SAAS,CAAC,aAAa,EACtC,QAAQ,EAAE,SAAS,CAAC,QAAQ,EAC5B,QAAQ,EAAE,SAAS,CAAC,QAAQ,EAC5B,UAAU,EAAE,SAAS,CAAC,UAAU,EAChC,YAAY,EAAE,SAAS,CAAC,YAAY,GACpC,CACH,CAAC,CAAC,CAAC,CACF,KAAC,SAAS,IAAC,KAAK,EAAE,SAAS,CAAC,KAAK,GAAI,CACtC,IACG,CACP,CAAA;AACH,CAAC"}
@@ -0,0 +1,10 @@
1
+ export interface SearchBarProps {
2
+ query: string;
3
+ onQueryChange: (query: string) => void;
4
+ onSubmit: () => void;
5
+ onCancel: () => void;
6
+ matchCount?: number;
7
+ currentMatch?: number;
8
+ }
9
+ export declare function SearchBar({ query, onQueryChange, onSubmit, onCancel: _onCancel, matchCount, currentMatch, }: SearchBarProps): import("react").ReactNode;
10
+ //# sourceMappingURL=SearchBar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SearchBar.d.ts","sourceRoot":"","sources":["../src/SearchBar.tsx"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACtC,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,aAAa,EACb,QAAQ,EACR,QAAQ,EAAE,SAAS,EACnB,UAAU,EACV,YAAY,GACb,EAAE,cAAc,6BAoChB"}
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
2
+ import { useTheme } from "@tooee/themes";
3
+ export function SearchBar({ query, onQueryChange, onSubmit, onCancel: _onCancel, matchCount, currentMatch, }) {
4
+ const { theme } = useTheme();
5
+ const matchDisplay = matchCount !== undefined && matchCount > 0
6
+ ? `${(currentMatch ?? 0) + 1}/${matchCount}`
7
+ : matchCount === 0 && query.length > 0
8
+ ? "No matches"
9
+ : "";
10
+ return (_jsxs("box", { style: {
11
+ flexDirection: "row",
12
+ flexShrink: 0,
13
+ backgroundColor: theme.backgroundPanel,
14
+ paddingLeft: 1,
15
+ paddingRight: 1,
16
+ }, children: [_jsx("text", { content: "/", style: { fg: theme.accent } }), _jsx("input", { value: query, focused: true, onInput: onQueryChange, onSubmit: onSubmit, backgroundColor: "transparent", focusedBackgroundColor: "transparent", textColor: theme.text, cursorColor: theme.accent, cursorStyle: { style: "line", blinking: true }, style: { flexGrow: 1 } }), matchDisplay ? _jsx("text", { content: ` ${matchDisplay}`, style: { fg: theme.textMuted } }) : null] }));
17
+ }
18
+ //# sourceMappingURL=SearchBar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SearchBar.js","sourceRoot":"","sources":["../src/SearchBar.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAWxC,MAAM,UAAU,SAAS,CAAC,EACxB,KAAK,EACL,aAAa,EACb,QAAQ,EACR,QAAQ,EAAE,SAAS,EACnB,UAAU,EACV,YAAY,GACG;IACf,MAAM,EAAE,KAAK,EAAE,GAAG,QAAQ,EAAE,CAAA;IAE5B,MAAM,YAAY,GAChB,UAAU,KAAK,SAAS,IAAI,UAAU,GAAG,CAAC;QACxC,CAAC,CAAC,GAAG,CAAC,YAAY,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,UAAU,EAAE;QAC5C,CAAC,CAAC,UAAU,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;YACpC,CAAC,CAAC,YAAY;YACd,CAAC,CAAC,EAAE,CAAA;IAEV,OAAO,CACL,eACE,KAAK,EAAE;YACL,aAAa,EAAE,KAAK;YACpB,UAAU,EAAE,CAAC;YACb,eAAe,EAAE,KAAK,CAAC,eAAe;YACtC,WAAW,EAAE,CAAC;YACd,YAAY,EAAE,CAAC;SAChB,aAED,eAAM,OAAO,EAAC,GAAG,EAAC,KAAK,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,MAAM,EAAE,GAAI,EACjD,gBACE,KAAK,EAAE,KAAK,EACZ,OAAO,QACP,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,QAAQ,EAClB,eAAe,EAAC,aAAa,EAC7B,sBAAsB,EAAC,aAAa,EACpC,SAAS,EAAE,KAAK,CAAC,IAAI,EACrB,WAAW,EAAE,KAAK,CAAC,MAAM,EACzB,WAAW,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,EAC9C,KAAK,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,GACtB,EACD,YAAY,CAAC,CAAC,CAAC,eAAM,OAAO,EAAE,IAAI,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,SAAS,EAAE,GAAI,CAAC,CAAC,CAAC,IAAI,IACxF,CACP,CAAA;AACH,CAAC"}
@@ -0,0 +1,10 @@
1
+ interface StatusBarProps {
2
+ items: StatusBarItem[];
3
+ }
4
+ export interface StatusBarItem {
5
+ label: string;
6
+ value?: string;
7
+ }
8
+ export declare function StatusBar({ items }: StatusBarProps): import("react").ReactNode;
9
+ export {};
10
+ //# sourceMappingURL=StatusBar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StatusBar.d.ts","sourceRoot":"","sources":["../src/StatusBar.tsx"],"names":[],"mappings":"AAEA,UAAU,cAAc;IACtB,KAAK,EAAE,aAAa,EAAE,CAAA;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,cAAc,6BAqBlD"}
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
2
+ import { useTheme } from "@tooee/themes";
3
+ export function StatusBar({ items }) {
4
+ const { theme } = useTheme();
5
+ return (_jsx("box", { style: {
6
+ flexDirection: "row",
7
+ flexShrink: 0,
8
+ backgroundColor: theme.backgroundPanel,
9
+ padding: 0,
10
+ paddingLeft: 1,
11
+ paddingRight: 1,
12
+ }, children: items.map((item, index) => (_jsxs("box", { style: { marginRight: 2, flexDirection: "row" }, children: [_jsx("text", { content: item.label, style: { fg: theme.textMuted } }), item.value && _jsx("text", { content: ` ${item.value}`, style: { fg: theme.text } })] }, index))) }));
13
+ }
14
+ //# sourceMappingURL=StatusBar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StatusBar.js","sourceRoot":"","sources":["../src/StatusBar.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAWxC,MAAM,UAAU,SAAS,CAAC,EAAE,KAAK,EAAkB;IACjD,MAAM,EAAE,KAAK,EAAE,GAAG,QAAQ,EAAE,CAAA;IAC5B,OAAO,CACL,cACE,KAAK,EAAE;YACL,aAAa,EAAE,KAAK;YACpB,UAAU,EAAE,CAAC;YACb,eAAe,EAAE,KAAK,CAAC,eAAe;YACtC,OAAO,EAAE,CAAC;YACV,WAAW,EAAE,CAAC;YACd,YAAY,EAAE,CAAC;SAChB,YAEA,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAC1B,eAAiB,KAAK,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,aAAa,EAAE,KAAK,EAAE,aAC9D,eAAM,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,SAAS,EAAE,GAAI,EAC5D,IAAI,CAAC,KAAK,IAAI,eAAM,OAAO,EAAE,IAAI,IAAI,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,IAAI,EAAE,GAAI,KAFrE,KAAK,CAGT,CACP,CAAC,GACE,CACP,CAAA;AACH,CAAC"}
@@ -0,0 +1,7 @@
1
+ interface TitleBarProps {
2
+ title: string;
3
+ subtitle?: string;
4
+ }
5
+ export declare function TitleBar({ title, subtitle }: TitleBarProps): import("react").ReactNode;
6
+ export {};
7
+ //# sourceMappingURL=TitleBar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TitleBar.d.ts","sourceRoot":"","sources":["../src/TitleBar.tsx"],"names":[],"mappings":"AAEA,UAAU,aAAa;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,QAAQ,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,aAAa,6BAiB1D"}
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
2
+ import { useTheme } from "@tooee/themes";
3
+ export function TitleBar({ title, subtitle }) {
4
+ const { theme } = useTheme();
5
+ return (_jsxs("box", { style: {
6
+ flexDirection: "row",
7
+ flexShrink: 0,
8
+ backgroundColor: theme.backgroundPanel,
9
+ padding: 0,
10
+ paddingLeft: 1,
11
+ paddingRight: 1,
12
+ }, children: [_jsx("text", { content: title, style: { fg: theme.primary } }), subtitle && _jsx("text", { content: ` — ${subtitle}`, style: { fg: theme.textMuted } })] }));
13
+ }
14
+ //# sourceMappingURL=TitleBar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TitleBar.js","sourceRoot":"","sources":["../src/TitleBar.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAOxC,MAAM,UAAU,QAAQ,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAiB;IACzD,MAAM,EAAE,KAAK,EAAE,GAAG,QAAQ,EAAE,CAAA;IAC5B,OAAO,CACL,eACE,KAAK,EAAE;YACL,aAAa,EAAE,KAAK;YACpB,UAAU,EAAE,CAAC;YACb,eAAe,EAAE,KAAK,CAAC,eAAe;YACtC,OAAO,EAAE,CAAC;YACV,WAAW,EAAE,CAAC;YACd,YAAY,EAAE,CAAC;SAChB,aAED,eAAM,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,OAAO,EAAE,GAAI,EACrD,QAAQ,IAAI,eAAM,OAAO,EAAE,MAAM,QAAQ,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,SAAS,EAAE,GAAI,IAC5E,CACP,CAAA;AACH,CAAC"}
@@ -0,0 +1,8 @@
1
+ export { AppLayout } from "./AppLayout.jsx";
2
+ export type { AppLayoutProps, AppLayoutSearchBar } from "./AppLayout.jsx";
3
+ export { StatusBar } from "./StatusBar.jsx";
4
+ export type { StatusBarItem } from "./StatusBar.jsx";
5
+ export { TitleBar } from "./TitleBar.jsx";
6
+ export { SearchBar } from "./SearchBar.jsx";
7
+ export type { SearchBarProps } from "./SearchBar.jsx";
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACzE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { AppLayout } from "./AppLayout.jsx";
2
+ export { StatusBar } from "./StatusBar.jsx";
3
+ export { TitleBar } from "./TitleBar.jsx";
4
+ export { SearchBar } from "./SearchBar.jsx";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAE3C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAE3C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA"}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@tooee/layout",
3
+ "version": "0.1.0",
4
+ "description": "Standard app chrome (title bar, status bar) for Tooee",
5
+ "license": "MIT",
6
+ "author": "Gareth Andrew",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/gingerhendrix/tooee.git",
10
+ "directory": "packages/layout"
11
+ },
12
+ "homepage": "https://github.com/gingerhendrix/tooee",
13
+ "bugs": "https://github.com/gingerhendrix/tooee/issues",
14
+ "keywords": ["tui", "terminal", "cli", "opentui", "layout"],
15
+ "type": "module",
16
+ "exports": {
17
+ ".": {
18
+ "import": {
19
+ "@tooee/source": "./src/index.ts",
20
+ "default": "./dist/index.js"
21
+ }
22
+ }
23
+ },
24
+ "files": ["dist", "src"],
25
+ "scripts": {
26
+ "typecheck": "tsc --noEmit"
27
+ },
28
+ "dependencies": {
29
+ "@tooee/themes": "0.0.0",
30
+ "@tooee/overlays": "0.0.0"
31
+ },
32
+ "devDependencies": {
33
+ "@opentui/core": "^0.1.67",
34
+ "@opentui/react": "^0.1.67",
35
+ "@types/bun": "^1.3.5",
36
+ "@types/react": "^19.1.10",
37
+ "typescript": "^5.8.3"
38
+ },
39
+ "peerDependencies": {
40
+ "@opentui/core": "^0.1.67",
41
+ "@opentui/react": "^0.1.67",
42
+ "react": "^18.0.0 || ^19.0.0"
43
+ }
44
+ }
@@ -0,0 +1,74 @@
1
+ import type { ReactNode, RefObject } from "react"
2
+ import type { ScrollBoxRenderable } from "@opentui/core"
3
+ import { TitleBar } from "./TitleBar.jsx"
4
+ import { StatusBar } from "./StatusBar.jsx"
5
+ import type { StatusBarItem } from "./StatusBar.jsx"
6
+ import { SearchBar } from "./SearchBar.jsx"
7
+ import type { SearchBarProps } from "./SearchBar.jsx"
8
+ import { useTheme } from "@tooee/themes"
9
+ import { useCurrentOverlay } from "@tooee/overlays"
10
+
11
+ export interface AppLayoutSearchBar extends SearchBarProps {
12
+ active: boolean
13
+ }
14
+
15
+ export interface AppLayoutProps {
16
+ titleBar?: { title: string; subtitle?: string }
17
+ statusBar: { items: StatusBarItem[] }
18
+ scrollRef?: RefObject<ScrollBoxRenderable | null>
19
+ scrollProps?: {
20
+ stickyScroll?: boolean
21
+ stickyStart?: "bottom" | "top"
22
+ focused?: boolean
23
+ }
24
+ searchBar?: AppLayoutSearchBar
25
+ overlay?: ReactNode
26
+ children: ReactNode
27
+ }
28
+
29
+ export function AppLayout({
30
+ titleBar,
31
+ statusBar,
32
+ scrollRef,
33
+ scrollProps,
34
+ searchBar,
35
+ overlay,
36
+ children,
37
+ }: AppLayoutProps) {
38
+ const { theme } = useTheme()
39
+ const contextOverlay = useCurrentOverlay()
40
+ const activeOverlay = overlay ?? contextOverlay
41
+ return (
42
+ <box flexDirection="column" width="100%" height="100%" backgroundColor={theme.background}>
43
+ {titleBar && <TitleBar title={titleBar.title} subtitle={titleBar.subtitle} />}
44
+ <box style={{ flexGrow: 1, position: "relative" }}>
45
+ <scrollbox
46
+ ref={scrollRef}
47
+ style={{ flexGrow: 1 }}
48
+ stickyScroll={scrollProps?.stickyScroll}
49
+ stickyStart={scrollProps?.stickyStart}
50
+ focused={scrollProps?.focused ?? true}
51
+ >
52
+ {children}
53
+ </scrollbox>
54
+ {activeOverlay && (
55
+ <box position="absolute" left={0} top={0} width="100%" height="100%">
56
+ {activeOverlay}
57
+ </box>
58
+ )}
59
+ </box>
60
+ {searchBar?.active ? (
61
+ <SearchBar
62
+ query={searchBar.query}
63
+ onQueryChange={searchBar.onQueryChange}
64
+ onSubmit={searchBar.onSubmit}
65
+ onCancel={searchBar.onCancel}
66
+ matchCount={searchBar.matchCount}
67
+ currentMatch={searchBar.currentMatch}
68
+ />
69
+ ) : (
70
+ <StatusBar items={statusBar.items} />
71
+ )}
72
+ </box>
73
+ )
74
+ }
@@ -0,0 +1,55 @@
1
+ import { useTheme } from "@tooee/themes"
2
+
3
+ export interface SearchBarProps {
4
+ query: string
5
+ onQueryChange: (query: string) => void
6
+ onSubmit: () => void
7
+ onCancel: () => void
8
+ matchCount?: number
9
+ currentMatch?: number
10
+ }
11
+
12
+ export function SearchBar({
13
+ query,
14
+ onQueryChange,
15
+ onSubmit,
16
+ onCancel: _onCancel,
17
+ matchCount,
18
+ currentMatch,
19
+ }: SearchBarProps) {
20
+ const { theme } = useTheme()
21
+
22
+ const matchDisplay =
23
+ matchCount !== undefined && matchCount > 0
24
+ ? `${(currentMatch ?? 0) + 1}/${matchCount}`
25
+ : matchCount === 0 && query.length > 0
26
+ ? "No matches"
27
+ : ""
28
+
29
+ return (
30
+ <box
31
+ style={{
32
+ flexDirection: "row",
33
+ flexShrink: 0,
34
+ backgroundColor: theme.backgroundPanel,
35
+ paddingLeft: 1,
36
+ paddingRight: 1,
37
+ }}
38
+ >
39
+ <text content="/" style={{ fg: theme.accent }} />
40
+ <input
41
+ value={query}
42
+ focused
43
+ onInput={onQueryChange}
44
+ onSubmit={onSubmit}
45
+ backgroundColor="transparent"
46
+ focusedBackgroundColor="transparent"
47
+ textColor={theme.text}
48
+ cursorColor={theme.accent}
49
+ cursorStyle={{ style: "line", blinking: true }}
50
+ style={{ flexGrow: 1 }}
51
+ />
52
+ {matchDisplay ? <text content={` ${matchDisplay}`} style={{ fg: theme.textMuted }} /> : null}
53
+ </box>
54
+ )
55
+ }
@@ -0,0 +1,33 @@
1
+ import { useTheme } from "@tooee/themes"
2
+
3
+ interface StatusBarProps {
4
+ items: StatusBarItem[]
5
+ }
6
+
7
+ export interface StatusBarItem {
8
+ label: string
9
+ value?: string
10
+ }
11
+
12
+ export function StatusBar({ items }: StatusBarProps) {
13
+ const { theme } = useTheme()
14
+ return (
15
+ <box
16
+ style={{
17
+ flexDirection: "row",
18
+ flexShrink: 0,
19
+ backgroundColor: theme.backgroundPanel,
20
+ padding: 0,
21
+ paddingLeft: 1,
22
+ paddingRight: 1,
23
+ }}
24
+ >
25
+ {items.map((item, index) => (
26
+ <box key={index} style={{ marginRight: 2, flexDirection: "row" }}>
27
+ <text content={item.label} style={{ fg: theme.textMuted }} />
28
+ {item.value && <text content={` ${item.value}`} style={{ fg: theme.text }} />}
29
+ </box>
30
+ ))}
31
+ </box>
32
+ )
33
+ }
@@ -0,0 +1,25 @@
1
+ import { useTheme } from "@tooee/themes"
2
+
3
+ interface TitleBarProps {
4
+ title: string
5
+ subtitle?: string
6
+ }
7
+
8
+ export function TitleBar({ title, subtitle }: TitleBarProps) {
9
+ const { theme } = useTheme()
10
+ return (
11
+ <box
12
+ style={{
13
+ flexDirection: "row",
14
+ flexShrink: 0,
15
+ backgroundColor: theme.backgroundPanel,
16
+ padding: 0,
17
+ paddingLeft: 1,
18
+ paddingRight: 1,
19
+ }}
20
+ >
21
+ <text content={title} style={{ fg: theme.primary }} />
22
+ {subtitle && <text content={` — ${subtitle}`} style={{ fg: theme.textMuted }} />}
23
+ </box>
24
+ )
25
+ }
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ export { AppLayout } from "./AppLayout.jsx"
2
+ export type { AppLayoutProps, AppLayoutSearchBar } from "./AppLayout.jsx"
3
+ export { StatusBar } from "./StatusBar.jsx"
4
+ export type { StatusBarItem } from "./StatusBar.jsx"
5
+ export { TitleBar } from "./TitleBar.jsx"
6
+ export { SearchBar } from "./SearchBar.jsx"
7
+ export type { SearchBarProps } from "./SearchBar.jsx"