@nexub/patterns 1.9.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,13 +1,19 @@
1
1
  'use client';
2
+ import {
3
+ StepIndicator
4
+ } from "./chunk-L456OB3Q.js";
5
+ import {
6
+ UserCard
7
+ } from "./chunk-7NYGYETG.js";
8
+ import {
9
+ FilterBar
10
+ } from "./chunk-Z6VMCMSW.js";
2
11
  import {
3
12
  InfoList
4
13
  } from "./chunk-WPNT5JOU.js";
5
14
  import {
6
15
  ListDetail
7
16
  } from "./chunk-5JU6HUVJ.js";
8
- import {
9
- FilterBar
10
- } from "./chunk-Z6VMCMSW.js";
11
17
  import {
12
18
  NotificationItem
13
19
  } from "./chunk-5IORYY2L.js";
@@ -24,36 +30,30 @@ import {
24
30
  import {
25
31
  StatCard
26
32
  } from "./chunk-INKX2GYA.js";
27
- import {
28
- StepIndicator
29
- } from "./chunk-L456OB3Q.js";
30
- import {
31
- UserCard
32
- } from "./chunk-7NYGYETG.js";
33
33
  import {
34
34
  ActivityFeed
35
35
  } from "./chunk-HYC5N5MO.js";
36
36
  import {
37
37
  AppHeader
38
- } from "./chunk-6PV4NM55.js";
38
+ } from "./chunk-2WKGBINE.js";
39
39
  import "./chunk-VYMTTYPR.js";
40
40
  import {
41
41
  AppLauncher
42
- } from "./chunk-H5UTW5ML.js";
42
+ } from "./chunk-BDCVWAH7.js";
43
43
  import {
44
44
  AuthCard
45
45
  } from "./chunk-HMJRSNJD.js";
46
46
  import {
47
- ErrorSummary,
48
- useFormErrors
49
- } from "./chunk-RWPFCNMJ.js";
47
+ BentoGrid,
48
+ BentoTile
49
+ } from "./chunk-WZTDCJYP.js";
50
50
  import {
51
51
  Conversation
52
52
  } from "./chunk-WRP73TXG.js";
53
53
  import {
54
- BentoGrid,
55
- BentoTile
56
- } from "./chunk-WZTDCJYP.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.9.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.9.0",
40
- "@nexub/react": "^1.9.0",
41
- "@nexub/tokens": "^1.9.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.9.0",
63
- "@nexub/react": "1.9.0",
64
- "@nexub/tokens": "1.9.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
- };