@protobase/ui 0.55.2 → 0.55.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,10 @@
1
+ import { allowEverything } from "../data/meta-data.js";
1
2
  import { humanize } from "../live/naming.js";
2
3
  import { text } from "./messages.js";
4
+ import { text as text$1 } from "./create/messages.js";
3
5
  import { iconFor } from "./icons.js";
4
6
  import { menuIcon } from "./menu-icons.js";
7
+ import { createOffered } from "./create/create-offered.js";
5
8
  import { userMenuResources } from "./user-menu-from-meta.js";
6
9
  import { LayoutDashboard } from "lucide-react";
7
10
  import { appPath } from "@protobase/schema";
@@ -21,10 +24,21 @@ const schemaGroup = (schema = "public") => {
21
24
  if (schema === "public") return "";
22
25
  return groupLabels[schema] ? text(groupLabels[schema]) : humanize(schema);
23
26
  };
27
+ /** The link under an entry whose `nav.create` asks for one, to whoever the list would offer New. */
28
+ const createLink = (meta, view, nav, singular, basePath) => {
29
+ if (!nav?.create || !createOffered(view, meta.pages, meta.permissions[view.resource] ?? allowEverything)) return {};
30
+ return { create: {
31
+ label: nav.create === true ? text$1("title", singular) : nav.create.label,
32
+ href: appPath({
33
+ kind: "create",
34
+ resource: view.resource
35
+ }, basePath)
36
+ } };
37
+ };
24
38
  /**
25
39
  * One sidebar group per `nav.group` (default: the database schema, none for `public`, and "Pages" for pages); `nav.hidden` and a place in
26
40
  * the user menu leave an entry out, `nav.order` sorts within a group, and `recent` (from `useNavRecent`) makes an entry
27
- * collapsible. Groups with pages come first, so a dashboard leads the sidebar. The resources of a source that cannot be
41
+ * collapsible, and `create` links to the create form under it. Groups with pages come first, so a dashboard leads the sidebar. The resources of a source that cannot be
28
42
  * read are listed under its label, each with the problem. A visitor also gets the resources whose views ask them to sign
29
43
  * in, where those views place them, under "Account" without a group. Each named list of a view is an entry of its
30
44
  * own, placed by its own `nav`.
@@ -73,7 +87,8 @@ const navFromMeta = (meta, basePath = "", recent = {}) => {
73
87
  kind: "list",
74
88
  resource: model.name
75
89
  }, basePath),
76
- ...recent[model.name] && { recent: recent[model.name] }
90
+ ...recent[model.name] && { recent: recent[model.name] },
91
+ ...createLink(meta, view, view.nav, view.names?.singular ?? humanize(model.name), basePath)
77
92
  }, view.nav?.order);
78
93
  for (const named of view.lists ?? []) {
79
94
  if (named.nav?.hidden) continue;
@@ -85,7 +100,8 @@ const navFromMeta = (meta, basePath = "", recent = {}) => {
85
100
  kind: "list",
86
101
  resource: model.name,
87
102
  list: named.name
88
- }, basePath)
103
+ }, basePath),
104
+ ...createLink(meta, view, named.nav, named.names.singular, basePath)
89
105
  }, named.nav?.order);
90
106
  }
91
107
  }
@@ -0,0 +1,26 @@
1
+ import { cn } from "../lib/cn.js";
2
+ import { subRow, subRowIndent } from "./sidebar-rows.js";
3
+ import { Plus } from "lucide-react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ //#region src/app-shell/sidebar-create-link.tsx
6
+ /** The link under a sidebar entry to its resource's create form, such as "New message", lined up with its records. */
7
+ const SidebarCreateLink = ({ create, mode, onNavigate }) => /* @__PURE__ */ jsx("div", {
8
+ className: cn("border-l border-transparent pl-1.5", subRowIndent[mode]),
9
+ children: /* @__PURE__ */ jsxs("a", {
10
+ href: create.href,
11
+ onClick: onNavigate && ((event) => {
12
+ event.preventDefault();
13
+ onNavigate(create.href);
14
+ }),
15
+ className: cn("flex items-center rounded-md text-faint-foreground transition-colors hover:bg-muted hover:text-foreground", subRow[mode]),
16
+ children: [/* @__PURE__ */ jsx(Plus, {
17
+ "aria-hidden": true,
18
+ className: "size-3.5 shrink-0"
19
+ }), /* @__PURE__ */ jsx("span", {
20
+ className: "truncate",
21
+ children: create.label
22
+ })]
23
+ })
24
+ });
25
+ //#endregion
26
+ export { SidebarCreateLink };
@@ -1,5 +1,6 @@
1
1
  import { cn } from "../lib/cn.js";
