@nexub/patterns 1.8.0 → 2.0.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.
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import {
3
3
  AppLauncher
4
- } from "./chunk-H5UTW5ML.js";
4
+ } from "./chunk-BDCVWAH7.js";
5
5
 
6
6
  // src/components/AppHeader/AppHeader.tsx
7
7
  import * as React from "react";
@@ -0,0 +1,123 @@
1
+ 'use client';
2
+
3
+ // src/components/AppLauncher/AppLauncher.tsx
4
+ import * as React from "react";
5
+ import { Avatar, IconButton, Popover, cn } from "@nexub/react";
6
+ import { Apps } from "@nexub/icons";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ var COLUMNS = 3;
9
+ function groupApps(apps) {
10
+ const sections = [];
11
+ for (const app of apps) {
12
+ const s = sections.find((x) => x.group === app.group);
13
+ if (s) s.items.push(app);
14
+ else sections.push({ group: app.group, items: [app], firstRow: 0 });
15
+ }
16
+ let row = 0;
17
+ for (const s of sections) {
18
+ s.firstRow = row;
19
+ row += Math.ceil(s.items.length / COLUMNS);
20
+ }
21
+ return sections;
22
+ }
23
+ function AppIcon({ app }) {
24
+ const [failed, setFailed] = React.useState(false);
25
+ if (app.icon && !failed) {
26
+ return /* @__PURE__ */ jsx(
27
+ "img",
28
+ {
29
+ src: app.icon,
30
+ alt: "",
31
+ width: 40,
32
+ height: 40,
33
+ className: "h-10 w-10 rounded-[12px] object-cover",
34
+ onError: () => setFailed(true)
35
+ }
36
+ );
37
+ }
38
+ return /* @__PURE__ */ jsx(Avatar, { name: app.name, size: "md", className: "rounded-[12px]", "aria-hidden": "true" });
39
+ }
40
+ var AppLauncher = React.forwardRef(
41
+ ({ apps, onNavigate, surface = "solid", label = "Nexub apps", className }, ref) => {
42
+ const [open, setOpen] = React.useState(false);
43
+ const listRef = React.useRef(null);
44
+ if (apps.length === 0) return null;
45
+ const sections = groupApps(apps);
46
+ const onOpenAutoFocus = (e) => {
47
+ e.preventDefault();
48
+ const list = listRef.current;
49
+ (list?.querySelector("a[aria-current]") ?? list?.querySelector("a"))?.focus();
50
+ };
51
+ const onKeyDown = (e) => {
52
+ const links = [...e.currentTarget.querySelectorAll("a")];
53
+ const i = links.indexOf(document.activeElement);
54
+ if (i < 0) return;
55
+ const at = (a) => [Number(a.dataset.row), Number(a.dataset.col)];
56
+ const vertical = (dir) => {
57
+ const [row, col] = at(links[i]);
58
+ const next = links.filter((a) => at(a)[0] === row + dir);
59
+ return next.length ? links.indexOf(next[Math.min(col, next.length - 1)]) : i;
60
+ };
61
+ const target = {
62
+ ArrowRight: () => i + 1,
63
+ ArrowLeft: () => i - 1,
64
+ ArrowDown: () => vertical(1),
65
+ ArrowUp: () => vertical(-1),
66
+ Home: () => 0,
67
+ End: () => links.length - 1
68
+ };
69
+ const go = target[e.key];
70
+ if (!go) return;
71
+ e.preventDefault();
72
+ links[Math.min(links.length - 1, Math.max(0, go()))]?.focus();
73
+ };
74
+ const onClick = (e, app) => {
75
+ const plain = e.button === 0 && !e.metaKey && !e.ctrlKey && !e.shiftKey && !e.altKey;
76
+ if (onNavigate && plain) {
77
+ e.preventDefault();
78
+ onNavigate(app);
79
+ }
80
+ if (plain) setOpen(false);
81
+ };
82
+ return /* @__PURE__ */ jsxs(Popover.Root, { open, onOpenChange: setOpen, children: [
83
+ /* @__PURE__ */ jsx(Popover.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(IconButton, { ref, label, className, children: /* @__PURE__ */ jsx(Apps, { size: "md", "aria-hidden": "true" }) }) }),
84
+ /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(
85
+ Popover.Content,
86
+ {
87
+ align: "end",
88
+ surface,
89
+ "aria-label": label,
90
+ onOpenAutoFocus,
91
+ className: "w-80 max-w-[calc(100vw-2rem)] p-2",
92
+ children: /* @__PURE__ */ jsx("div", { ref: listRef, className: "flex flex-col gap-2", onKeyDown, children: sections.map(({ group, items, firstRow }) => /* @__PURE__ */ jsxs("div", { children: [
93
+ group && /* @__PURE__ */ jsx("h3", { className: "px-2 py-1 text-label-sm text-[var(--color-on-surface-variant)]", children: group }),
94
+ /* @__PURE__ */ jsx("ul", { className: "grid grid-cols-3 gap-1", "aria-label": group, children: items.map((app, n) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
95
+ "a",
96
+ {
97
+ href: app.href,
98
+ "aria-current": app.current ? "page" : void 0,
99
+ "data-row": firstRow + Math.floor(n / COLUMNS),
100
+ "data-col": n % COLUMNS,
101
+ onClick: (e) => onClick(e, app),
102
+ className: cn(
103
+ "flex flex-col items-center gap-1.5 rounded-[8px] px-1 py-3 text-center text-xs text-[var(--color-on-surface)] transition-colors",
104
+ "hover:bg-[color-mix(in_srgb,var(--color-on-surface)_8%,transparent)]",
105
+ app.current && "bg-[var(--color-surface-container-low)] font-semibold"
106
+ ),
107
+ children: [
108
+ /* @__PURE__ */ jsx(AppIcon, { app }),
109
+ /* @__PURE__ */ jsx("span", { className: "w-full truncate", children: app.name })
110
+ ]
111
+ }
112
+ ) }, app.id)) })
113
+ ] }, group ?? "")) })
114
+ }
115
+ ) })
116
+ ] });
117
+ }
118
+ );
119
+ AppLauncher.displayName = "AppLauncher";
120
+
121
+ export {
122
+ AppLauncher
123
+ };
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
  import {
3
3
  AppHeader
4
- } from "../../chunk-6PV4NM55.js";
5
- import "../../chunk-H5UTW5ML.js";
4
+ } from "../../chunk-2WKGBINE.js";
5
+ import "../../chunk-BDCVWAH7.js";
6
6
  export {
7
7
  AppHeader
8
8
  };
