@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.
- package/dist/{chunk-6PV4NM55.js → chunk-2WKGBINE.js} +1 -1
- package/dist/chunk-BDCVWAH7.js +123 -0
- package/dist/components/AppHeader/index.js +2 -2
- package/dist/components/AppLauncher/index.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +10 -10
- package/package.json +7 -7
- package/dist/chunk-H5UTW5ML.js +0 -97
|
@@ -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
|
+
};
|
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
|
-
|
|
7
|
-
|
|
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-
|
|
38
|
+
} from "./chunk-2WKGBINE.js";
|
|
40
39
|
import "./chunk-VYMTTYPR.js";
|
|
41
40
|
import {
|
|
42
41
|
AppLauncher
|
|
43
|
-
} from "./chunk-
|
|
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
|
-
|
|
56
|
-
|
|
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": "
|
|
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": "^
|
|
40
|
-
"@nexub/react": "^
|
|
41
|
-
"@nexub/tokens": "^
|
|
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": "
|
|
63
|
-
"@nexub/react": "
|
|
64
|
-
"@nexub/tokens": "
|
|
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",
|
package/dist/chunk-H5UTW5ML.js
DELETED
|
@@ -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
|
-
};
|