elbe-ui 0.4.18 → 0.4.20

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/dist/index.d.ts CHANGED
@@ -1,10 +1,9 @@
1
1
  import "./elbe.css";
2
+ export * as wouter from "wouter";
2
3
  export * from "./api/api_worker";
3
4
  export * from "./api/error";
4
5
  export * from "./api/errors";
5
6
  export * from "./bit/bit";
6
- export * from "./bit/old/old_bit";
7
- export * from "./bit/old/old_ctrl_bit";
8
7
  export * from "./ui/util/confirm_dialog";
9
8
  export * from "./ui/util/ctx_toolbar";
10
9
  export * from "./ui/util/l10n/l10n";
@@ -14,12 +13,14 @@ export * from "./ui/util/util";
14
13
  export * from "./ui/components/base/box";
15
14
  export * from "./ui/components/base/card";
16
15
  export * from "./ui/components/base/padded";
16
+ export * from "./ui/components/routing/route";
17
17
  export * from "./ui/components/button/button";
18
18
  export * from "./ui/components/button/choose_button";
19
19
  export * from "./ui/components/button/icon_button";
20
20
  export * from "./ui/components/button/toggle_button";
21
21
  export * from "./ui/components/layout/alignment";
22
22
  export * from "./ui/components/layout/app_base";
23
+ export * from "./ui/components/layout/ctx_app_base";
23
24
  export * from "./ui/components/layout/flex";
24
25
  export * from "./ui/components/layout/header";
25
26
  export * from "./ui/components/layout/page";
package/dist/index.js CHANGED
@@ -1,13 +1,12 @@
1
1
  import { jsx as _jsx } from "preact/jsx-runtime";
2
2
  import * as Lucide from "lucide-react";
3
3
  import "./elbe.css";
4
+ export * as wouter from "wouter";
4
5
  // exports
5
6
  export * from "./api/api_worker";
6
7
  export * from "./api/error";
7
8
  export * from "./api/errors";
8
9
  export * from "./bit/bit";
9
- export * from "./bit/old/old_bit";
10
- export * from "./bit/old/old_ctrl_bit";
11
10
  export * from "./ui/util/confirm_dialog";
12
11
  export * from "./ui/util/ctx_toolbar";
13
12
  export * from "./ui/util/l10n/l10n";
@@ -17,12 +16,14 @@ export * from "./ui/util/util";
17
16
  export * from "./ui/components/base/box";
18
17
  export * from "./ui/components/base/card";
19
18
  export * from "./ui/components/base/padded";
19
+ export * from "./ui/components/routing/route";
20
20
  export * from "./ui/components/button/button";
21
21
  export * from "./ui/components/button/choose_button";
22
22
  export * from "./ui/components/button/icon_button";
23
23
  export * from "./ui/components/button/toggle_button";
24
24
  export * from "./ui/components/layout/alignment";
25
25
  export * from "./ui/components/layout/app_base";
26
+ export * from "./ui/components/layout/ctx_app_base";
26
27
  export * from "./ui/components/layout/flex";
27
28
  export * from "./ui/components/layout/header";
28
29
  export * from "./ui/components/layout/page";