2
2
  import { text } from "./messages.js";
3
+ import { subRow, subRowIndent } from "./sidebar-rows.js";
3
4
  import { navOpenStorageKey, readNavOpen, writeNavOpen } from "./nav-open-storage.js";
4
5
  import { SidebarItem } from "./sidebar-item.js";
5
6
  import { StatusDot } from "./status-dot.js";
@@ -7,15 +8,6 @@ import { useId, useState } from "react";
7
8
  import { ChevronRight } from "lucide-react";
8
9
  import { jsx, jsxs } from "react/jsx-runtime";
9
10
  //#region src/app-shell/sidebar-group.tsx
10
- const row = {
11
- "text-large": "h-8 gap-2.5 px-2.5",
12
- "text-small": "h-7 gap-2 px-2 text-body"
13
- };
14
- /** Lines the records up under the entry's icon. */
15
- const indent = {
16
- "text-large": "ml-[20px]",
17
- "text-small": "ml-[17px]"
18
- };
19
11
  /**
20
12
  * A sidebar entry with a few of its records under it. The label opens the list, the chevron shows or hides the
21
13
  * records (remembered in this browser), and "View all" at the end opens the list too.
@@ -58,14 +50,14 @@ const SidebarGroup = ({ item, recent, mode, active, onNavigate }) => {
58
50
  }), open && /* @__PURE__ */ jsxs("ul", {
59
51
  id: listId,
60
52
  "aria-label": text("recent", item.label.toLowerCase()),
61
- className: cn("flex flex-col gap-0.5 border-l pl-1.5", indent[mode]),
53
+ className: cn("flex flex-col gap-0.5 border-l pl-1.5", subRowIndent[mode]),
62
54
  children: [
63
55
  recent.state === "loading" && /* @__PURE__ */ jsx("li", {
64
- className: cn("flex items-center text-faint-foreground", row[mode]),
56
+ className: cn("flex items-center text-faint-foreground", subRow[mode]),
65
57
  children: text("loading")
66
58
  }),
67
59
  recent.state === "error" && /* @__PURE__ */ jsx("li", {
68
- className: cn("flex items-center text-faint-foreground", row[mode]),
60
+ className: cn("flex items-center text-faint-foreground", subRow[mode]),
69
61
  children: text("couldNotLoad")
70
62
  }),
71
63
  recent.state === "ready" && recent.records.map((record) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("a", {
@@ -73,7 +65,7 @@ const SidebarGroup = ({ item, recent, mode, active, onNavigate }) => {
73
65
  "aria-label": record.status ? `${record.label}, ${record.status.label}` : record.label,
74
66
  title: record.status ? `${record.label} (${record.status.label})` : record.label,
75
67
  "aria-current": record.active ? "page" : void 0,
76
- className: cn("flex min-w-0 items-center rounded-md font-medium transition-colors", row[mode], record.active ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground"),
68
+ className: cn("flex min-w-0 items-center rounded-md font-medium transition-colors", subRow[mode], record.active ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground"),
77
69
  children: [record.status && /* @__PURE__ */ jsx(StatusDot, {
78
70
  tone: record.status.tone,
79
71
  pulse: record.status.pulse
@@ -84,7 +76,7 @@ const SidebarGroup = ({ item, recent, mode, active, onNavigate }) => {
84
76
  }) }, record.id)),
85
77
  /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("a", {
86
78
  ...link(href),
87
- className: cn("flex items-center rounded-md text-faint-foreground transition-colors hover:bg-muted hover:text-foreground", row[mode]),
79
+ className: cn("flex items-center rounded-md text-faint-foreground transition-colors hover:bg-muted hover:text-foreground", subRow[mode]),
88
80
  children: text("viewAll")
89
81
  }) })
90
82
  ]
@@ -0,0 +1,13 @@
1
+ //#region src/app-shell/sidebar-rows.ts
2
+ /** The height and spacing of a row under a sidebar entry, such as a recent record or its create link. */
3
+ const subRow = {
4
+ "text-large": "h-8 gap-2.5 px-2.5",
5
+ "text-small": "h-7 gap-2 px-2 text-body"
6
+ };
7
+ /** Lines the rows under an entry up under the entry's icon. */
8
+ const subRowIndent = {
9
+ "text-large": "ml-[20px]",
10
+ "text-small": "ml-[17px]"
11
+ };
12
+ //#endregion
13
+ export { subRow, subRowIndent };
@@ -5,11 +5,13 @@ import { Logo } from "./logo.js";
5
5
  import { navGroups } from "./nav.js";
6
6
  import { ThemeSwitch } from "./theme-switch.js";
7
7
  import { ProfileMenu } from "./profile-menu.js";
8
+ import { SidebarCreateLink } from "./sidebar-create-link.js";
8
9
  import { isTextMode, sidebarWidth } from "./sidebar-mode.js";
9
10
  import { SidebarItem } from "./sidebar-item.js";
10
11
  import { SidebarGroup } from "./sidebar-group.js";
12
+ import { Fragment } from "react";
11
13
  import { LogIn } from "lucide-react";
12
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
14
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
13
15
  //#region src/app-shell/sidebar.tsx
14
16
  const Sidebar = ({ mode, activeItem, user, workspace, appName, appIcon, forceExpanded, profileMenuOpen, groups = navGroups, userMenu, account, onNavigate, onSignOut, signIn, organizations, roles, theme, sticky, logo: SidebarLogo = Logo, notice }) => {
15
17
  const expanding = mode === "icon-expand";
@@ -37,18 +39,22 @@ const Sidebar = ({ mode, activeItem, user, workspace, appName, appIcon, forceExp
37
39
  children: [inline ? group.label && /* @__PURE__ */ jsx("div", {
38
40
  className: cn("px-3 pb-1 pt-1 text-xs font-medium uppercase tracking-wide text-faint-foreground", expanding && "h-4 whitespace-nowrap py-0 opacity-0 transition-opacity group-hover/side:opacity-100 group-focus-within/side:opacity-100", expanded && "opacity-100"),
39
41
  children: group.label
40
- }) : index > 0 && /* @__PURE__ */ jsx("div", { className: "mx-auto mb-1 h-px w-6 bg-border" }), group.items.map((item) => item.recent && isTextMode(mode) ? /* @__PURE__ */ jsx(SidebarGroup, {
42
+ }) : index > 0 && /* @__PURE__ */ jsx("div", { className: "mx-auto mb-1 h-px w-6 bg-border" }), group.items.map((item) => /* @__PURE__ */ jsxs(Fragment, { children: [item.recent && isTextMode(mode) ? /* @__PURE__ */ jsx(SidebarGroup, {
41
43
  item,
42
44
  recent: item.recent,
43
45
  mode,
44
46
  active: item.id === activeItem,
45
47
  onNavigate
46
- }, item.id) : /* @__PURE__ */ jsx(SidebarItem, {
48
+ }) : /* @__PURE__ */ jsx(SidebarItem, {
47
49
  item,
48
50
  mode,
49
51
  active: item.id === activeItem,
50
52
  onNavigate
51
- }, item.id))]
53
+ }), item.create && isTextMode(mode) && /* @__PURE__ */ jsx(SidebarCreateLink, {
54
+ create: item.create,
55
+ mode,
56
+ onNavigate
57
+ })] }, item.id))]
52
58
  }, group.label))
53
59
  }),
54
60
  notice,
@@ -65,7 +71,7 @@ const Sidebar = ({ mode, activeItem, user, workspace, appName, appIcon, forceExp
65
71
  organizations,
66
72
  roles,
67
73
  theme
68
- }) : /* @__PURE__ */ jsxs(Fragment, { children: [theme && inline && /* @__PURE__ */ jsx(ThemeSwitch, {
74
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [theme && inline && /* @__PURE__ */ jsx(ThemeSwitch, {
69
75
  theme,
70
76
  className: "flex min-h-11 w-full items-center gap-2.5 whitespace-nowrap p-1.5 text-muted-foreground md:min-h-0"
71
77
  }), signIn && /* @__PURE__ */ jsxs(RouteLink, {
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.55.2";
2
+ var version = "0.55.3";
3
3
  var protobase = { "pluginApi": 1 };
4
4
  //#endregion
5
5
  export { protobase, version };