@@ -2,7 +2,7 @@
2
2
  import "../../chunk-VYMTTYPR.js";
3
3
  import {
4
4
  AppLauncher
5
- } from "../../chunk-H5UTW5ML.js";
5
+ } from "../../chunk-BDCVWAH7.js";
6
6
  export {
7
7
  AppLauncher
8
8
  };
package/dist/index.d.ts CHANGED
@@ -156,6 +156,8 @@ interface AppLauncherApp {
156
156
  href: string;
157
157
  /** Tile image URL (e.g. `@nexub/brand/app-icon-account.svg`). Falls back to initials. */
158
158
  icon?: string;
159
+ /** Section heading, e.g. "People & HR". Apps with the same group sit together, in order of first appearance. */
160
+ group?: string;
159
161
  /** The app the user is in now. */
160
162
  current?: boolean;
161
163
  }
package/dist/index.js CHANGED
@@ -1,11 +1,13 @@
1
1
  'use client';
2
+ import {
3
+ StepIndicator
4
+ } from "./chunk-L456OB3Q.js";
2
5
  import {
3
6
  UserCard
4
7
  } from "./chunk-7NYGYETG.js";
5
8
  import {
6
- ErrorSummary,
7
- useFormErrors
8
- } from "./chunk-RWPFCNMJ.js";
9
+ FilterBar
10
+ } from "./chunk-Z6VMCMSW.js";
9
11
  import {
10
12
  InfoList
11
13
  } from "./chunk-WPNT5JOU.js";
@@ -28,19 +30,16 @@ import {
28
30
  import {
29
31
  StatCard
30
32
  } from "./chunk-INKX2GYA.js";
31
- import {
32
- StepIndicator
33
- } from "./chunk-L456OB3Q.js";
34
33
  import {
35
34
  ActivityFeed
36
35
  } from "./chunk-HYC5N5MO.js";
37
36
  import {
38
37
  AppHeader
39
- } from "./chunk-6PV4NM55.js";
38
+ } from "./chunk-2WKGBINE.js";
40
39
  import "./chunk-VYMTTYPR.js";
41
40
  import {
42
41
  AppLauncher
43
- } from "./chunk-H5UTW5ML.js";
42
+ } from "./chunk-BDCVWAH7.js";
44
43
  import {
45
44
  AuthCard
46
45
  } from "./chunk-HMJRSNJD.js";
@@ -52,8 +51,9 @@ import {
52
51
  Conversation
53
52
  } from "./chunk-WRP73TXG.js";