@@ -43,5 +43,9 @@ function _btn(_a, manner) {
43
43
  export function Icon(p) {
44
44
  if (!p.icon)
45
45
  return null;
46
- return (_jsx("span", Object.assign({}, applyProps("icon", p), { children: typeof p.icon === "function" ? p.icon({}) : p.icon })));
46
+ return (_jsx("div", Object.assign({}, applyProps("icon", p, null, {
47
+ display: "flex",
48
+ alignItems: "center",
49
+ justifyContent: "center",
50
+ }), { children: typeof p.icon === "function" ? p.icon({}) : p.icon })));
47
51
  }
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
2
  import { useSignal } from "@preact/signals";
3
3
  import { icons } from "lucide-react";
4
- import { route } from "preact-router";
4
+ import { useLocation } from "wouter";
5
5
  import { ElbeDialog, Icon, Icons, toElbeError, } from "../..";
6
6
  import { _maybeL10n } from "../util/l10n/_l10n_util";
7
7
  export function ErrorView({ error, retry, debug, }) {
@@ -37,5 +37,6 @@ export function PrettyErrorView({ error, retry, labels = {
37
37
  var _a, _b, _c, _d, _e, _f;
38
38
  const l10n = _maybeL10n();
39
39
  const openSig = useSignal(false);
40
- return (_jsxs("div", { class: "column padded cross-center", style: "margin: 1rem 0", children: [_jsx(Icon, { icon: (_a = error.icon) !== null && _a !== void 0 ? _a : icons.OctagonAlert }), _jsx("h4", { style: "margin: 0", children: (_b = l10n === null || l10n === void 0 ? void 0 : l10n.inline(error.message)) !== null && _b !== void 0 ? _b : "error" }), _jsx("span", { class: "pointer", onClick: () => (openSig.value = true), children: (_c = l10n === null || l10n === void 0 ? void 0 : l10n.inline(error.description)) !== null && _c !== void 0 ? _c : "" }), retry && (_jsxs("button", { class: "action", onClick: () => retry(), children: [_jsx(Icons.RotateCcw, {}), " ", (_d = l10n === null || l10n === void 0 ? void 0 : l10n.inline(labels.retry)) !== null && _d !== void 0 ? _d : "retry"] })), error.code === 404 && (_jsxs("button", { class: "action", onClick: () => route("/"), children: [_jsx(Icons.House, {}), (_e = l10n === null || l10n === void 0 ? void 0 : l10n.inline(labels.home)) !== null && _e !== void 0 ? _e : "go home"] })), _jsx(ElbeDialog, { title: (_f = l10n === null || l10n === void 0 ? void 0 : l10n.inline(labels.details)) !== null && _f !== void 0 ? _f : "error details", open: openSig.value, onClose: () => (openSig.value = false), children: _jsx("pre", { class: "card inverse", children: `code: ${error.code}\n\n` + JSON.stringify(error.details, null, 2) }) })] }));
40
+ const [loc, navigate] = useLocation();
41
+ return (_jsxs("div", { class: "column padded cross-center", style: "margin: 1rem 0", children: [_jsx(Icon, { icon: (_a = error.icon) !== null && _a !== void 0 ? _a : icons.OctagonAlert }), _jsx("h4", { style: "margin: 0", children: (_b = l10n === null || l10n === void 0 ? void 0 : l10n.inline(error.message)) !== null && _b !== void 0 ? _b : "error" }), _jsx("span", { class: "pointer", onClick: () => (openSig.value = true), children: (_c = l10n === null || l10n === void 0 ? void 0 : l10n.inline(error.description)) !== null && _c !== void 0 ? _c : "" }), retry && (_jsxs("button", { class: "action", onClick: () => retry(), children: [_jsx(Icons.RotateCcw, {}), " ", (_d = l10n === null || l10n === void 0 ? void 0 : l10n.inline(labels.retry)) !== null && _d !== void 0 ? _d : "retry"] })), error.code === 404 && (_jsxs("button", { class: "action", onClick: () => navigate("/", { replace: true }), children: [_jsx(Icons.House, {}), (_e = l10n === null || l10n === void 0 ? void 0 : l10n.inline(labels.home)) !== null && _e !== void 0 ? _e : "go home"] })), _jsx(ElbeDialog, { title: (_f = l10n === null || l10n === void 0 ? void 0 : l10n.inline(labels.details)) !== null && _f !== void 0 ? _f : "error details", open: openSig.value, onClose: () => (openSig.value = false), children: _jsx("pre", { class: "card inverse", children: `code: ${error.code}\n\n` + JSON.stringify(error.details, null, 2) }) })] }));
41
42
  }
@@ -10,7 +10,7 @@ export function Footer({ left, right, copyright, version, legal, marginTop, }) {
10
10
  borderTopLeftRadius: layoutMode === "wide" ? "var(--g-radius)" : null,
11
11
  color: "color-mix(in srgb, var(--c-context-front) 60%, transparent)",
12
12
  marginTop: `${marginTop !== null && marginTop !== void 0 ? marginTop : 0}rem`,
13
- }, children: _jsxs(Column, { gap: 0.5, children: [_jsxs(Row, { main: "space-between", cross: "start", children: [left && (_jsx(Column, { gap: 0.5, flex: 1, cross: "start", children: left.map((item) => item.label ? _jsx(_Link, Object.assign({}, item)) : item) })), right && (_jsx(Column, { gap: 0.5, flex: 1, cross: "end", children: right.map((item) => item.label ? _jsx(_Link, Object.assign({}, item)) : item) }))] }), (left || right) && (copyright || version || legal) && (_jsx("hr", { style: { opacity: 0.7 } })), (copyright || version || legal) && (_jsxs(Row, { children: [copyright &&
13
+ }, children: _jsxs(Column, { gap: 0.5, children: [(left || right) && (_jsxs(Row, { main: "space-between", cross: "start", children: [left && (_jsx(Column, { gap: 0.5, flex: 1, cross: "start", children: left.map((item) => item.label ? _jsx(_Link, Object.assign({}, item)) : item) })), right && (_jsx(Column, { gap: 0.5, flex: 1, cross: "end", children: right.map((item) => item.label ? _jsx(_Link, Object.assign({}, item)) : item) }))] })), (left || right) && (copyright || version || legal) && (_jsx("hr", { style: { opacity: 0.7 } })), (copyright || version || legal) && (_jsxs(Row, { children: [copyright &&
14
14
  (typeof copyright === "string" ? _jsx("b", { children: copyright }) : copyright), version && _jsx(_Version, { version: version }), _jsx(FlexSpace, {}), legal && _jsx(_Link, Object.assign({}, legal))] }))] }) }));
15
15
  }
16
16
  function _Version({ version }) {
@@ -1,21 +1,15 @@
1
- import { VNode } from "preact";
2
- import { NestedArray } from "preact-iso";
3
- import { ElbeChild, HeaderLogos } from "../../..";
4
- import { MenuItem } from "./menu";
1
+ import { ElbeChild, ElbeRoute, HeaderLogos } from "../../..";
5
2
  export type AppBaseProps = HeaderLogos & {
6
- initial?: string;
7
- menu: MenuItem[];
8
3
  globalActions?: ElbeChild[];
9
- children: NestedArray<VNode>;
4
+ children: ElbeRoute | ElbeRoute[];
5
+ hashBasedRouting?: boolean;
10
6
  };
11
7
  /**
12
- * app base is a layout component that provides a side menu and a content area.
8
+ * app base is a layout component that provides an optional side menu and a content area.
13
9
  * it is designed to be used as a base for other components and is
14
10
  * used to create a consistent layout for pages. You can also pass global actions
15
11
  * that will be displayed in the header of all pages.
12
+ *
13
+ * Provide `wouter.Route` or `MenuRoute` components as children to define the routes and menu items.
16
14
  */
17
- export declare function AppBase(p: AppBaseProps): import("preact").JSX.Element;
18
- export declare const Redirect: (p: {
19
- path: string;
20
- to: string;
21
- }) => null;
15
+ export declare function AppBase(p: AppBaseProps): import("preact/compat").JSX.Element;
@@ -1,18 +1,28 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
- import { Router, useLocation } from "preact-iso";
3
- import { useEffect, useState } from "preact/compat";
4
- import { Box } from "../../..";
2
+ import { useMemo, useState } from "preact/compat";
3
+ import { Router } from "wouter";
4
+ import { useHashLocation } from "wouter/use-hash-location";
5
+ import { Box, isMenuRoute, wouter, } from "../../..";
5
6
  import { AppBaseContext } from "./ctx_app_base";
6
7
  import { Menu } from "./menu";
7
8
  /**
8
- * app base is a layout component that provides a side menu and a content area.
9
+ * app base is a layout component that provides an optional side menu and a content area.
9
10
  * it is designed to be used as a base for other components and is
10
11
  * used to create a consistent layout for pages. You can also pass global actions
11
12
  * that will be displayed in the header of all pages.
13
+ *
14
+ * Provide `wouter.Route` or `MenuRoute` components as children to define the routes and menu items.
12
15
  */
13
16
  export function AppBase(p) {
14
- var _a, _b;
17
+ return (_jsx(Router, { hook: p.hashBasedRouting ? useHashLocation : undefined, children: _jsx(_AppBase, Object.assign({}, p)) }));
18
+ }
19
+ function _AppBase(p) {
20
+ var _a;
15
21
  const [menuOpen, setMenuOpen] = useState(false);
22
+ const menuItems = useMemo(() => {
23
+ return _extractMenuItems(p.children);
24
+ }, [p.children]);
25
+ const [location, navigate] = wouter.useLocation();
16
26
  return (_jsx(AppBaseContext.Provider, { value: {
17
27
  menuOpen: menuOpen,
18
28
  icons: {
@@ -23,16 +33,20 @@ export function AppBase(p) {
23
33
  },
24
34
  globalActions: (_a = p.globalActions) !== null && _a !== void 0 ? _a : [],
25
35
  setMenuOpen: (b) => setMenuOpen(b),
36
+ go: (p, replace) => navigate(p, { replace: replace !== null && replace !== void 0 ? replace : false }),
26
37
  }, children: _jsxs(Box, { scheme: "primary", style: {
27
38
  display: "flex",
28
39
  width: "100%",
29
40
  minHeight: "100vh",
30
- }, children: [_jsx(Menu, { items: p.menu }), _jsx("div", { style: { flex: 1 }, children: _jsxs(Router, { children: [_jsx(Redirect, { path: "/", to: `/${(_b = p.initial) !== null && _b !== void 0 ? _b : p.menu[0].id}` }), p.children] }) })] }) }));
41
+ }, children: [menuItems.length > 0 && _jsx(Menu, { items: menuItems }), _jsx("div", { style: { flex: 1 }, children: _jsx(wouter.Switch, { children: p.children }) })] }) }));
42
+ }
43
+ function _extractMenuItems(children) {
44
+ const childs = Array.isArray(children) ? children : [children];
45
+ const items = [];
46
+ for (const child of childs) {
47
+ if (!isMenuRoute(child))
48
+ continue;
49
+ items.push(child.props);
50
+ }
51
+ return items;
31
52
  }
32
- export const Redirect = (p) => {
33
- const location = useLocation();
34
- useEffect(() => {
35
- location.route(p.to, true);
36
- }, [p.to]);
37
- return null; // This component doesn't render anything
38
- };
@@ -4,11 +4,11 @@ export interface _AppBaseState {
4
4
  menuOpen: boolean;
5
5
  icons: HeaderLogos;
6
6
  globalActions: ElbeChild[];
7
+ go: (path: string, replace?: boolean) => void;
7
8
  }
8
- interface _AppBaseControl extends _AppBaseState {
9
+ export interface _AppBaseControl extends _AppBaseState {
9
10
  setMenuOpen: (open: boolean) => void;
10
11
  }
11
12
  export declare const AppBaseContext: import("preact").Context<_AppBaseControl | null>;
12
13
  export declare function useAppBase(): _AppBaseControl;
13
14
  export declare function maybeAppBase(): _AppBaseControl | null;
14
- export {};
@@ -1,4 +1,4 @@
1
- import { ElbeChild } from "../../..";
1
+ import { ElbeChild, ElbeColorSchemes } from "../../..";
2
2
  export type HeaderLogos = {
3
3
  logo?: string | ElbeChild;
4
4
  logoDark?: string | ElbeChild;
@@ -10,6 +10,7 @@ export type HeaderProps = HeaderLogos & {
10
10
  title: string | ElbeChild;
11
11
  centerTitle?: boolean;
12
12
  actions?: ElbeChild[];
13
+ scheme?: ElbeColorSchemes;
13
14
  };
14
15
  export declare function Header(p: HeaderProps): import("preact").JSX.Element;
15
16
  export declare function BackButton(p: {
@@ -18,3 +19,7 @@ export declare function BackButton(p: {
18
19
  export declare function CloseButton(p: {
19
20
  onTap: () => void;
20
21
  }): import("preact").JSX.Element;
22
+ export declare function _HeaderTitle(p: {
23
+ title: string | ElbeChild;
24
+ center: boolean;
25
+ }): import("preact").JSX.Element;
@@ -1,19 +1,19 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
2
  import { ChevronLeft, MenuIcon, XIcon } from "lucide-react";
3
- import { useEffect, useState } from "preact/hooks";
3
+ import { useEffect, useMemo, useState } from "preact/hooks";
4
4
  import { Card, IconButton, Text, useLayoutMode, useSiteScroll, useTheme, useThemeConfig, } from "../../..";
5
5
  import { maybeAppBase } from "./ctx_app_base";
6
6
  import { _Toolbar } from "./toolbar";
7
7
  const _backBtn = _jsx(BackButton, { onTap: () => history.go(-1) });
8
8
  const _closeBtn = _jsx(CloseButton, { onTap: () => history.go(-1) });
9
9
  export function Header(p) {
10
- var _a, _b, _c, _d, _e, _f;
10
+ var _a, _b, _c, _d, _e, _f, _g, _h;
11
11
  const appBase = maybeAppBase();
12
12
  const layoutMode = useLayoutMode();
13
13
  const scroll = useSiteScroll();
14
14
  const tConfig = useThemeConfig();
15
15
  const theme = useTheme();
16
- return (_jsxs(Card, { padding: 0, scheme: "primary", bordered: true, frosted: !tConfig.highVis, sharp: true, style: {
16
+ return (_jsxs(Card, { padding: 0, scheme: (_a = p.scheme) !== null && _a !== void 0 ? _a : "primary", bordered: true, frosted: !tConfig.highVis, sharp: true, style: {
17
17
  position: "sticky",
18
18
  top: 0,
19
19
  left: 0,
@@ -31,9 +31,7 @@ export function Header(p) {
31
31
  }, children: [p.leading && p.leading !== "back" && p.leading !== "close"
32
32
  ? p.leading
33
33
  : appBase &&
34
- layoutMode != "wide" && (_jsx(IconButton.plain, { ariaLabel: "open/close menu", onTap: () => appBase.setMenuOpen(!appBase.menuOpen), icon: MenuIcon })), p.leading === "back" && _backBtn, p.leading === "close" && _closeBtn, _jsx(_Logo, { logo: (_a = p.logo) !== null && _a !== void 0 ? _a : appBase === null || appBase === void 0 ? void 0 : appBase.icons.logo, logoDark: (_b = p.logoDark) !== null && _b !== void 0 ? _b : appBase === null || appBase === void 0 ? void 0 : appBase.icons.logoDark, lMargin: 0.5 }), typeof p.title === "string" ? (_jsx(Text.h3, { style: {
35
- marginLeft: !appBase || layoutMode === "wide" ? ".5rem" : 0,
36
- }, align: p.centerTitle ? "center" : "start", flex: 1, v: p.title })) : (_jsx("div", { style: { flex: 1 }, children: p.title })), _jsx(_Toolbar, { actions: [...((_c = p.actions) !== null && _c !== void 0 ? _c : []), ...((_d = appBase === null || appBase === void 0 ? void 0 : appBase.globalActions) !== null && _d !== void 0 ? _d : [])] }), layoutMode === "wide" && (_jsx(_Logo, { logo: (_e = p.endLogo) !== null && _e !== void 0 ? _e : appBase === null || appBase === void 0 ? void 0 : appBase.icons.endLogo, logoDark: (_f = p.endLogoDark) !== null && _f !== void 0 ? _f : appBase === null || appBase === void 0 ? void 0 : appBase.icons.endLogoDark, rMargin: 0.5 }))] }));
34
+ layoutMode != "wide" && (_jsx(IconButton.plain, { ariaLabel: "open/close menu", onTap: () => appBase.setMenuOpen(!appBase.menuOpen), icon: MenuIcon })), p.leading === "back" && _backBtn, p.leading === "close" && _closeBtn, layoutMode !== "mobile" && (_jsx(_Logo, { logo: (_b = p.logo) !== null && _b !== void 0 ? _b : appBase === null || appBase === void 0 ? void 0 : appBase.icons.logo, logoDark: (_c = p.logoDark) !== null && _c !== void 0 ? _c : appBase === null || appBase === void 0 ? void 0 : appBase.icons.logoDark, lMargin: 0.5 })), (!appBase || layoutMode === "wide") && (_jsx("div", { style: { margin: "-1rem", width: "1.5rem" } })), _jsx(_HeaderTitle, { title: p.title, center: (_d = p.centerTitle) !== null && _d !== void 0 ? _d : false }), _jsx(_Toolbar, { actions: [...((_e = p.actions) !== null && _e !== void 0 ? _e : []), ...((_f = appBase === null || appBase === void 0 ? void 0 : appBase.globalActions) !== null && _f !== void 0 ? _f : [])] }), layoutMode === "wide" && (_jsx(_Logo, { logo: (_g = p.endLogo) !== null && _g !== void 0 ? _g : appBase === null || appBase === void 0 ? void 0 : appBase.icons.endLogo, logoDark: (_h = p.endLogoDark) !== null && _h !== void 0 ? _h : appBase === null || appBase === void 0 ? void 0 : appBase.icons.endLogoDark, rMargin: 0.5 }))] }));
37
35
  }
38
36
  function _Logo(p) {
39
37
  var _a, _b;
@@ -56,3 +54,21 @@ export function BackButton(p) {
56
54
  export function CloseButton(p) {
57
55
  return _jsx(IconButton.plain, { ariaLabel: "close", onTap: p.onTap, icon: XIcon });
58
56
  }
57
+ export function _HeaderTitle(p) {
58
+ const layoutMode = useLayoutMode();
59
+ const globalCenter = useMemo(() => {
60
+ return layoutMode !== "mobile" && p.center;
61
+ }, [layoutMode]);
62
+ return (_jsx("div", { style: {
63
+ flex: 1,
64
+ display: "flex",
65
+ justifyContent: p.center ? "center" : "flex-start",
66
+ }, children: _jsx("div", { style: {
67
+ transform: globalCenter
68
+ ? "translateX(-50%) translateY(-50%)"
69
+ : "none",
70
+ // align to center of the screen:
71
+ position: globalCenter ? "absolute" : "static",
72
+ left: globalCenter ? "50%" : "0",
73
+ }, children: typeof p.title === "string" ? (_jsx(Text.h3, { align: p.center ? "center" : "start", v: p.title })) : (p.title) }) }));
74
+ }
@@ -1,11 +1,4 @@
1
- import { IconChild } from "../button/icon_button";
2
- export type MenuItem = {
3
- id: string;
4
- label: string;
5
- icon?: IconChild;
6
- bottom?: boolean;
7
- disabled?: boolean;
8
- };
1
+ import { MenuItem } from "../../..";
9
2
  export declare function Menu(p: {
10
3
  items: MenuItem[];
11
4
  }): import("preact").JSX.Element;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "preact/jsx-runtime";
2
2
  import { MenuIcon } from "lucide-react";
3
- import { useLocation } from "preact-iso";
3
+ import { useLocation } from "wouter";
4
4
  import { useLayoutMode, useTheme, useThemeConfig } from "../../..";
5
5
  import { Card, elevatedShadow } from "../base/card";
6
6
  import { Button } from "../button/button";
@@ -53,7 +53,9 @@ export function Menu(p) {
53
53
  } }), _jsx(Column, { flex: 1, scroll: true, noScrollbar: true, children: topBot.top.map((i) => (_jsx(_MenuItemView, { item: i }))) }), topBot.bottom.map((i) => (_jsx(_MenuItemView, { item: i })))] })) })] }));
54
54
  }
55
55
  function _MenuItemView({ item }) {
56
- const location = useLocation();
56
+ const [location, navigate] = useLocation();
57
57
  const appBase = useAppBase();
58
- return (_jsx(Button, { ariaLabel: item.label, contentAlign: "start", manner: location.path.startsWith(`/${item.id}`) ? "major" : "plain", label: appBase.menuOpen ? item.label : undefined, icon: item.icon, onTap: item.disabled ? undefined : () => location.route(`/${item.id}`, true) }));
58
+ return (_jsx(Button, { ariaLabel: item.label, contentAlign: "start", manner: (item.path === "/" ? location === "/" : location.startsWith(item.path))
59
+ ? "major"
60
+ : "plain", label: appBase.menuOpen ? item.label : undefined, icon: item.icon, onTap: item.disabled ? undefined : () => navigate(item.path) }));
59
61
  }
@@ -3,6 +3,8 @@ import { HeaderProps } from "./header";
3
3
  type ContentBaseProps = {
4
4
  padding?: number;
5
5
  narrow?: boolean;
6
+ noScroll?: boolean;
7
+ scheme?: string;
6
8
  };
7
9
  /**
8
10
  * A component that represents a full page layout with a header and footer.
@@ -14,7 +16,7 @@ type ContentBaseProps = {
14
16
  * - `padding` will add padding around the content, defaulting to `1rem`.
15
17
  */