54
53
  import {
55
- FilterBar
56
- } from "./chunk-Z6VMCMSW.js";
54
+ ErrorSummary,
55
+ useFormErrors
56
+ } from "./chunk-RWPFCNMJ.js";
57
57
  export {
58
58
  ActivityFeed,
59
59
  AppHeader,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexub/patterns",
3
- "version": "1.8.0",
3
+ "version": "2.0.0",
4
4
  "description": "Composite UI patterns for Nexub Design System — pre-built, production-ready layouts and widgets built on @nexub/react.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -36,9 +36,9 @@
36
36
  "peerDependencies": {
37
37
  "react": ">=18 <20",
38
38
  "react-dom": ">=18 <20",
39
- "@nexub/icons": "^1.8.0",
40
- "@nexub/react": "^1.8.0",
41
- "@nexub/tokens": "^1.8.0"
39
+ "@nexub/icons": "^2.0.0",
40
+ "@nexub/react": "^2.0.0",
41
+ "@nexub/tokens": "^2.0.0"
42
42
  },
43
43
  "peerDependenciesMeta": {
44
44
  "@nexub/icons": {
@@ -59,9 +59,9 @@
59
59
  "typescript": "^5.9.3",
60
60
  "vite": "^7.3.7",
61
61
  "vitest": "^5.0.5",
62
- "@nexub/icons": "1.8.0",
63
- "@nexub/react": "1.8.0",
64
- "@nexub/tokens": "1.8.0"
62
+ "@nexub/icons": "2.0.0",
63
+ "@nexub/react": "2.0.0",
64
+ "@nexub/tokens": "2.0.0"
65
65
  },
66
66
  "scripts": {
67
67
  "build": "tsup",
@@ -1,97 +0,0 @@
1
- 'use client';
2
-
3
- // src/components/AppLauncher/AppLauncher.tsx
4
- import * as React from "react";
5
- import { Avatar, IconButton, Popover, cn } from "@nexub/react";
6
- import { Apps } from "@nexub/icons";
7
- import { jsx, jsxs } from "react/jsx-runtime";
8
- var COLUMNS = 3;
9
- function AppIcon({ app }) {
10
- const [failed, setFailed] = React.useState(false);
11
- if (app.icon && !failed) {
12
- return /* @__PURE__ */ jsx(
13
- "img",
14
- {
15
- src: app.icon,
16
- alt: "",
17
- width: 40,
18
- height: 40,
19
- className: "h-10 w-10 rounded-[12px] object-cover",
20
- onError: () => setFailed(true)
21
- }
22
- );
23
- }
24
- return /* @__PURE__ */ jsx(Avatar, { name: app.name, size: "md", className: "rounded-[12px]", "aria-hidden": "true" });
25
- }
26
- var AppLauncher = React.forwardRef(
27
- ({ apps, onNavigate, surface = "solid", label = "Nexub apps", className }, ref) => {
28
- const [open, setOpen] = React.useState(false);
29
- const listRef = React.useRef(null);
30
- if (apps.length === 0) return null;
31
- const onOpenAutoFocus = (e) => {
32
- e.preventDefault();
33
- const list = listRef.current;
34
- (list?.querySelector("a[aria-current]") ?? list?.querySelector("a"))?.focus();
35
- };
36
- const onKeyDown = (e) => {
37
- const links = [...e.currentTarget.querySelectorAll("a")];
38
- const i = links.indexOf(document.activeElement);
39
- if (i < 0) return;
40
- const step = {
41
- ArrowRight: 1,
42
- ArrowLeft: -1,
43
- ArrowDown: COLUMNS,
44
- ArrowUp: -COLUMNS,
45
- Home: -i,
46
- End: links.length - 1 - i
47
- };
48
- const delta = step[e.key];
49
- if (delta === void 0) return;
50
- e.preventDefault();
51
- links[Math.min(links.length - 1, Math.max(0, i + delta))]?.focus();
52
- };
53
- const onClick = (e, app) => {
54
- const plain = e.button === 0 && !e.metaKey && !e.ctrlKey && !e.shiftKey && !e.altKey;
55
- if (onNavigate && plain) {
56
- e.preventDefault();
57
- onNavigate(app);
58
- }
59
- if (plain) setOpen(false);
60
- };
61
- return /* @__PURE__ */ jsxs(Popover.Root, { open, onOpenChange: setOpen, children: [
62
- /* @__PURE__ */ jsx(Popover.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(IconButton, { ref, label, className, children: /* @__PURE__ */ jsx(Apps, { size: "md", "aria-hidden": "true" }) }) }),
63
- /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(
64
- Popover.Content,
65
- {
66
- align: "end",
67
- surface,
68
- "aria-label": label,
69
- onOpenAutoFocus,
70
- className: "w-80 max-w-[calc(100vw-2rem)] p-2",
71
- children: /* @__PURE__ */ jsx("ul", { ref: listRef, className: "grid grid-cols-3 gap-1", onKeyDown, children: apps.map((app) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
72
- "a",
73
- {
74
- href: app.href,
75
- "aria-current": app.current ? "page" : void 0,
76
- onClick: (e) => onClick(e, app),
77
- className: cn(
78
- "flex flex-col items-center gap-1.5 rounded-[8px] px-1 py-3 text-center text-xs text-[var(--color-on-surface)] transition-colors",
79
- "hover:bg-[color-mix(in_srgb,var(--color-on-surface)_8%,transparent)]",
80
- app.current && "bg-[var(--color-surface-container-low)] font-semibold"
81
- ),
82
- children: [
83
- /* @__PURE__ */ jsx(AppIcon, { app }),
84
- /* @__PURE__ */ jsx("span", { className: "w-full truncate", children: app.name })
85
- ]
86
- }
87
- ) }, app.id)) })
88
- }
89
- ) })
90
- ] });
91
- }
92
- );
93
- AppLauncher.displayName = "AppLauncher";
94
-
95
- export {
96
- AppLauncher
97
- };