16
18
  export declare function Page(p: HeaderProps & ContentBaseProps & {
17
- children: ElbeChildren;
18
- footer: ElbeChild;
19
+ children?: ElbeChildren;
20
+ footer?: ElbeChild;
19
21
  }): import("preact").JSX.Element;
20
22
  export {};
@@ -13,6 +13,7 @@ import { Header } from "./header";
13
13
  * - `padding` will add padding around the content, defaulting to `1rem`.
14
14
  */
15
15
  export function Page(p) {
16
+ var _a;
16
17
  const [hasHeader, setHasHeader] = useState(false);
17
18
  useEffect(() => {
18
19
  const headerProps = Object.assign({}, p);
@@ -20,18 +21,21 @@ export function Page(p) {
20
21
  delete headerProps.footer;
21
22
  delete headerProps.padding;
22
23
  delete headerProps.narrow;
24
+ delete headerProps.noScroll;
23
25
  setHasHeader(Object.keys(headerProps).length > 0);
24
26
  });
25
- return (_jsxs(Box, { scheme: "primary", typeLabel: "page", style: {
27
+ return (_jsxs(Box, { scheme: (_a = p.scheme) !== null && _a !== void 0 ? _a : "primary", typeLabel: "page", style: {
26
28
  display: "flex",
27
29
  flexDirection: "column",
28
30
  alignItems: "stretch",
29
31
  minHeight: "100vh",
32
+ height: p.noScroll ? "100vh" : "auto",
30
33
  }, children: [hasHeader && _jsx(Header, Object.assign({}, p)), _jsx(_ContentBase, { padding: p.padding, narrow: p.narrow, children: p.children }), p.footer] }));
31
34
  }
32
35
  function _ContentBase(p) {
33
36
  return (_jsx(Column, { style: {
34
37
  flex: 1,
38
+ minHeight: 0,
35
39
  padding: p.padding ? `${p.padding}rem` : "1rem",
36
40
  width: p.narrow ? "min(100%, 900px)" : "auto",
37
41
  margin: p.narrow ? "0 auto" : "0",
@@ -0,0 +1,30 @@
1
+ import { VNode } from "preact";
2
+ import { ElbeChildren, IconChild, wouter } from "../../..";
3
+ export type MenuItem = {
4
+ path: string;
5
+ label: string;
6
+ icon: IconChild;
7
+ bottom?: boolean;
8
+ disabled?: boolean;
9
+ };
10
+ type _MenuRouteProps = MenuItem & {
11
+ children?: ElbeChildren;
12
+ };
13
+ export type ElbeRoute = VNode<{
14
+ path: string;
15
+ }> | VNode<_MenuRouteProps>;
16
+ /**
17
+ * a route that also renders a menu item in the <AppBase> component.
18
+ * place it as a child of <AppBase> to have it rendered in the menu.
19
+ *
20
+ * it automatically creates a nestable route
21
+ *
22
+ * ⚠️ a route with path "/" can't be used for nesting
23
+ */
24
+ export declare function MenuRoute(p: _MenuRouteProps): import("preact").JSX.Element;
25
+ /**
26
+ * a helper function to create a <wouter.Route> with a path.
27
+ */
28
+ export declare const Route: typeof wouter.Route;
29
+ export declare function isMenuRoute(r: ElbeRoute): r is VNode<_MenuRouteProps>;
30
+ export {};
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx } from "preact/jsx-runtime";
2
+ import { wouter } from "../../..";
3
+ /**
4
+ * a route that also renders a menu item in the <AppBase> component.
5
+ * place it as a child of <AppBase> to have it rendered in the menu.
6
+ *
7
+ * it automatically creates a nestable route
8
+ *
9
+ * ⚠️ a route with path "/" can't be used for nesting
10
+ */
11
+ export function MenuRoute(p) {
12
+ return (_jsx(Route, { nest: true, path: p.path, children: p.children }));
13
+ }
14
+ /**
15
+ * a helper function to create a <wouter.Route> with a path.
16
+ */
17
+ export const Route = wouter.Route;
18
+ export function isMenuRoute(r) {
19
+ return "path" in r.props && "label" in r.props && "icon" in r.props;
20
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "elbe-ui",
3
- "version": "0.4.18",
3
+ "version": "0.4.20",
4
4
  "author": "Robin Naumann",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -38,8 +38,7 @@
38
38
  "colors-convert": "^1.4.1",
39
39
  "lucide-react": "^0.438.0",
40
40
  "preact": "^10.24.2",
41
- "preact-iso": "^2.9.1",
42
- "preact-router": "^4.1.2",
43
- "vite": "^5.4.10"
41
+ "vite": "^5.4.10",
42
+ "wouter": "^3.7.1"
44
43
  }
45
44
  }
@@ -1,37 +0,0 @@
1
- import { Signal } from "@preact/signals";
2
- import { type PreactContext } from "preact";
3
- export interface OldBitUseInterface<C, T> {
4
- signal: Signal<OldBitState<T>>;
5
- ctrl: C;
6
- map: <D>(m: OldTriMap<T, D>) => D | preact.JSX.Element;
7
- onData: <D>(f: (d: T) => any, { onLoading, onError, }?: {
8
- onLoading?: () => any;
9
- onError?: (e: string) => any;
10
- }) => any;
11
- }
12
- interface OldBitData<C, T> {
13
- ctrl: C;
14
- state: Signal<OldBitState<T>>;
15
- }
16
- export interface OldBitState<T> {
17
- loading?: boolean;
18
- error?: any;
19
- data?: T;
20
- }
21
- export type OldBitContext<T, C> = PreactContext<OldBitData<T, C> | null>;
22
- export interface OldTriMap<T, D> {
23
- onLoading?: () => D;
24
- onError?: (e: string) => D;
25
- onData?: (value: T) => D;
26
- }
27
- export interface OldTWParams<T> {
28
- emit: (t: T) => void;
29
- emitLoading: () => void;
30
- emitError: (e: any) => void;
31
- map: <D>(m: OldTriMap<T, D>) => D;
32
- signal: Signal<OldBitState<T>>;
33
- }
34
- export declare function makeOldBit<C, T>(name: string): OldBitContext<C, T>;
35
- export declare function ProvideOldBit<I, C, T>(context: OldBitContext<C, T>, parameters: I, worker: (p: I, d: OldTWParams<T>, ctrl: C) => void, ctrl: (p: I, d: OldTWParams<T>) => C, children: any): import("preact").JSX.Element;
36
- export declare function useOldBit<C, T>(context: OldBitContext<C, T>): OldBitUseInterface<C, T>;
37
- export {};
@@ -1,79 +0,0 @@
1
- import { jsx as _jsx } from "preact/jsx-runtime";
2
- import { useSignal } from "@preact/signals";
3
- import { createContext } from "preact";
4
- import { useContext } from "preact/hooks";
5
- import { ErrorView } from "../../ui/components/error_view";
6
- import { Column } from "../../ui/components/layout/flex";
7
- import { Spinner } from "../../ui/components/spinner";
8
- export function makeOldBit(name) {
9
- const c = createContext(null);
10
- c.displayName = name;
11
- return c;
12
- }
13
- export function ProvideOldBit(context, parameters, worker, ctrl, children) {
14
- const s = useSignal({ loading: true });
15
- const _set = (n) => {
16
- try {
17
- if (JSON.stringify(n) === JSON.stringify(s.peek()))
18
- return;
19
- }
20
- catch (e) { }
21
- s.value = n;
22
- };
23
- const emit = (data) => _set({ data });
24
- const emitLoading = () => _set({ loading: true });
25
- const emitError = (error) => {
26
- console.warn(`BIT: ${context.displayName} emitted ERROR`, error);
27
- return _set({ error });
28
- };
29
- function map(m) {
30
- var _a;
31
- const st = s.value;
32
- if (st.loading)
33
- return m.onLoading();
34
- if (st.error)
35
- return m.onError(st.error);
36
- return m.onData((_a = st.data) !== null && _a !== void 0 ? _a : null);
37
- }
38
- const c = ctrl(parameters, { emit, emitLoading, emitError, map, signal: s });
39
- worker(parameters, { emit, emitLoading, emitError, map, signal: s }, c);
40
- return (_jsx(context.Provider, { value: { ctrl: c, state: s }, children: children }));
41
- }
42
- export function useOldBit(context) {
43
- try {
44
- const { ctrl, state } = useContext(context);
45
- const v = state.value;
46
- function map(m) {
47
- var _a;
48
- if (v.loading)
49
- return (m.onLoading ||
50
- (() => (_jsx(Column, { cross: "center", children: _jsx(Spinner, {}) }))))();
51
- if (v.error)
52
- return (m.onError ||
53
- ((e) => { var _a; return _jsx(ErrorView, { error: e, retry: (_a = ctrl.reload) !== null && _a !== void 0 ? _a : null }); }))(v.error);
54
- return m.onData((_a = v.data) !== null && _a !== void 0 ? _a : null);
55
- }
56
- return {
57
- signal: state,
58
- ctrl,
59
- map,
60
- /**
61
- * this is a quality of life function that allows
62
- * you to chain the map function with the onData function
63
- * @param f the builder function
64
- * @returns the built component
65
- */
66
- onData: (f, { onLoading, onError, } = {}) => map({ onData: f, onLoading: onLoading, onError: onError }),
67
- };
68
- }
69
- catch (e) {
70
- const err = `BIT ERROR: NO ${context.displayName} PROVIDED`;
71
- console.error(err, e);
72
- return {
73
- map: (_) => _jsx("div", { children: err }),
74
- ctrl: null,
75
- signal: null,
76
- onData: () => _jsx("div", { children: err }),
77
- };
78
- }
79
- }
@@ -1,30 +0,0 @@
1
- import type { JSX } from "preact/jsx-runtime";
2
- import { OldBitUseInterface, OldTWParams } from "./old_bit";
3
- declare abstract class OldBitControl<I, DT> {
4
- p: I;
5
- bit: OldTWParams<DT>;
6
- constructor(p: I, bit: OldTWParams<DT>);
7
- act(fn: (b: DT) => Promise<void>): void;
8
- /**
9
- * Clean up resources. This is called once
10
- * the element is removed from the DOM.
11
- */
12
- dispose(): void;
13
- }
14
- export declare abstract class WorkerControl<I, DT> extends OldBitControl<I, DT> {
15
- reload: (() => Promise<void>) | null;
16
- abstract worker(): Promise<DT>;
17
- }
18
- export declare abstract class StreamControl<I, DT, Stream> extends OldBitControl<I, DT> {
19
- protected stream: Stream | null;
20
- abstract listen(): Stream;
21
- dispose(): void;
22
- abstract disposeStream(stream: Stream): void;
23
- }
24
- export declare function CtrlBit<I, DT, C extends OldBitControl<I, DT>>(ctrl: (p: I, d: OldTWParams<DT>) => C, name?: string): {
25
- Provide: (props: I & {
26
- children: React.ReactNode;
27
- }) => JSX.Element;
28
- use: () => OldBitUseInterface<C, DT>;
29
- };
30
- export {};
@@ -1,108 +0,0 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
- var __rest = (this && this.__rest) || function (s, e) {
11
- var t = {};
12
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
13
- t[p] = s[p];
14
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
15
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
16
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
17
- t[p[i]] = s[p[i]];
18
- }
19
- return t;
20
- };
21
- import { useEffect } from "preact/hooks";
22
- import { makeOldBit, ProvideOldBit, useOldBit, } from "./old_bit";
23
- class OldBitControl {
24
- constructor(p, bit) {
25
- this.bit = bit;
26
- this.p = p;
27
- }
28
- act(fn) {
29
- this.bit.map({
30
- onData: (d) => __awaiter(this, void 0, void 0, function* () {
31
- try {
32
- yield fn(d);
33
- }
34
- catch (e) {
35
- if (e && e.code && e.message)
36
- console.error(`[BitERROR] act: ${e.code} (${e.message})`);
37
- else
38
- console.error("[BitERROR] act: ", e);
39
- }
40
- }),
41
- });
42
- }
43
- /**
44
- * Clean up resources. This is called once
45
- * the element is removed from the DOM.
46
- */
47
- dispose() { }
48
- }
49
- export class WorkerControl extends OldBitControl {
50
- constructor() {
51
- super(...arguments);
52
- this.reload = null;
53
- }
54
- }
55
- export class StreamControl extends OldBitControl {
56
- constructor() {
57
- super(...arguments);
58
- this.stream = null;
59
- }
60
- dispose() {
61
- if (this.stream)
62
- this.disposeStream(this.stream);
63
- }
64
- }
65
- function make(name) {
66
- return makeOldBit(name);
67
- }
68
- function use(b) {
69
- return useOldBit(b);
70
- }
71
- export function CtrlBit(ctrl, name) {
72
- const context = make((name || "Unknown") + "Bit");
73
- function Provide(_a) {
74
- var { children } = _a, p = __rest(_a, ["children"]);
75
- return ProvideOldBit(context, p, (p, b, c) => __awaiter(this, void 0, void 0, function* () {
76
- b.emitLoading();
77
- try {
78
- if (c instanceof WorkerControl) {
79
- if (c.reload)
80
- yield c.reload();
81
- }
82
- if (c instanceof StreamControl) {
83
- c.stream = c.listen();
84
- }
85
- }
86
- catch (e) {
87
- b.emitError(e);
88
- }
89
- }), (p, b) => {
90
- const c = ctrl(p, b);
91
- // clean up on unmount
92
- useEffect(() => () => c.dispose(), []);
93
- if (c instanceof WorkerControl) {
94
- c.reload = () => __awaiter(this, void 0, void 0, function* () {
95
- b.emitLoading();
96
- try {
97
- b.emit(yield c.worker());
98
- }
99
- catch (e) {
100
- b.emitError(e);
101
- }
102
- });
103
- }
104
- return c;
105
- }, children);
106
- }
107
- return { Provide: Provide, use: () => use(context) };
108
- }