tempest-react-sdk 0.80.0 → 0.82.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/README.md +40 -10
- package/bin/create-tempest-app.e2e.test.mjs +31 -0
- package/bin/create-tempest-app.mjs +11 -1
- package/bin/lib/component/generate.test.mjs +13 -1
- package/bin/lib/doctor/doctor.e2e.test.mjs +29 -0
- package/bin/lib/doctor/vitest.mjs +120 -0
- package/bin/lib/doctor/vitest.test.mjs +139 -0
- package/bin/lib/fs/contain.mjs +7 -0
- package/bin/lib/openapi/escape.mjs +68 -4
- package/bin/lib/openapi/generate.hostile.test.mjs +150 -1
- package/bin/lib/openapi/generate.mjs +78 -13
- package/bin/lib/openapi/generate.test.mjs +13 -1
- package/bin/lib/openapi/pattern-risk.mjs +134 -0
- package/bin/lib/openapi/pattern-risk.test.mjs +45 -0
- package/bin/tempest-gen.test.mjs +100 -0
- package/bin/tempest.mjs +24 -8
- package/dist/access/use-can.cjs +1 -1
- package/dist/access/use-can.cjs.map +1 -1
- package/dist/access/use-can.js +25 -28
- package/dist/access/use-can.js.map +1 -1
- package/dist/auth/create-tempest-auth.cjs +1 -1
- package/dist/auth/create-tempest-auth.cjs.map +1 -1
- package/dist/auth/create-tempest-auth.js +234 -122
- package/dist/auth/create-tempest-auth.js.map +1 -1
- package/dist/auth/session-identity.cjs +2 -0
- package/dist/auth/session-identity.cjs.map +1 -0
- package/dist/auth/session-identity.js +12 -0
- package/dist/auth/session-identity.js.map +1 -0
- package/dist/br/boleto.cjs +1 -1
- package/dist/br/boleto.cjs.map +1 -1
- package/dist/br/boleto.js +2 -1
- package/dist/br/boleto.js.map +1 -1
- package/dist/br.d.ts +22 -3
- package/dist/components/AuthForm/AuthFieldControl.cjs +2 -0
- package/dist/components/AuthForm/AuthFieldControl.cjs.map +1 -0
- package/dist/components/AuthForm/AuthFieldControl.js +85 -0
- package/dist/components/AuthForm/AuthFieldControl.js.map +1 -0
- package/dist/components/AuthForm/AuthForm.cjs +2 -0
- package/dist/components/AuthForm/AuthForm.cjs.map +1 -0
- package/dist/components/AuthForm/AuthForm.js +83 -0
- package/dist/components/AuthForm/AuthForm.js.map +1 -0
- package/dist/components/AuthForm/AuthForm.module.cjs +3 -0
- package/dist/components/AuthForm/AuthForm.module.cjs.map +1 -0
- package/dist/components/AuthForm/AuthForm.module.js +14 -0
- package/dist/components/AuthForm/AuthForm.module.js.map +1 -0
- package/dist/components/AuthForm/AuthLinks.cjs +2 -0
- package/dist/components/AuthForm/AuthLinks.cjs.map +1 -0
- package/dist/components/AuthForm/AuthLinks.js +32 -0
- package/dist/components/AuthForm/AuthLinks.js.map +1 -0
- package/dist/components/AuthForm/ForgotPasswordForm.cjs +2 -0
- package/dist/components/AuthForm/ForgotPasswordForm.cjs.map +1 -0
- package/dist/components/AuthForm/ForgotPasswordForm.js +32 -0
- package/dist/components/AuthForm/ForgotPasswordForm.js.map +1 -0
- package/dist/components/AuthForm/LoginForm.cjs +2 -0
- package/dist/components/AuthForm/LoginForm.cjs.map +1 -0
- package/dist/components/AuthForm/LoginForm.js +39 -0
- package/dist/components/AuthForm/LoginForm.js.map +1 -0
- package/dist/components/AuthForm/ResetPasswordForm.cjs +2 -0
- package/dist/components/AuthForm/ResetPasswordForm.cjs.map +1 -0
- package/dist/components/AuthForm/ResetPasswordForm.js +39 -0
- package/dist/components/AuthForm/ResetPasswordForm.js.map +1 -0
- package/dist/components/AuthForm/SignUpForm.cjs +2 -0
- package/dist/components/AuthForm/SignUpForm.cjs.map +1 -0
- package/dist/components/AuthForm/SignUpForm.js +32 -0
- package/dist/components/AuthForm/SignUpForm.js.map +1 -0
- package/dist/components/AuthForm/auth-defaults.cjs +2 -0
- package/dist/components/AuthForm/auth-defaults.cjs.map +1 -0
- package/dist/components/AuthForm/auth-defaults.js +54 -0
- package/dist/components/AuthForm/auth-defaults.js.map +1 -0
- package/dist/components/AuthForm/auth-schemas.cjs +2 -0
- package/dist/components/AuthForm/auth-schemas.cjs.map +1 -0
- package/dist/components/AuthForm/auth-schemas.js +26 -0
- package/dist/components/AuthForm/auth-schemas.js.map +1 -0
- package/dist/components/AuthPage/AuthPage.cjs +2 -0
- package/dist/components/AuthPage/AuthPage.cjs.map +1 -0
- package/dist/components/AuthPage/AuthPage.js +97 -0
- package/dist/components/AuthPage/AuthPage.js.map +1 -0
- package/dist/components/AuthPage/AuthPage.module.cjs +3 -0
- package/dist/components/AuthPage/AuthPage.module.cjs.map +1 -0
- package/dist/components/AuthPage/AuthPage.module.js +24 -0
- package/dist/components/AuthPage/AuthPage.module.js.map +1 -0
- package/dist/components/Breadcrumbs/Breadcrumbs.cjs +1 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.cjs.map +1 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.js +22 -21
- package/dist/components/Breadcrumbs/Breadcrumbs.js.map +1 -1
- package/dist/components/Button/Button.cjs +1 -1
- package/dist/components/Button/Button.cjs.map +1 -1
- package/dist/components/Button/Button.js +41 -40
- package/dist/components/Button/Button.js.map +1 -1
- package/dist/components/Chat/ChatAttachments.cjs +1 -1
- package/dist/components/Chat/ChatAttachments.cjs.map +1 -1
- package/dist/components/Chat/ChatAttachments.js +38 -38
- package/dist/components/FilterBar/filter-apply.cjs +1 -1
- package/dist/components/FilterBar/filter-apply.cjs.map +1 -1
- package/dist/components/FilterBar/filter-apply.js +7 -7
- package/dist/components/Form/Form.cjs +1 -1
- package/dist/components/Form/Form.cjs.map +1 -1
- package/dist/components/Form/Form.js +30 -32
- package/dist/components/Form/Form.js.map +1 -1
- package/dist/components/Layout/Layout.cjs +1 -1
- package/dist/components/Layout/Layout.cjs.map +1 -1
- package/dist/components/Layout/Layout.js +25 -24
- package/dist/components/Layout/Layout.js.map +1 -1
- package/dist/components/Layout/gap.cjs +2 -0
- package/dist/components/Layout/gap.cjs.map +1 -0
- package/dist/components/Layout/gap.js +15 -0
- package/dist/components/Layout/gap.js.map +1 -0
- package/dist/components/NavigationMenu/NavigationMenu.cjs +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.cjs.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.js +71 -70
- package/dist/components/NavigationMenu/NavigationMenu.js.map +1 -1
- package/dist/components/Sidebar/Sidebar.cjs.map +1 -1
- package/dist/components/Sidebar/Sidebar.js.map +1 -1
- package/dist/components/Sidebar/SidebarEntry.cjs +1 -1
- package/dist/components/Sidebar/SidebarEntry.cjs.map +1 -1
- package/dist/components/Sidebar/SidebarEntry.js +30 -29
- package/dist/components/Sidebar/SidebarEntry.js.map +1 -1
- package/dist/data/create-data-provider.cjs +1 -1
- package/dist/data/create-data-provider.cjs.map +1 -1
- package/dist/data/create-data-provider.js +1 -2
- package/dist/data/create-data-provider.js.map +1 -1
- package/dist/forms/index.js +7 -7
- package/dist/forms/masked-inputs.cjs +1 -1
- package/dist/forms/masked-inputs.cjs.map +1 -1
- package/dist/forms/masked-inputs.js +15 -15
- package/dist/forms/validate-form.cjs +1 -1
- package/dist/forms/validate-form.cjs.map +1 -1
- package/dist/forms/validate-form.js +6 -1
- package/dist/forms/validate-form.js.map +1 -1
- package/dist/forms/zod-resolver.cjs +1 -1
- package/dist/forms/zod-resolver.cjs.map +1 -1
- package/dist/forms/zod-resolver.js +8 -3
- package/dist/forms/zod-resolver.js.map +1 -1
- package/dist/http/api-client.cjs +1 -1
- package/dist/http/api-client.cjs.map +1 -1
- package/dist/http/api-client.js +137 -116
- package/dist/http/api-client.js.map +1 -1
- package/dist/http/credential-scope.cjs +1 -1
- package/dist/http/credential-scope.cjs.map +1 -1
- package/dist/http/credential-scope.js +33 -9
- package/dist/http/credential-scope.js.map +1 -1
- package/dist/http/presigned-post.cjs.map +1 -1
- package/dist/http/presigned-post.js.map +1 -1
- package/dist/http/resumable-upload.cjs +1 -1
- package/dist/http/resumable-upload.cjs.map +1 -1
- package/dist/http/resumable-upload.js +140 -134
- package/dist/http/resumable-upload.js.map +1 -1
- package/dist/http/upload-with-progress.cjs +1 -1
- package/dist/http/upload-with-progress.cjs.map +1 -1
- package/dist/http/upload-with-progress.js +17 -17
- package/dist/http/upload-with-progress.js.map +1 -1
- package/dist/http/use-describe-api-error.cjs +1 -1
- package/dist/http/use-describe-api-error.cjs.map +1 -1
- package/dist/http/use-describe-api-error.js +9 -9
- package/dist/oauth/GoogleSignIn.cjs +1 -1
- package/dist/oauth/GoogleSignIn.cjs.map +1 -1
- package/dist/oauth/GoogleSignIn.js +5 -4
- package/dist/oauth/GoogleSignIn.js.map +1 -1
- package/dist/offline/create-offline-sync.cjs +1 -1
- package/dist/offline/create-offline-sync.cjs.map +1 -1
- package/dist/offline/create-offline-sync.js +108 -68
- package/dist/offline/create-offline-sync.js.map +1 -1
- package/dist/query/offline-persistence.cjs +1 -1
- package/dist/query/offline-persistence.cjs.map +1 -1
- package/dist/query/offline-persistence.js +54 -44
- package/dist/query/offline-persistence.js.map +1 -1
- package/dist/sse/create-event-stream.cjs +1 -1
- package/dist/sse/create-event-stream.cjs.map +1 -1
- package/dist/sse/create-event-stream.js +48 -41
- package/dist/sse/create-event-stream.js.map +1 -1
- package/dist/sse/ticket-event-stream.cjs +1 -1
- package/dist/sse/ticket-event-stream.cjs.map +1 -1
- package/dist/sse/ticket-event-stream.js +48 -19
- package/dist/sse/ticket-event-stream.js.map +1 -1
- package/dist/sse/use-event-stream.cjs +1 -1
- package/dist/sse/use-event-stream.cjs.map +1 -1
- package/dist/sse/use-event-stream.js +19 -14
- package/dist/sse/use-event-stream.js.map +1 -1
- package/dist/styles/AuthForm.css +2 -0
- package/dist/styles/AuthPage.css +2 -0
- package/dist/styles/component/AuthForm.css +9 -0
- package/dist/styles/component/AuthPage.css +29 -0
- package/dist/styles/forms.css +36 -0
- package/dist/styles/manifest.json +63 -0
- package/dist/styles.css +1 -1
- package/dist/sw/background-sync.cjs +1 -1
- package/dist/sw/background-sync.cjs.map +1 -1
- package/dist/sw/background-sync.js +132 -112
- package/dist/sw/background-sync.js.map +1 -1
- package/dist/sw/cache.cjs +1 -1
- package/dist/sw/cache.cjs.map +1 -1
- package/dist/sw/cache.js +36 -30
- package/dist/sw/cache.js.map +1 -1
- package/dist/sw/create-push-handler.cjs +1 -1
- package/dist/sw/create-push-handler.cjs.map +1 -1
- package/dist/sw/create-push-handler.js +20 -10
- package/dist/sw/create-push-handler.js.map +1 -1
- package/dist/sw.d.ts +93 -28
- package/dist/tabular/compact.cjs +1 -1
- package/dist/tabular/compact.cjs.map +1 -1
- package/dist/tabular/compact.js +35 -16
- package/dist/tabular/compact.js.map +1 -1
- package/dist/tabular/manifest.cjs +1 -1
- package/dist/tabular/manifest.cjs.map +1 -1
- package/dist/tabular/manifest.js +33 -15
- package/dist/tabular/manifest.js.map +1 -1
- package/dist/tabular.d.ts +11 -1
- package/dist/tempest-react-sdk.cjs +1 -1
- package/dist/tempest-react-sdk.d.ts +1057 -83
- package/dist/tempest-react-sdk.js +373 -365
- package/dist/theme/initial-theme.cjs +4 -4
- package/dist/theme/initial-theme.cjs.map +1 -1
- package/dist/theme/initial-theme.js +8 -5
- package/dist/theme/initial-theme.js.map +1 -1
- package/dist/utils/compressed-storage.cjs +1 -1
- package/dist/utils/compressed-storage.cjs.map +1 -1
- package/dist/utils/compressed-storage.js +13 -7
- package/dist/utils/compressed-storage.js.map +1 -1
- package/dist/utils/format.cjs +1 -1
- package/dist/utils/format.cjs.map +1 -1
- package/dist/utils/format.js +25 -25
- package/dist/utils/numbers.cjs +1 -1
- package/dist/utils/numbers.cjs.map +1 -1
- package/dist/utils/numbers.js +7 -7
- package/dist/utils/objects.cjs +1 -1
- package/dist/utils/objects.cjs.map +1 -1
- package/dist/utils/objects.js +13 -7
- package/dist/utils/objects.js.map +1 -1
- package/dist/utils/safe-url.cjs +1 -1
- package/dist/utils/safe-url.cjs.map +1 -1
- package/dist/utils/safe-url.js +4 -1
- package/dist/utils/safe-url.js.map +1 -1
- package/dist/webrtc/peer-mesh.cjs +1 -1
- package/dist/webrtc/peer-mesh.cjs.map +1 -1
- package/dist/webrtc/peer-mesh.js +7 -1
- package/dist/webrtc/peer-mesh.js.map +1 -1
- package/dist/ws/create-web-socket.cjs +1 -1
- package/dist/ws/create-web-socket.cjs.map +1 -1
- package/dist/ws/create-web-socket.js +77 -61
- package/dist/ws/create-web-socket.js.map +1 -1
- package/dist/ws/use-web-socket.cjs +1 -1
- package/dist/ws/use-web-socket.cjs.map +1 -1
- package/dist/ws/use-web-socket.js +4 -3
- package/dist/ws/use-web-socket.js.map +1 -1
- package/package.json +1 -1
- package/template-pwa/src/pwa.tsx +42 -0
- package/template-pwa/src/sw.ts +15 -10
|
@@ -1,104 +1,105 @@
|
|
|
1
1
|
import { cn as e } from "../../utils/cn.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import a from "
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
2
|
+
import { vettedHref as t } from "../../utils/safe-url.js";
|
|
3
|
+
import { useEscapeLayer as n } from "../Portal/escape-layer.js";
|
|
4
|
+
import { Portal as r } from "../Portal/Portal.js";
|
|
5
|
+
import { useAnchorPosition as i } from "../Portal/anchor-position.js";
|
|
6
|
+
import { usePortalTabOrder as a } from "../Portal/tab-bridge.js";
|
|
7
|
+
import o from "./NavigationMenu.module.js";
|
|
8
|
+
import { useCallback as s, useEffect as c, useId as l, useRef as u, useState as d } from "react";
|
|
9
|
+
import { jsx as f, jsxs as p } from "react/jsx-runtime";
|
|
9
10
|
//#region src/components/NavigationMenu/NavigationMenu.tsx
|
|
10
|
-
var
|
|
11
|
-
function
|
|
12
|
-
let [
|
|
13
|
-
|
|
14
|
-
}, []),
|
|
15
|
-
anchor:
|
|
16
|
-
floating:
|
|
11
|
+
var m = 4;
|
|
12
|
+
function h({ items: h, portal: g = !0, className: _, ...v }) {
|
|
13
|
+
let [y, b] = d(-1), [x, S] = d(-1), C = l(), w = u(null), T = s(() => {
|
|
14
|
+
b(-1), S(-1);
|
|
15
|
+
}, []), E = y === -1 ? x : y, [D, O] = d(null), [k, A] = d(null), j = g && E !== -1, M = i({
|
|
16
|
+
anchor: D,
|
|
17
|
+
floating: k,
|
|
17
18
|
side: "bottom",
|
|
18
19
|
align: "start",
|
|
19
|
-
offset:
|
|
20
|
-
enabled:
|
|
20
|
+
offset: m,
|
|
21
|
+
enabled: j
|
|
21
22
|
});
|
|
22
|
-
|
|
23
|
-
enabled:
|
|
24
|
-
anchor:
|
|
25
|
-
panel:
|
|
26
|
-
}),
|
|
27
|
-
if (
|
|
23
|
+
a({
|
|
24
|
+
enabled: j,
|
|
25
|
+
anchor: D,
|
|
26
|
+
panel: k
|
|
27
|
+
}), n(E !== -1, T), c(() => {
|
|
28
|
+
if (E === -1) return;
|
|
28
29
|
let e = (e) => {
|
|
29
30
|
let t = e.target;
|
|
30
|
-
|
|
31
|
+
w.current?.contains(t) || k?.contains(t) || T();
|
|
31
32
|
};
|
|
32
33
|
return window.addEventListener("mousedown", e), () => {
|
|
33
34
|
window.removeEventListener("mousedown", e);
|
|
34
35
|
};
|
|
35
36
|
}, [
|
|
37
|
+
E,
|
|
36
38
|
T,
|
|
37
|
-
|
|
38
|
-
O
|
|
39
|
+
k
|
|
39
40
|
]);
|
|
40
|
-
let
|
|
41
|
-
let
|
|
42
|
-
e.onSelect?.(),
|
|
43
|
-
};
|
|
44
|
-
return
|
|
45
|
-
href:
|
|
46
|
-
role:
|
|
47
|
-
className:
|
|
48
|
-
onClick:
|
|
41
|
+
let N = (e, n) => {
|
|
42
|
+
let r = () => {
|
|
43
|
+
e.onSelect?.(), T();
|
|
44
|
+
}, i = t(e.href);
|
|
45
|
+
return i ? /* @__PURE__ */ f("a", {
|
|
46
|
+
href: i,
|
|
47
|
+
role: n === "menuitem" ? "menuitem" : void 0,
|
|
48
|
+
className: o.link,
|
|
49
|
+
onClick: r,
|
|
49
50
|
children: e.label
|
|
50
|
-
}) : /* @__PURE__ */
|
|
51
|
+
}) : /* @__PURE__ */ f("button", {
|
|
51
52
|
type: "button",
|
|
52
|
-
role:
|
|
53
|
-
className:
|
|
54
|
-
onClick:
|
|
53
|
+
role: n === "menuitem" ? "menuitem" : void 0,
|
|
54
|
+
className: o.link,
|
|
55
|
+
onClick: r,
|
|
55
56
|
children: e.label
|
|
56
57
|
});
|
|
57
58
|
};
|
|
58
|
-
return /* @__PURE__ */
|
|
59
|
-
ref:
|
|
60
|
-
className: e(
|
|
61
|
-
...
|
|
62
|
-
children: /* @__PURE__ */
|
|
63
|
-
className:
|
|
64
|
-
children:
|
|
65
|
-
if (!(t.children && t.children.length > 0)) return /* @__PURE__ */
|
|
66
|
-
className:
|
|
67
|
-
children:
|
|
68
|
-
},
|
|
69
|
-
let i =
|
|
70
|
-
ref:
|
|
71
|
-
id:
|
|
59
|
+
return /* @__PURE__ */ f("nav", {
|
|
60
|
+
ref: w,
|
|
61
|
+
className: e(o.root, _),
|
|
62
|
+
...v,
|
|
63
|
+
children: /* @__PURE__ */ f("ul", {
|
|
64
|
+
className: o.list,
|
|
65
|
+
children: h.map((t, n) => {
|
|
66
|
+
if (!(t.children && t.children.length > 0)) return /* @__PURE__ */ f("li", {
|
|
67
|
+
className: o.item,
|
|
68
|
+
children: N(t, "none")
|
|
69
|
+
}, n);
|
|
70
|
+
let i = E === n, a = `${C}-panel-${n}`, s = /* @__PURE__ */ f("ul", {
|
|
71
|
+
ref: A,
|
|
72
|
+
id: a,
|
|
72
73
|
role: "menu",
|
|
73
|
-
className: e(
|
|
74
|
-
style:
|
|
75
|
-
children: t.children.map((e, t) => /* @__PURE__ */
|
|
74
|
+
className: e(o.panel, g && o.portalled),
|
|
75
|
+
style: M,
|
|
76
|
+
children: t.children.map((e, t) => /* @__PURE__ */ f("li", {
|
|
76
77
|
role: "none",
|
|
77
|
-
className:
|
|
78
|
-
children:
|
|
78
|
+
className: o.subItem,
|
|
79
|
+
children: N(e, "menuitem")
|
|
79
80
|
}, t))
|
|
80
81
|
});
|
|
81
|
-
return /* @__PURE__ */
|
|
82
|
-
ref: i ?
|
|
83
|
-
className:
|
|
84
|
-
onMouseEnter: () =>
|
|
85
|
-
onMouseLeave: () =>
|
|
86
|
-
children: [/* @__PURE__ */
|
|
82
|
+
return /* @__PURE__ */ p("li", {
|
|
83
|
+
ref: i ? O : void 0,
|
|
84
|
+
className: o.item,
|
|
85
|
+
onMouseEnter: () => S(n),
|
|
86
|
+
onMouseLeave: () => S((e) => e === n ? -1 : e),
|
|
87
|
+
children: [/* @__PURE__ */ f("button", {
|
|
87
88
|
type: "button",
|
|
88
|
-
className:
|
|
89
|
+
className: o.trigger,
|
|
89
90
|
"aria-haspopup": "menu",
|
|
90
91
|
"aria-expanded": i,
|
|
91
|
-
"aria-controls":
|
|
92
|
-
onClick: () =>
|
|
93
|
-
onFocus: () =>
|
|
92
|
+
"aria-controls": a,
|
|
93
|
+
onClick: () => b((e) => e === n ? -1 : n),
|
|
94
|
+
onFocus: () => S(n),
|
|
94
95
|
children: t.label
|
|
95
|
-
}), i && (
|
|
96
|
-
},
|
|
96
|
+
}), i && (g ? /* @__PURE__ */ f(r, { children: s }) : s)]
|
|
97
|
+
}, n);
|
|
97
98
|
})
|
|
98
99
|
})
|
|
99
100
|
});
|
|
100
101
|
}
|
|
101
102
|
//#endregion
|
|
102
|
-
export {
|
|
103
|
+
export { h as NavigationMenu };
|
|
103
104
|
|
|
104
105
|
//# sourceMappingURL=NavigationMenu.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NavigationMenu.js","names":["styles","NavigationMenu_module_default","PANEL_OFFSET","NavigationMenu","props","clickedIndex","setClickedIndex","useState","hovered","setHovered","baseId","useId","rootRef","useRef","close","useCallback","openIndex","anchorNode","setAnchorNode","panelNode","setPanelNode","floating","portal","floatingStyle","useAnchorPosition","usePortalTabOrder","useEscapeLayer","useEffect","onDown","target","event","renderLeaf","handleClick","item","role","cn","className","items","index","open","panelId","panel","child","childIndex","prev","Portal"],"sources":["../../../src/components/NavigationMenu/NavigationMenu.tsx"],"sourcesContent":["/**\n * @tempest-limits function-lines — the body resolves the active item from the\n * current path, owns the open submenu and the hover-intent delay, all of which\n * decide the same highlighted row.\n */\nimport { useCallback, useEffect, useId, useRef, useState } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { Portal } from \"@/components/Portal\";\nimport { useAnchorPosition } from \"@/components/Portal/anchor-position\";\nimport { useEscapeLayer } from \"@/components/Portal/escape-layer\";\nimport { usePortalTabOrder } from \"@/components/Portal/tab-bridge\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./NavigationMenu.module.css\";\n\nexport interface NavigationMenuItem {\n /** Visible label for the entry. */\n label: ReactNode;\n /** When set, the entry renders as an anchor. */\n href?: string;\n /** Invoked when the entry is activated (click / Enter). */\n onSelect?: () => void;\n /** Nested entries — presence turns the item into a submenu trigger. */\n children?: NavigationMenuItem[];\n}\n\nexport interface NavigationMenuProps extends HTMLAttributes<HTMLElement> {\n /** Top-level navigation entries. */\n items: NavigationMenuItem[];\n /**\n * Render the open panel in a portal, positioned under its trigger. Default\n * `true`.\n *\n * In flow, an ancestor with `overflow` other than `visible` clips the panel,\n * and the SDK's own `Navbar` is one: its `nav` slot scrolls horizontally, and\n * `overflow-x: auto` forces `overflow-y` to `auto` too — a submenu showed\n * 0% of its panel there (measured in Chrome at 1440 px). In a portal the\n * panel escapes the clip, flips at the viewport edge, stays in the tab order\n * right after its trigger, and follows it on scroll and resize.\n */\n portal?: boolean;\n}\n\n/** Gap between trigger and panel, in px — the in-flow CSS uses the same 4 px. */\nconst PANEL_OFFSET = 4;\n\n/**\n * Horizontal navigation menu with hover/click/focus dropdown submenus.\n *\n * - Top-level items render in a `<nav><ul>`.\n * - Items with `children` open a submenu panel (`role=\"menu\"`) on hover, focus,\n * or click. Only one panel is open at a time.\n * - Closes on outside click, Escape, or selecting a leaf entry.\n *\n * @example\n * <NavigationMenu\n * items={[\n * { label: \"Home\", href: \"/\" },\n * {\n * label: \"Products\",\n * children: [\n * { label: \"Analytics\", href: \"/analytics\" },\n * { label: \"Billing\", onSelect: () => openBilling() },\n * ],\n * },\n * ]}\n * />\n */\nexport function NavigationMenu({ items, portal = true, className, ...props }: NavigationMenuProps) {\n const [clickedIndex, setClickedIndex] = useState<number>(-1);\n const [hovered, setHovered] = useState<number>(-1);\n const baseId = useId();\n const rootRef = useRef<HTMLElement>(null);\n\n const close = useCallback((): void => {\n setClickedIndex(-1);\n setHovered(-1);\n }, []);\n\n const openIndex = clickedIndex !== -1 ? clickedIndex : hovered;\n\n const [anchorNode, setAnchorNode] = useState<HTMLLIElement | null>(null);\n const [panelNode, setPanelNode] = useState<HTMLUListElement | null>(null);\n const floating = portal && openIndex !== -1;\n const floatingStyle = useAnchorPosition({\n anchor: anchorNode,\n floating: panelNode,\n side: \"bottom\",\n align: \"start\",\n offset: PANEL_OFFSET,\n enabled: floating,\n });\n usePortalTabOrder({ enabled: floating, anchor: anchorNode, panel: panelNode });\n\n useEscapeLayer(openIndex !== -1, close);\n\n /**\n * Close on a press outside both the menu and the open panel.\n *\n * The panel is checked on its own because in a portal it is not inside the\n * menu's root: a press on one of its entries would otherwise count as\n * outside, close the panel on `mousedown`, and swallow the entry's click.\n */\n useEffect(() => {\n if (openIndex === -1) return;\n const onDown = (event: MouseEvent): void => {\n const target = event.target as Node;\n if (rootRef.current?.contains(target) || panelNode?.contains(target)) return;\n close();\n };\n window.addEventListener(\"mousedown\", onDown);\n return () => {\n window.removeEventListener(\"mousedown\", onDown);\n };\n }, [openIndex, close, panelNode]);\n\n const renderLeaf = (item: NavigationMenuItem, role: \"none\" | \"menuitem\"): ReactNode => {\n const handleClick = (): void => {\n item.onSelect?.();\n close();\n };\n if (item.href) {\n return (\n <a\n href={item.href}\n role={role === \"menuitem\" ? \"menuitem\" : undefined}\n className={styles.link}\n onClick={handleClick}\n >\n {item.label}\n </a>\n );\n }\n return (\n <button\n type=\"button\"\n role={role === \"menuitem\" ? \"menuitem\" : undefined}\n className={styles.link}\n onClick={handleClick}\n >\n {item.label}\n </button>\n );\n };\n\n return (\n <nav ref={rootRef} className={cn(styles.root, className)} {...props}>\n <ul className={styles.list}>\n {items.map((item, index) => {\n const hasChildren = !!item.children && item.children.length > 0;\n if (!hasChildren) {\n return (\n <li key={index} className={styles.item}>\n {renderLeaf(item, \"none\")}\n </li>\n );\n }\n const open = openIndex === index;\n const panelId = `${baseId}-panel-${index}`;\n const panel = (\n <ul\n ref={setPanelNode}\n id={panelId}\n role=\"menu\"\n className={cn(styles.panel, portal && styles.portalled)}\n style={floatingStyle}\n >\n {item.children!.map((child, childIndex) => (\n <li key={childIndex} role=\"none\" className={styles.subItem}>\n {renderLeaf(child, \"menuitem\")}\n </li>\n ))}\n </ul>\n );\n return (\n <li\n key={index}\n ref={open ? setAnchorNode : undefined}\n className={styles.item}\n onMouseEnter={() => setHovered(index)}\n onMouseLeave={() => setHovered((prev) => (prev === index ? -1 : prev))}\n >\n <button\n type=\"button\"\n className={styles.trigger}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n aria-controls={panelId}\n onClick={() =>\n setClickedIndex((prev) => (prev === index ? -1 : index))\n }\n onFocus={() => setHovered(index)}\n >\n {item.label}\n </button>\n {open && (portal ? <Portal>{panel}</Portal> : panel)}\n </li>\n );\n })}\n </ul>\n </nav>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"NavigationMenu.js","names":["styles","NavigationMenu_module_default","PANEL_OFFSET","NavigationMenu","props","clickedIndex","setClickedIndex","useState","hovered","setHovered","baseId","useId","rootRef","useRef","close","useCallback","openIndex","anchorNode","setAnchorNode","panelNode","setPanelNode","floating","portal","floatingStyle","useAnchorPosition","usePortalTabOrder","useEscapeLayer","useEffect","onDown","target","event","renderLeaf","handleClick","item","href","vettedHref","role","cn","className","items","index","open","panelId","panel","child","childIndex","prev","Portal"],"sources":["../../../src/components/NavigationMenu/NavigationMenu.tsx"],"sourcesContent":["/**\n * @tempest-limits function-lines — the body resolves the active item from the\n * current path, owns the open submenu and the hover-intent delay, all of which\n * decide the same highlighted row.\n */\nimport { useCallback, useEffect, useId, useRef, useState } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { Portal } from \"@/components/Portal\";\nimport { useAnchorPosition } from \"@/components/Portal/anchor-position\";\nimport { useEscapeLayer } from \"@/components/Portal/escape-layer\";\nimport { usePortalTabOrder } from \"@/components/Portal/tab-bridge\";\nimport { cn } from \"@/utils/cn\";\nimport { vettedHref } from \"@/utils/safe-url\";\nimport styles from \"./NavigationMenu.module.css\";\n\nexport interface NavigationMenuItem {\n /** Visible label for the entry. */\n label: ReactNode;\n /**\n * When set, the entry renders as an anchor. The URL is checked against\n * `safeLinkUrl`'s allowlist: a scheme outside `http:`/`https:`/`mailto:`/`tel:`/`sms:`\n * renders the entry as a button instead, so a menu built from API data cannot\n * put a clickable `javascript:` link on the page under React 18.\n */\n href?: string;\n /** Invoked when the entry is activated (click / Enter). */\n onSelect?: () => void;\n /** Nested entries — presence turns the item into a submenu trigger. */\n children?: NavigationMenuItem[];\n}\n\nexport interface NavigationMenuProps extends HTMLAttributes<HTMLElement> {\n /** Top-level navigation entries. */\n items: NavigationMenuItem[];\n /**\n * Render the open panel in a portal, positioned under its trigger. Default\n * `true`.\n *\n * In flow, an ancestor with `overflow` other than `visible` clips the panel,\n * and the SDK's own `Navbar` is one: its `nav` slot scrolls horizontally, and\n * `overflow-x: auto` forces `overflow-y` to `auto` too — a submenu showed\n * 0% of its panel there (measured in Chrome at 1440 px). In a portal the\n * panel escapes the clip, flips at the viewport edge, stays in the tab order\n * right after its trigger, and follows it on scroll and resize.\n */\n portal?: boolean;\n}\n\n/** Gap between trigger and panel, in px — the in-flow CSS uses the same 4 px. */\nconst PANEL_OFFSET = 4;\n\n/**\n * Horizontal navigation menu with hover/click/focus dropdown submenus.\n *\n * - Top-level items render in a `<nav><ul>`.\n * - Items with `children` open a submenu panel (`role=\"menu\"`) on hover, focus,\n * or click. Only one panel is open at a time.\n * - Closes on outside click, Escape, or selecting a leaf entry.\n *\n * @example\n * <NavigationMenu\n * items={[\n * { label: \"Home\", href: \"/\" },\n * {\n * label: \"Products\",\n * children: [\n * { label: \"Analytics\", href: \"/analytics\" },\n * { label: \"Billing\", onSelect: () => openBilling() },\n * ],\n * },\n * ]}\n * />\n */\nexport function NavigationMenu({ items, portal = true, className, ...props }: NavigationMenuProps) {\n const [clickedIndex, setClickedIndex] = useState<number>(-1);\n const [hovered, setHovered] = useState<number>(-1);\n const baseId = useId();\n const rootRef = useRef<HTMLElement>(null);\n\n const close = useCallback((): void => {\n setClickedIndex(-1);\n setHovered(-1);\n }, []);\n\n const openIndex = clickedIndex !== -1 ? clickedIndex : hovered;\n\n const [anchorNode, setAnchorNode] = useState<HTMLLIElement | null>(null);\n const [panelNode, setPanelNode] = useState<HTMLUListElement | null>(null);\n const floating = portal && openIndex !== -1;\n const floatingStyle = useAnchorPosition({\n anchor: anchorNode,\n floating: panelNode,\n side: \"bottom\",\n align: \"start\",\n offset: PANEL_OFFSET,\n enabled: floating,\n });\n usePortalTabOrder({ enabled: floating, anchor: anchorNode, panel: panelNode });\n\n useEscapeLayer(openIndex !== -1, close);\n\n /**\n * Close on a press outside both the menu and the open panel.\n *\n * The panel is checked on its own because in a portal it is not inside the\n * menu's root: a press on one of its entries would otherwise count as\n * outside, close the panel on `mousedown`, and swallow the entry's click.\n */\n useEffect(() => {\n if (openIndex === -1) return;\n const onDown = (event: MouseEvent): void => {\n const target = event.target as Node;\n if (rootRef.current?.contains(target) || panelNode?.contains(target)) return;\n close();\n };\n window.addEventListener(\"mousedown\", onDown);\n return () => {\n window.removeEventListener(\"mousedown\", onDown);\n };\n }, [openIndex, close, panelNode]);\n\n const renderLeaf = (item: NavigationMenuItem, role: \"none\" | \"menuitem\"): ReactNode => {\n const handleClick = (): void => {\n item.onSelect?.();\n close();\n };\n const href = vettedHref(item.href);\n if (href) {\n return (\n <a\n href={href}\n role={role === \"menuitem\" ? \"menuitem\" : undefined}\n className={styles.link}\n onClick={handleClick}\n >\n {item.label}\n </a>\n );\n }\n return (\n <button\n type=\"button\"\n role={role === \"menuitem\" ? \"menuitem\" : undefined}\n className={styles.link}\n onClick={handleClick}\n >\n {item.label}\n </button>\n );\n };\n\n return (\n <nav ref={rootRef} className={cn(styles.root, className)} {...props}>\n <ul className={styles.list}>\n {items.map((item, index) => {\n const hasChildren = !!item.children && item.children.length > 0;\n if (!hasChildren) {\n return (\n <li key={index} className={styles.item}>\n {renderLeaf(item, \"none\")}\n </li>\n );\n }\n const open = openIndex === index;\n const panelId = `${baseId}-panel-${index}`;\n const panel = (\n <ul\n ref={setPanelNode}\n id={panelId}\n role=\"menu\"\n className={cn(styles.panel, portal && styles.portalled)}\n style={floatingStyle}\n >\n {item.children!.map((child, childIndex) => (\n <li key={childIndex} role=\"none\" className={styles.subItem}>\n {renderLeaf(child, \"menuitem\")}\n </li>\n ))}\n </ul>\n );\n return (\n <li\n key={index}\n ref={open ? setAnchorNode : undefined}\n className={styles.item}\n onMouseEnter={() => setHovered(index)}\n onMouseLeave={() => setHovered((prev) => (prev === index ? -1 : prev))}\n >\n <button\n type=\"button\"\n className={styles.trigger}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n aria-controls={panelId}\n onClick={() =>\n setClickedIndex((prev) => (prev === index ? -1 : index))\n }\n onFocus={() => setHovered(index)}\n >\n {item.label}\n </button>\n {open && (portal ? <Portal>{panel}</Portal> : panel)}\n </li>\n );\n })}\n </ul>\n </nav>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sidebar.cjs","names":["activeNavIndex","useId","cn","styles","SidebarEntry","require_cn","require_active_nav_key","require_Sidebar_module","require_SidebarEntry","react","react_jsx_runtime","toBlocks","blocks","open","entry","entries","resolveActiveKey","match","value","items","item","Sidebar","props","baseId","activeKey","finalWidth","collapsed","collapsedWidth","width","className","style","header","block","onChange","labelId","footer"],"sources":["../../../src/components/Sidebar/Sidebar.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — header, items, footer are the three regions,\n * value/onChange/match the selection, and collapsed/width/collapsedWidth the two\n * layout modes it switches between.\n */\nimport { useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { activeNavIndex } from \"./active-nav-key\";\nimport styles from \"./Sidebar.module.css\";\nimport { SidebarEntry } from \"./SidebarEntry\";\n\nexport interface SidebarItem {\n key: string;\n label: ReactNode;\n icon?: ReactNode;\n badge?: ReactNode;\n disabled?: boolean;\n /**\n * Renders the entry as a link to this URL instead of a button.\n *\n * `onChange` still fires on click, so an app that tracks the active key keeps\n * working — the difference is that the entry now behaves like navigation:\n * middle-click, ctrl-click and \"copy link address\" do what the user expects,\n * and a screen reader announces a link rather than a button.\n *\n * A `disabled` entry ignores this and stays a `<button disabled>`: there is no\n * disabled state for an anchor, and dropping the `href` to fake one leaves a\n * link that announces itself as actionable and is not.\n */\n href?: string;\n}\n\n/**\n * A section heading. Every item after it belongs to the section, until the next\n * section or separator.\n */\nexport interface SidebarSection {\n type: \"section\";\n key: string;\n label: ReactNode;\n}\n\n/** A plain divider, for splitting the list without naming the parts. */\nexport interface SidebarSeparator {\n type: \"separator\";\n key: string;\n}\n\n/**\n * One entry of the navigation list.\n *\n * An entry with no `type` is an item, which is what keeps a plain\n * `SidebarItem[]` a valid `SidebarEntry[]`: adding sections to this component\n * needed no change at any existing call site.\n */\nexport type SidebarEntry = ({ type?: \"item\" } & SidebarItem) | SidebarSection | SidebarSeparator;\n\nexport interface SidebarProps extends Omit<HTMLAttributes<HTMLElement>, \"onChange\"> {\n /** Top slot — typically the logo + brand. */\n header?: ReactNode;\n /** Navigation entries. An entry with no `type` is an item. */\n items: SidebarEntry[];\n /**\n * The active item: its `key` by default, or the current route with\n * `match=\"route\"`.\n */\n value?: string;\n /**\n * How `value` picks the active item.\n *\n * - `\"key\"` (default): the item whose `key` equals `value`.\n * - `\"route\"`: `value` is the current pathname, and the active item is the one\n * whose `href` (or `key`, when it has no `href`) is the longest path covering\n * it on a segment boundary — see `activeNavKey`. `/dashboard/tips/42` lights\n * `/dashboard/tips`, not `/dashboard`, and `/users-admin` never lights\n * `/users`. Sections and separators take no part.\n *\n * `\"route\"` is what lets the app pass `useLocation().pathname` and stop\n * computing a key: the component already holds the entries the resolution runs\n * against, so asking the app to repeat them is how the two drift apart. It\n * reads no router itself — the value comes in as a string — so the component\n * keeps working with no router mounted.\n */\n match?: \"key\" | \"route\";\n /** Fires when an item is clicked. Receives the item's `key`. */\n onChange?: (key: string) => void;\n /** Bottom slot — typically settings/profile/logout. */\n footer?: ReactNode;\n /** Collapsed mode — only icons visible. Default `false`. */\n collapsed?: boolean;\n /** Width when expanded, in pixels or any CSS length. Default `240px`. */\n width?: number | string;\n /** Width when collapsed, in pixels or any CSS length. Default `64px`. */\n collapsedWidth?: number | string;\n}\n\n/** A run of items, optionally under a section heading. */\ninterface ItemBlock {\n kind: \"items\";\n key: string;\n section?: SidebarSection;\n items: SidebarItem[];\n}\n\n/** A standalone divider between blocks. */\ninterface SeparatorBlock {\n kind: \"separator\";\n key: string;\n}\n\ntype Block = ItemBlock | SeparatorBlock;\n\n/**\n * Fold the flat entry list into the blocks that get rendered.\n *\n * A section opens a block that swallows every following item; a separator closes\n * whatever is open, so items after it are loose again. Items before the first\n * section are loose too — which is the whole existing behaviour, and why a list\n * with no sections comes out of here as one unnamed block.\n *\n * @param entries - The `items` prop, as given.\n * @returns Blocks in render order.\n */\nfunction toBlocks(entries: readonly SidebarEntry[]): Block[] {\n const blocks: Block[] = [];\n let open: ItemBlock | undefined;\n\n for (const entry of entries) {\n if (entry.type === \"separator\") {\n blocks.push({ kind: \"separator\", key: entry.key });\n open = undefined;\n continue;\n }\n if (entry.type === \"section\") {\n open = { kind: \"items\", key: entry.key, section: entry, items: [] };\n blocks.push(open);\n continue;\n }\n if (!open) {\n open = { kind: \"items\", key: `loose-${entry.key}`, items: [] };\n blocks.push(open);\n }\n open.items.push(entry);\n }\n return blocks;\n}\n\n/**\n * The key of the item `value` selects, under the given `match` mode.\n *\n * @param entries - The `items` prop, as given.\n * @param value - The `value` prop.\n * @param match - The `match` prop.\n * @returns The active item's key, or `undefined` when no item is active.\n */\nfunction resolveActiveKey(\n entries: readonly SidebarEntry[],\n value: string | undefined,\n match: \"key\" | \"route\",\n): string | undefined {\n if (match === \"key\" || value === undefined) {\n return value;\n }\n const items = entries.filter(\n (entry): entry is { type?: \"item\" } & SidebarItem =>\n entry.type !== \"section\" && entry.type !== \"separator\",\n );\n return items[\n activeNavIndex(\n value,\n items.map((item) => item.href ?? item.key),\n )\n ]?.key;\n}\n\n/**\n * Desktop sidebar navigation. Pair with `<Show above=\"md\">` and a `Drawer`\n * for mobile.\n *\n * Sixteen screens in one flat list is a wall, so `items` also takes section\n * headings and separators. A section renders as a labelled `role=\"group\"`, which\n * is what tells a screen reader \"Monitoring, group, 3 items\" — the reason it is\n * not a styled `disabled` item, which would announce an unavailable button and\n * stay in the navigation order.\n *\n * @example\n * const [tab, setTab] = useState(\"home\");\n * <Show above=\"md\">\n * <Sidebar\n * header={<Brand />}\n * items={[{ key: \"home\", label: \"Home\", icon: <Home /> }]}\n * value={tab}\n * onChange={setTab}\n * />\n * </Show>\n *\n * @example\n * const { pathname } = useLocation();\n * <Sidebar\n * match=\"route\"\n * value={pathname}\n * items={[\n * { key: \"overview\", label: \"Overview\", href: \"/dashboard\" },\n * { key: \"tips\", label: \"Tips\", href: \"/dashboard/tips\" },\n * ]}\n * />\n *\n * @example\n * <Sidebar\n * items={[\n * { type: \"section\", key: \"monitoring\", label: \"Monitoring\" },\n * { key: \"overview\", label: \"Overview\", href: \"/overview\" },\n * { key: \"activity\", label: \"Activity\", href: \"/activity\" },\n * { type: \"section\", key: \"admin\", label: \"Administration\" },\n * { key: \"settings\", label: \"Settings\", href: \"/settings\" },\n * ]}\n * value={tab}\n * onChange={setTab}\n * />\n */\nexport function Sidebar({\n header,\n items,\n value,\n onChange,\n match = \"key\",\n footer,\n collapsed = false,\n width = 240,\n collapsedWidth = 64,\n className,\n style,\n ...props\n}: SidebarProps) {\n const baseId = useId();\n const blocks = toBlocks(items);\n const activeKey = resolveActiveKey(items, value, match);\n const finalWidth =\n typeof (collapsed ? collapsedWidth : width) === \"number\"\n ? `${collapsed ? collapsedWidth : width}px`\n : collapsed\n ? collapsedWidth\n : width;\n\n return (\n <aside\n className={cn(styles.sidebar, collapsed && styles.collapsed, className)}\n style={{ width: finalWidth, ...style }}\n {...props}\n >\n {header && <div className={styles.header}>{header}</div>}\n <nav className={styles.nav} aria-label=\"Navegação lateral\">\n {blocks.map((block) => {\n if (block.kind === \"separator\") {\n return <hr key={block.key} className={styles.separator} />;\n }\n if (!block.section) {\n return (\n <div key={block.key} className={styles.group}>\n {block.items.map((item) => (\n <SidebarEntry\n key={item.key}\n item={item}\n active={item.key === activeKey}\n collapsed={collapsed}\n onSelect={onChange}\n />\n ))}\n </div>\n );\n }\n const labelId = `${baseId}-${block.key}`;\n return (\n <div\n key={block.key}\n role=\"group\"\n aria-labelledby={labelId}\n className={styles.group}\n >\n <div\n id={labelId}\n role=\"presentation\"\n className={cn(styles.section, collapsed && styles.sectionCollapsed)}\n >\n {block.section.label}\n </div>\n {block.items.map((item) => (\n <SidebarEntry\n key={item.key}\n item={item}\n active={item.key === activeKey}\n collapsed={collapsed}\n onSelect={onChange}\n />\n ))}\n </div>\n );\n })}\n </nav>\n {footer && <div className={styles.footer}>{footer}</div>}\n </aside>\n );\n}\n"],"mappings":"A,gMA2HA,SAASW,EAAS,EAA2C,CACzD,IAAMC,EAAkB,CAAC,EACrBC,EAEJ,IAAK,IAAMC,KAASC,EACZD,EAAM,OAAS,aACfF,EAAO,KAAK,CAAE,KAAM,YAAa,IAAKE,EAAM,GAAI,CAAC,EACjD,EAAO,IAAA,IAGPA,EAAM,OAAS,WACf,EAAO,CAAE,KAAM,QAAS,IAAKA,EAAM,IAAK,QAASA,EAAO,MAAO,CAAC,CAAE,EAClEF,EAAO,KAAKC,CAAI,IAGfA,IACD,EAAO,CAAE,KAAM,QAAS,IAAK,SAASC,EAAM,MAAO,MAAO,CAAC,CAAE,EAC7DF,EAAO,KAAKC,CAAI,GAEpBA,EAAK,MAAM,KAAKC,CAAK,GAEzB,OAAOF,CACX,CAUA,SAASI,EACL,EACA,EACA,EACkB,CAClB,GAAIC,IAAU,OAASC,IAAU,IAAA,GAC7B,OAAOA,EAEX,IAAMC,EAAQJ,EAAQ,OACjB,GACGD,EAAM,OAAS,WAAaA,EAAM,OAAS,WACnD,EACA,OAAOK,EACHnB,EAAAA,eACIkB,EACAC,EAAM,IAAK,GAASC,EAAK,MAAQA,EAAK,GAAG,CAC7C,EACH,EAAE,GACP,CA+CA,SAAgBC,EAAQ,CACpB,SACA,QACA,QACA,WACA,QAAQ,MACR,SACA,YAAY,GACZ,QAAQ,IACR,iBAAiB,GACjB,YACA,QACA,GAAGC,GACU,CACb,IAAMC,GAAA,EAAStB,EAAAA,MAAAA,CAAM,EACfW,EAASD,EAASQ,CAAK,EACvBK,EAAYR,EAAiBG,EAAOD,EAAOD,CAAK,EAChDQ,EACF,OAAQC,EAAYC,EAAiBC,IAAW,SAC1C,GAAGF,EAAYC,EAAiBC,EAAM,IACtCF,EACEC,EACAC,EAEZ,OACI,EAAAlB,EAAA,KAAA,CAAC,QAAD,CACI,UAAWR,EAAAA,GAAGC,EAAAA,QAAO,QAASuB,GAAavB,EAAAA,QAAO,UAAW0B,CAAS,EACtE,MAAO,CAAE,MAAOJ,EAAY,GAAGK,CAAM,EACrC,GAAIR,EAHR,SAAA,CAKKS,IAAU,EAAArB,EAAA,IAAA,CAAC,MAAD,CAAK,UAAWP,EAAAA,QAAO,OAAS,SAAA4B,CAAY,CAAA,GACvD,EAAArB,EAAA,IAAA,CAAC,MAAD,CAAK,UAAWP,EAAAA,QAAO,IAAK,aAAW,oBAClC,SAAAS,EAAO,IAAK,GAAU,CACnB,GAAIoB,EAAM,OAAS,YACf,OAAO,EAAAtB,EAAA,IAAA,CAAC,KAAD,CAAoB,UAAWP,EAAAA,QAAO,SAAY,EAAzC6B,EAAM,GAAmC,EAE7D,GAAI,CAACA,EAAM,QACP,OACI,EAAAtB,EAAA,IAAA,CAAC,MAAD,CAAqB,UAAWP,EAAAA,QAAO,MAClC,SAAA6B,EAAM,MAAM,IAAK,IACd,EAAAtB,EAAA,IAAA,CAACN,EAAAA,aAAD,CAEU,OACN,OAAQgB,EAAK,MAAQI,EACV,YACX,SAAUS,CACb,EALQb,EAAK,GAKb,CACJ,CACA,EAVKY,EAAM,GAUX,EAGb,IAAME,EAAU,GAAGX,EAAO,GAAGS,EAAM,MACnC,OACI,EAAAtB,EAAA,KAAA,CAAC,MAAD,CAEI,KAAK,QACL,kBAAiBwB,EACjB,UAAW/B,EAAAA,QAAO,MAJtB,SAAA,EAMI,EAAAO,EAAA,IAAA,CAAC,MAAD,CACI,GAAIwB,EACJ,KAAK,eACL,UAAWhC,EAAAA,GAAGC,EAAAA,QAAO,QAASuB,GAAavB,EAAAA,QAAO,gBAAgB,EAEjE,SAAA6B,EAAM,QAAQ,KACd,CAAA,EACJA,EAAM,MAAM,IAAK,IACd,EAAAtB,EAAA,IAAA,CAACN,EAAAA,aAAD,CAEU,OACN,OAAQgB,EAAK,MAAQI,EACV,YACX,SAAUS,CACb,EALQb,EAAK,GAKb,CACJ,CACA,CArBI,EAAAY,EAAM,GAqBV,CAEb,CAAC,CACA,CAAA,EACJG,IAAU,EAAAzB,EAAA,IAAA,CAAC,MAAD,CAAK,UAAWP,EAAAA,QAAO,OAAS,SAAAgC,CAAY,CAAA,CACpD,C,EAEf,C"}
|
|
1
|
+
{"version":3,"file":"Sidebar.cjs","names":["activeNavIndex","useId","cn","styles","SidebarEntry","require_cn","require_active_nav_key","require_Sidebar_module","require_SidebarEntry","react","react_jsx_runtime","toBlocks","blocks","open","entry","entries","resolveActiveKey","match","value","items","item","Sidebar","props","baseId","activeKey","finalWidth","collapsed","collapsedWidth","width","className","style","header","block","onChange","labelId","footer"],"sources":["../../../src/components/Sidebar/Sidebar.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — header, items, footer are the three regions,\n * value/onChange/match the selection, and collapsed/width/collapsedWidth the two\n * layout modes it switches between.\n */\nimport { useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { activeNavIndex } from \"./active-nav-key\";\nimport styles from \"./Sidebar.module.css\";\nimport { SidebarEntry } from \"./SidebarEntry\";\n\nexport interface SidebarItem {\n key: string;\n label: ReactNode;\n icon?: ReactNode;\n badge?: ReactNode;\n disabled?: boolean;\n /**\n * Renders the entry as a link to this URL instead of a button.\n *\n * `onChange` still fires on click, so an app that tracks the active key keeps\n * working — the difference is that the entry now behaves like navigation:\n * middle-click, ctrl-click and \"copy link address\" do what the user expects,\n * and a screen reader announces a link rather than a button.\n *\n * A `disabled` entry ignores this and stays a `<button disabled>`: there is no\n * disabled state for an anchor, and dropping the `href` to fake one leaves a\n * link that announces itself as actionable and is not.\n *\n * The URL goes through `safeLinkUrl`: a scheme outside\n * `http:`/`https:`/`mailto:`/`tel:`/`sms:` makes the entry a button, the same\n * as an entry without `href`.\n */\n href?: string;\n}\n\n/**\n * A section heading. Every item after it belongs to the section, until the next\n * section or separator.\n */\nexport interface SidebarSection {\n type: \"section\";\n key: string;\n label: ReactNode;\n}\n\n/** A plain divider, for splitting the list without naming the parts. */\nexport interface SidebarSeparator {\n type: \"separator\";\n key: string;\n}\n\n/**\n * One entry of the navigation list.\n *\n * An entry with no `type` is an item, which is what keeps a plain\n * `SidebarItem[]` a valid `SidebarEntry[]`: adding sections to this component\n * needed no change at any existing call site.\n */\nexport type SidebarEntry = ({ type?: \"item\" } & SidebarItem) | SidebarSection | SidebarSeparator;\n\nexport interface SidebarProps extends Omit<HTMLAttributes<HTMLElement>, \"onChange\"> {\n /** Top slot — typically the logo + brand. */\n header?: ReactNode;\n /** Navigation entries. An entry with no `type` is an item. */\n items: SidebarEntry[];\n /**\n * The active item: its `key` by default, or the current route with\n * `match=\"route\"`.\n */\n value?: string;\n /**\n * How `value` picks the active item.\n *\n * - `\"key\"` (default): the item whose `key` equals `value`.\n * - `\"route\"`: `value` is the current pathname, and the active item is the one\n * whose `href` (or `key`, when it has no `href`) is the longest path covering\n * it on a segment boundary — see `activeNavKey`. `/dashboard/tips/42` lights\n * `/dashboard/tips`, not `/dashboard`, and `/users-admin` never lights\n * `/users`. Sections and separators take no part.\n *\n * `\"route\"` is what lets the app pass `useLocation().pathname` and stop\n * computing a key: the component already holds the entries the resolution runs\n * against, so asking the app to repeat them is how the two drift apart. It\n * reads no router itself — the value comes in as a string — so the component\n * keeps working with no router mounted.\n */\n match?: \"key\" | \"route\";\n /** Fires when an item is clicked. Receives the item's `key`. */\n onChange?: (key: string) => void;\n /** Bottom slot — typically settings/profile/logout. */\n footer?: ReactNode;\n /** Collapsed mode — only icons visible. Default `false`. */\n collapsed?: boolean;\n /** Width when expanded, in pixels or any CSS length. Default `240px`. */\n width?: number | string;\n /** Width when collapsed, in pixels or any CSS length. Default `64px`. */\n collapsedWidth?: number | string;\n}\n\n/** A run of items, optionally under a section heading. */\ninterface ItemBlock {\n kind: \"items\";\n key: string;\n section?: SidebarSection;\n items: SidebarItem[];\n}\n\n/** A standalone divider between blocks. */\ninterface SeparatorBlock {\n kind: \"separator\";\n key: string;\n}\n\ntype Block = ItemBlock | SeparatorBlock;\n\n/**\n * Fold the flat entry list into the blocks that get rendered.\n *\n * A section opens a block that swallows every following item; a separator closes\n * whatever is open, so items after it are loose again. Items before the first\n * section are loose too — which is the whole existing behaviour, and why a list\n * with no sections comes out of here as one unnamed block.\n *\n * @param entries - The `items` prop, as given.\n * @returns Blocks in render order.\n */\nfunction toBlocks(entries: readonly SidebarEntry[]): Block[] {\n const blocks: Block[] = [];\n let open: ItemBlock | undefined;\n\n for (const entry of entries) {\n if (entry.type === \"separator\") {\n blocks.push({ kind: \"separator\", key: entry.key });\n open = undefined;\n continue;\n }\n if (entry.type === \"section\") {\n open = { kind: \"items\", key: entry.key, section: entry, items: [] };\n blocks.push(open);\n continue;\n }\n if (!open) {\n open = { kind: \"items\", key: `loose-${entry.key}`, items: [] };\n blocks.push(open);\n }\n open.items.push(entry);\n }\n return blocks;\n}\n\n/**\n * The key of the item `value` selects, under the given `match` mode.\n *\n * @param entries - The `items` prop, as given.\n * @param value - The `value` prop.\n * @param match - The `match` prop.\n * @returns The active item's key, or `undefined` when no item is active.\n */\nfunction resolveActiveKey(\n entries: readonly SidebarEntry[],\n value: string | undefined,\n match: \"key\" | \"route\",\n): string | undefined {\n if (match === \"key\" || value === undefined) {\n return value;\n }\n const items = entries.filter(\n (entry): entry is { type?: \"item\" } & SidebarItem =>\n entry.type !== \"section\" && entry.type !== \"separator\",\n );\n return items[\n activeNavIndex(\n value,\n items.map((item) => item.href ?? item.key),\n )\n ]?.key;\n}\n\n/**\n * Desktop sidebar navigation. Pair with `<Show above=\"md\">` and a `Drawer`\n * for mobile.\n *\n * Sixteen screens in one flat list is a wall, so `items` also takes section\n * headings and separators. A section renders as a labelled `role=\"group\"`, which\n * is what tells a screen reader \"Monitoring, group, 3 items\" — the reason it is\n * not a styled `disabled` item, which would announce an unavailable button and\n * stay in the navigation order.\n *\n * @example\n * const [tab, setTab] = useState(\"home\");\n * <Show above=\"md\">\n * <Sidebar\n * header={<Brand />}\n * items={[{ key: \"home\", label: \"Home\", icon: <Home /> }]}\n * value={tab}\n * onChange={setTab}\n * />\n * </Show>\n *\n * @example\n * const { pathname } = useLocation();\n * <Sidebar\n * match=\"route\"\n * value={pathname}\n * items={[\n * { key: \"overview\", label: \"Overview\", href: \"/dashboard\" },\n * { key: \"tips\", label: \"Tips\", href: \"/dashboard/tips\" },\n * ]}\n * />\n *\n * @example\n * <Sidebar\n * items={[\n * { type: \"section\", key: \"monitoring\", label: \"Monitoring\" },\n * { key: \"overview\", label: \"Overview\", href: \"/overview\" },\n * { key: \"activity\", label: \"Activity\", href: \"/activity\" },\n * { type: \"section\", key: \"admin\", label: \"Administration\" },\n * { key: \"settings\", label: \"Settings\", href: \"/settings\" },\n * ]}\n * value={tab}\n * onChange={setTab}\n * />\n */\nexport function Sidebar({\n header,\n items,\n value,\n onChange,\n match = \"key\",\n footer,\n collapsed = false,\n width = 240,\n collapsedWidth = 64,\n className,\n style,\n ...props\n}: SidebarProps) {\n const baseId = useId();\n const blocks = toBlocks(items);\n const activeKey = resolveActiveKey(items, value, match);\n const finalWidth =\n typeof (collapsed ? collapsedWidth : width) === \"number\"\n ? `${collapsed ? collapsedWidth : width}px`\n : collapsed\n ? collapsedWidth\n : width;\n\n return (\n <aside\n className={cn(styles.sidebar, collapsed && styles.collapsed, className)}\n style={{ width: finalWidth, ...style }}\n {...props}\n >\n {header && <div className={styles.header}>{header}</div>}\n <nav className={styles.nav} aria-label=\"Navegação lateral\">\n {blocks.map((block) => {\n if (block.kind === \"separator\") {\n return <hr key={block.key} className={styles.separator} />;\n }\n if (!block.section) {\n return (\n <div key={block.key} className={styles.group}>\n {block.items.map((item) => (\n <SidebarEntry\n key={item.key}\n item={item}\n active={item.key === activeKey}\n collapsed={collapsed}\n onSelect={onChange}\n />\n ))}\n </div>\n );\n }\n const labelId = `${baseId}-${block.key}`;\n return (\n <div\n key={block.key}\n role=\"group\"\n aria-labelledby={labelId}\n className={styles.group}\n >\n <div\n id={labelId}\n role=\"presentation\"\n className={cn(styles.section, collapsed && styles.sectionCollapsed)}\n >\n {block.section.label}\n </div>\n {block.items.map((item) => (\n <SidebarEntry\n key={item.key}\n item={item}\n active={item.key === activeKey}\n collapsed={collapsed}\n onSelect={onChange}\n />\n ))}\n </div>\n );\n })}\n </nav>\n {footer && <div className={styles.footer}>{footer}</div>}\n </aside>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sidebar.js","names":["styles","Sidebar_module_default","toBlocks","blocks","open","entry","entries","resolveActiveKey","match","value","items","activeNavIndex","item","Sidebar","props","baseId","useId","activeKey","finalWidth","collapsed","collapsedWidth","width","cn","className","style","header","block","SidebarEntry","onChange","labelId","footer"],"sources":["../../../src/components/Sidebar/Sidebar.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — header, items, footer are the three regions,\n * value/onChange/match the selection, and collapsed/width/collapsedWidth the two\n * layout modes it switches between.\n */\nimport { useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { activeNavIndex } from \"./active-nav-key\";\nimport styles from \"./Sidebar.module.css\";\nimport { SidebarEntry } from \"./SidebarEntry\";\n\nexport interface SidebarItem {\n key: string;\n label: ReactNode;\n icon?: ReactNode;\n badge?: ReactNode;\n disabled?: boolean;\n /**\n * Renders the entry as a link to this URL instead of a button.\n *\n * `onChange` still fires on click, so an app that tracks the active key keeps\n * working — the difference is that the entry now behaves like navigation:\n * middle-click, ctrl-click and \"copy link address\" do what the user expects,\n * and a screen reader announces a link rather than a button.\n *\n * A `disabled` entry ignores this and stays a `<button disabled>`: there is no\n * disabled state for an anchor, and dropping the `href` to fake one leaves a\n * link that announces itself as actionable and is not.\n */\n href?: string;\n}\n\n/**\n * A section heading. Every item after it belongs to the section, until the next\n * section or separator.\n */\nexport interface SidebarSection {\n type: \"section\";\n key: string;\n label: ReactNode;\n}\n\n/** A plain divider, for splitting the list without naming the parts. */\nexport interface SidebarSeparator {\n type: \"separator\";\n key: string;\n}\n\n/**\n * One entry of the navigation list.\n *\n * An entry with no `type` is an item, which is what keeps a plain\n * `SidebarItem[]` a valid `SidebarEntry[]`: adding sections to this component\n * needed no change at any existing call site.\n */\nexport type SidebarEntry = ({ type?: \"item\" } & SidebarItem) | SidebarSection | SidebarSeparator;\n\nexport interface SidebarProps extends Omit<HTMLAttributes<HTMLElement>, \"onChange\"> {\n /** Top slot — typically the logo + brand. */\n header?: ReactNode;\n /** Navigation entries. An entry with no `type` is an item. */\n items: SidebarEntry[];\n /**\n * The active item: its `key` by default, or the current route with\n * `match=\"route\"`.\n */\n value?: string;\n /**\n * How `value` picks the active item.\n *\n * - `\"key\"` (default): the item whose `key` equals `value`.\n * - `\"route\"`: `value` is the current pathname, and the active item is the one\n * whose `href` (or `key`, when it has no `href`) is the longest path covering\n * it on a segment boundary — see `activeNavKey`. `/dashboard/tips/42` lights\n * `/dashboard/tips`, not `/dashboard`, and `/users-admin` never lights\n * `/users`. Sections and separators take no part.\n *\n * `\"route\"` is what lets the app pass `useLocation().pathname` and stop\n * computing a key: the component already holds the entries the resolution runs\n * against, so asking the app to repeat them is how the two drift apart. It\n * reads no router itself — the value comes in as a string — so the component\n * keeps working with no router mounted.\n */\n match?: \"key\" | \"route\";\n /** Fires when an item is clicked. Receives the item's `key`. */\n onChange?: (key: string) => void;\n /** Bottom slot — typically settings/profile/logout. */\n footer?: ReactNode;\n /** Collapsed mode — only icons visible. Default `false`. */\n collapsed?: boolean;\n /** Width when expanded, in pixels or any CSS length. Default `240px`. */\n width?: number | string;\n /** Width when collapsed, in pixels or any CSS length. Default `64px`. */\n collapsedWidth?: number | string;\n}\n\n/** A run of items, optionally under a section heading. */\ninterface ItemBlock {\n kind: \"items\";\n key: string;\n section?: SidebarSection;\n items: SidebarItem[];\n}\n\n/** A standalone divider between blocks. */\ninterface SeparatorBlock {\n kind: \"separator\";\n key: string;\n}\n\ntype Block = ItemBlock | SeparatorBlock;\n\n/**\n * Fold the flat entry list into the blocks that get rendered.\n *\n * A section opens a block that swallows every following item; a separator closes\n * whatever is open, so items after it are loose again. Items before the first\n * section are loose too — which is the whole existing behaviour, and why a list\n * with no sections comes out of here as one unnamed block.\n *\n * @param entries - The `items` prop, as given.\n * @returns Blocks in render order.\n */\nfunction toBlocks(entries: readonly SidebarEntry[]): Block[] {\n const blocks: Block[] = [];\n let open: ItemBlock | undefined;\n\n for (const entry of entries) {\n if (entry.type === \"separator\") {\n blocks.push({ kind: \"separator\", key: entry.key });\n open = undefined;\n continue;\n }\n if (entry.type === \"section\") {\n open = { kind: \"items\", key: entry.key, section: entry, items: [] };\n blocks.push(open);\n continue;\n }\n if (!open) {\n open = { kind: \"items\", key: `loose-${entry.key}`, items: [] };\n blocks.push(open);\n }\n open.items.push(entry);\n }\n return blocks;\n}\n\n/**\n * The key of the item `value` selects, under the given `match` mode.\n *\n * @param entries - The `items` prop, as given.\n * @param value - The `value` prop.\n * @param match - The `match` prop.\n * @returns The active item's key, or `undefined` when no item is active.\n */\nfunction resolveActiveKey(\n entries: readonly SidebarEntry[],\n value: string | undefined,\n match: \"key\" | \"route\",\n): string | undefined {\n if (match === \"key\" || value === undefined) {\n return value;\n }\n const items = entries.filter(\n (entry): entry is { type?: \"item\" } & SidebarItem =>\n entry.type !== \"section\" && entry.type !== \"separator\",\n );\n return items[\n activeNavIndex(\n value,\n items.map((item) => item.href ?? item.key),\n )\n ]?.key;\n}\n\n/**\n * Desktop sidebar navigation. Pair with `<Show above=\"md\">` and a `Drawer`\n * for mobile.\n *\n * Sixteen screens in one flat list is a wall, so `items` also takes section\n * headings and separators. A section renders as a labelled `role=\"group\"`, which\n * is what tells a screen reader \"Monitoring, group, 3 items\" — the reason it is\n * not a styled `disabled` item, which would announce an unavailable button and\n * stay in the navigation order.\n *\n * @example\n * const [tab, setTab] = useState(\"home\");\n * <Show above=\"md\">\n * <Sidebar\n * header={<Brand />}\n * items={[{ key: \"home\", label: \"Home\", icon: <Home /> }]}\n * value={tab}\n * onChange={setTab}\n * />\n * </Show>\n *\n * @example\n * const { pathname } = useLocation();\n * <Sidebar\n * match=\"route\"\n * value={pathname}\n * items={[\n * { key: \"overview\", label: \"Overview\", href: \"/dashboard\" },\n * { key: \"tips\", label: \"Tips\", href: \"/dashboard/tips\" },\n * ]}\n * />\n *\n * @example\n * <Sidebar\n * items={[\n * { type: \"section\", key: \"monitoring\", label: \"Monitoring\" },\n * { key: \"overview\", label: \"Overview\", href: \"/overview\" },\n * { key: \"activity\", label: \"Activity\", href: \"/activity\" },\n * { type: \"section\", key: \"admin\", label: \"Administration\" },\n * { key: \"settings\", label: \"Settings\", href: \"/settings\" },\n * ]}\n * value={tab}\n * onChange={setTab}\n * />\n */\nexport function Sidebar({\n header,\n items,\n value,\n onChange,\n match = \"key\",\n footer,\n collapsed = false,\n width = 240,\n collapsedWidth = 64,\n className,\n style,\n ...props\n}: SidebarProps) {\n const baseId = useId();\n const blocks = toBlocks(items);\n const activeKey = resolveActiveKey(items, value, match);\n const finalWidth =\n typeof (collapsed ? collapsedWidth : width) === \"number\"\n ? `${collapsed ? collapsedWidth : width}px`\n : collapsed\n ? collapsedWidth\n : width;\n\n return (\n <aside\n className={cn(styles.sidebar, collapsed && styles.collapsed, className)}\n style={{ width: finalWidth, ...style }}\n {...props}\n >\n {header && <div className={styles.header}>{header}</div>}\n <nav className={styles.nav} aria-label=\"Navegação lateral\">\n {blocks.map((block) => {\n if (block.kind === \"separator\") {\n return <hr key={block.key} className={styles.separator} />;\n }\n if (!block.section) {\n return (\n <div key={block.key} className={styles.group}>\n {block.items.map((item) => (\n <SidebarEntry\n key={item.key}\n item={item}\n active={item.key === activeKey}\n collapsed={collapsed}\n onSelect={onChange}\n />\n ))}\n </div>\n );\n }\n const labelId = `${baseId}-${block.key}`;\n return (\n <div\n key={block.key}\n role=\"group\"\n aria-labelledby={labelId}\n className={styles.group}\n >\n <div\n id={labelId}\n role=\"presentation\"\n className={cn(styles.section, collapsed && styles.sectionCollapsed)}\n >\n {block.section.label}\n </div>\n {block.items.map((item) => (\n <SidebarEntry\n key={item.key}\n item={item}\n active={item.key === activeKey}\n collapsed={collapsed}\n onSelect={onChange}\n />\n ))}\n </div>\n );\n })}\n </nav>\n {footer && <div className={styles.footer}>{footer}</div>}\n </aside>\n );\n}\n"],"mappings":";;;;;;;AA2HA,SAASE,EAAS,GAA2C;CACzD,IAAMC,IAAkB,CAAC,GACrBC;CAEJ,KAAK,IAAMC,KAASC,GAChB,AAAID,EAAM,SAAS,eACfF,EAAO,KAAK;EAAE,MAAM;EAAa,KAAKE,EAAM;CAAI,CAAC,GACjD,IAAO,KAAA,KAGPA,EAAM,SAAS,aACf,IAAO;EAAE,MAAM;EAAS,KAAKA,EAAM;EAAK,SAASA;EAAO,OAAO,CAAC;CAAE,GAClEF,EAAO,KAAKC,CAAI,MAGfA,MACD,IAAO;EAAE,MAAM;EAAS,KAAK,SAASC,EAAM;EAAO,OAAO,CAAC;CAAE,GAC7DF,EAAO,KAAKC,CAAI,IAEpBA,EAAK,MAAM,KAAKC,CAAK;CAEzB,OAAOF;AACX;AAUA,SAASI,EACL,GACA,GACA,GACkB;CAClB,IAAIC,MAAU,SAASC,MAAU,KAAA,GAC7B,OAAOA;CAEX,IAAMC,IAAQJ,EAAQ,QACjB,MACGD,EAAM,SAAS,aAAaA,EAAM,SAAS,WACnD;CACA,OAAOK,EACHC,EACIF,GACAC,EAAM,KAAK,MAASE,EAAK,QAAQA,EAAK,GAAG,CAC7C,EACH,EAAE;AACP;AA+CA,SAAgBC,EAAQ,EACpB,WACA,UACA,UACA,aACA,WAAQ,OACR,WACA,eAAY,IACZ,WAAQ,KACR,oBAAiB,IACjB,cACA,UACA,GAAGC,KACU;CACb,IAAMC,IAASC,EAAM,GACfb,IAASD,EAASQ,CAAK,GACvBO,IAAYV,EAAiBG,GAAOD,GAAOD,CAAK,GAChDU,IACF,QAAQC,IAAYC,IAAiBC,MAAW,WAC1C,GAAGF,IAAYC,IAAiBC,EAAM,MACtCF,IACEC,IACAC;CAEZ,OACI,kBAAC,SAAD;EACI,WAAWC,EAAGtB,EAAO,SAASmB,KAAanB,EAAO,WAAWuB,CAAS;EACtE,OAAO;GAAE,OAAOL;GAAY,GAAGM;EAAM;EACrC,GAAIV;EAHR,UAAA;GAKKW,KAAU,kBAAC,OAAD;IAAK,WAAWzB,EAAO;IAAS,UAAAyB;GAAY,CAAA;GACvD,kBAAC,OAAD;IAAK,WAAWzB,EAAO;IAAK,cAAW;IAClC,UAAAG,EAAO,KAAK,MAAU;KACnB,IAAIuB,EAAM,SAAS,aACf,OAAO,kBAAC,MAAD,EAAoB,WAAW1B,EAAO,UAAY,GAAzC0B,EAAM,GAAmC;KAE7D,IAAI,CAACA,EAAM,SACP,OACI,kBAAC,OAAD;MAAqB,WAAW1B,EAAO;MAClC,UAAA0B,EAAM,MAAM,KAAK,MACd,kBAACC,GAAD;OAEU;OACN,QAAQf,EAAK,QAAQK;OACV;OACX,UAAUW;MACb,GALQhB,EAAK,GAKb,CACJ;KACA,GAVKc,EAAM,GAUX;KAGb,IAAMG,IAAU,GAAGd,EAAO,GAAGW,EAAM;KACnC,OACI,kBAAC,OAAD;MAEI,MAAK;MACL,mBAAiBG;MACjB,WAAW7B,EAAO;MAJtB,UAAA,CAMI,kBAAC,OAAD;OACI,IAAI6B;OACJ,MAAK;OACL,WAAWP,EAAGtB,EAAO,SAASmB,KAAanB,EAAO,gBAAgB;OAEjE,UAAA0B,EAAM,QAAQ;MACd,CAAA,GACJA,EAAM,MAAM,KAAK,MACd,kBAACC,GAAD;OAEU;OACN,QAAQf,EAAK,QAAQK;OACV;OACX,UAAUW;MACb,GALQhB,EAAK,GAKb,CACJ,CACA;KArBI,GAAAc,EAAM,GAqBV;IAEb,CAAC;GACA,CAAA;GACJI,KAAU,kBAAC,OAAD;IAAK,WAAW9B,EAAO;IAAS,UAAA8B;GAAY,CAAA;EACpD;;AAEf"}
|
|
1
|
+
{"version":3,"file":"Sidebar.js","names":["styles","Sidebar_module_default","toBlocks","blocks","open","entry","entries","resolveActiveKey","match","value","items","activeNavIndex","item","Sidebar","props","baseId","useId","activeKey","finalWidth","collapsed","collapsedWidth","width","cn","className","style","header","block","SidebarEntry","onChange","labelId","footer"],"sources":["../../../src/components/Sidebar/Sidebar.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — header, items, footer are the three regions,\n * value/onChange/match the selection, and collapsed/width/collapsedWidth the two\n * layout modes it switches between.\n */\nimport { useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { activeNavIndex } from \"./active-nav-key\";\nimport styles from \"./Sidebar.module.css\";\nimport { SidebarEntry } from \"./SidebarEntry\";\n\nexport interface SidebarItem {\n key: string;\n label: ReactNode;\n icon?: ReactNode;\n badge?: ReactNode;\n disabled?: boolean;\n /**\n * Renders the entry as a link to this URL instead of a button.\n *\n * `onChange` still fires on click, so an app that tracks the active key keeps\n * working — the difference is that the entry now behaves like navigation:\n * middle-click, ctrl-click and \"copy link address\" do what the user expects,\n * and a screen reader announces a link rather than a button.\n *\n * A `disabled` entry ignores this and stays a `<button disabled>`: there is no\n * disabled state for an anchor, and dropping the `href` to fake one leaves a\n * link that announces itself as actionable and is not.\n *\n * The URL goes through `safeLinkUrl`: a scheme outside\n * `http:`/`https:`/`mailto:`/`tel:`/`sms:` makes the entry a button, the same\n * as an entry without `href`.\n */\n href?: string;\n}\n\n/**\n * A section heading. Every item after it belongs to the section, until the next\n * section or separator.\n */\nexport interface SidebarSection {\n type: \"section\";\n key: string;\n label: ReactNode;\n}\n\n/** A plain divider, for splitting the list without naming the parts. */\nexport interface SidebarSeparator {\n type: \"separator\";\n key: string;\n}\n\n/**\n * One entry of the navigation list.\n *\n * An entry with no `type` is an item, which is what keeps a plain\n * `SidebarItem[]` a valid `SidebarEntry[]`: adding sections to this component\n * needed no change at any existing call site.\n */\nexport type SidebarEntry = ({ type?: \"item\" } & SidebarItem) | SidebarSection | SidebarSeparator;\n\nexport interface SidebarProps extends Omit<HTMLAttributes<HTMLElement>, \"onChange\"> {\n /** Top slot — typically the logo + brand. */\n header?: ReactNode;\n /** Navigation entries. An entry with no `type` is an item. */\n items: SidebarEntry[];\n /**\n * The active item: its `key` by default, or the current route with\n * `match=\"route\"`.\n */\n value?: string;\n /**\n * How `value` picks the active item.\n *\n * - `\"key\"` (default): the item whose `key` equals `value`.\n * - `\"route\"`: `value` is the current pathname, and the active item is the one\n * whose `href` (or `key`, when it has no `href`) is the longest path covering\n * it on a segment boundary — see `activeNavKey`. `/dashboard/tips/42` lights\n * `/dashboard/tips`, not `/dashboard`, and `/users-admin` never lights\n * `/users`. Sections and separators take no part.\n *\n * `\"route\"` is what lets the app pass `useLocation().pathname` and stop\n * computing a key: the component already holds the entries the resolution runs\n * against, so asking the app to repeat them is how the two drift apart. It\n * reads no router itself — the value comes in as a string — so the component\n * keeps working with no router mounted.\n */\n match?: \"key\" | \"route\";\n /** Fires when an item is clicked. Receives the item's `key`. */\n onChange?: (key: string) => void;\n /** Bottom slot — typically settings/profile/logout. */\n footer?: ReactNode;\n /** Collapsed mode — only icons visible. Default `false`. */\n collapsed?: boolean;\n /** Width when expanded, in pixels or any CSS length. Default `240px`. */\n width?: number | string;\n /** Width when collapsed, in pixels or any CSS length. Default `64px`. */\n collapsedWidth?: number | string;\n}\n\n/** A run of items, optionally under a section heading. */\ninterface ItemBlock {\n kind: \"items\";\n key: string;\n section?: SidebarSection;\n items: SidebarItem[];\n}\n\n/** A standalone divider between blocks. */\ninterface SeparatorBlock {\n kind: \"separator\";\n key: string;\n}\n\ntype Block = ItemBlock | SeparatorBlock;\n\n/**\n * Fold the flat entry list into the blocks that get rendered.\n *\n * A section opens a block that swallows every following item; a separator closes\n * whatever is open, so items after it are loose again. Items before the first\n * section are loose too — which is the whole existing behaviour, and why a list\n * with no sections comes out of here as one unnamed block.\n *\n * @param entries - The `items` prop, as given.\n * @returns Blocks in render order.\n */\nfunction toBlocks(entries: readonly SidebarEntry[]): Block[] {\n const blocks: Block[] = [];\n let open: ItemBlock | undefined;\n\n for (const entry of entries) {\n if (entry.type === \"separator\") {\n blocks.push({ kind: \"separator\", key: entry.key });\n open = undefined;\n continue;\n }\n if (entry.type === \"section\") {\n open = { kind: \"items\", key: entry.key, section: entry, items: [] };\n blocks.push(open);\n continue;\n }\n if (!open) {\n open = { kind: \"items\", key: `loose-${entry.key}`, items: [] };\n blocks.push(open);\n }\n open.items.push(entry);\n }\n return blocks;\n}\n\n/**\n * The key of the item `value` selects, under the given `match` mode.\n *\n * @param entries - The `items` prop, as given.\n * @param value - The `value` prop.\n * @param match - The `match` prop.\n * @returns The active item's key, or `undefined` when no item is active.\n */\nfunction resolveActiveKey(\n entries: readonly SidebarEntry[],\n value: string | undefined,\n match: \"key\" | \"route\",\n): string | undefined {\n if (match === \"key\" || value === undefined) {\n return value;\n }\n const items = entries.filter(\n (entry): entry is { type?: \"item\" } & SidebarItem =>\n entry.type !== \"section\" && entry.type !== \"separator\",\n );\n return items[\n activeNavIndex(\n value,\n items.map((item) => item.href ?? item.key),\n )\n ]?.key;\n}\n\n/**\n * Desktop sidebar navigation. Pair with `<Show above=\"md\">` and a `Drawer`\n * for mobile.\n *\n * Sixteen screens in one flat list is a wall, so `items` also takes section\n * headings and separators. A section renders as a labelled `role=\"group\"`, which\n * is what tells a screen reader \"Monitoring, group, 3 items\" — the reason it is\n * not a styled `disabled` item, which would announce an unavailable button and\n * stay in the navigation order.\n *\n * @example\n * const [tab, setTab] = useState(\"home\");\n * <Show above=\"md\">\n * <Sidebar\n * header={<Brand />}\n * items={[{ key: \"home\", label: \"Home\", icon: <Home /> }]}\n * value={tab}\n * onChange={setTab}\n * />\n * </Show>\n *\n * @example\n * const { pathname } = useLocation();\n * <Sidebar\n * match=\"route\"\n * value={pathname}\n * items={[\n * { key: \"overview\", label: \"Overview\", href: \"/dashboard\" },\n * { key: \"tips\", label: \"Tips\", href: \"/dashboard/tips\" },\n * ]}\n * />\n *\n * @example\n * <Sidebar\n * items={[\n * { type: \"section\", key: \"monitoring\", label: \"Monitoring\" },\n * { key: \"overview\", label: \"Overview\", href: \"/overview\" },\n * { key: \"activity\", label: \"Activity\", href: \"/activity\" },\n * { type: \"section\", key: \"admin\", label: \"Administration\" },\n * { key: \"settings\", label: \"Settings\", href: \"/settings\" },\n * ]}\n * value={tab}\n * onChange={setTab}\n * />\n */\nexport function Sidebar({\n header,\n items,\n value,\n onChange,\n match = \"key\",\n footer,\n collapsed = false,\n width = 240,\n collapsedWidth = 64,\n className,\n style,\n ...props\n}: SidebarProps) {\n const baseId = useId();\n const blocks = toBlocks(items);\n const activeKey = resolveActiveKey(items, value, match);\n const finalWidth =\n typeof (collapsed ? collapsedWidth : width) === \"number\"\n ? `${collapsed ? collapsedWidth : width}px`\n : collapsed\n ? collapsedWidth\n : width;\n\n return (\n <aside\n className={cn(styles.sidebar, collapsed && styles.collapsed, className)}\n style={{ width: finalWidth, ...style }}\n {...props}\n >\n {header && <div className={styles.header}>{header}</div>}\n <nav className={styles.nav} aria-label=\"Navegação lateral\">\n {blocks.map((block) => {\n if (block.kind === \"separator\") {\n return <hr key={block.key} className={styles.separator} />;\n }\n if (!block.section) {\n return (\n <div key={block.key} className={styles.group}>\n {block.items.map((item) => (\n <SidebarEntry\n key={item.key}\n item={item}\n active={item.key === activeKey}\n collapsed={collapsed}\n onSelect={onChange}\n />\n ))}\n </div>\n );\n }\n const labelId = `${baseId}-${block.key}`;\n return (\n <div\n key={block.key}\n role=\"group\"\n aria-labelledby={labelId}\n className={styles.group}\n >\n <div\n id={labelId}\n role=\"presentation\"\n className={cn(styles.section, collapsed && styles.sectionCollapsed)}\n >\n {block.section.label}\n </div>\n {block.items.map((item) => (\n <SidebarEntry\n key={item.key}\n item={item}\n active={item.key === activeKey}\n collapsed={collapsed}\n onSelect={onChange}\n />\n ))}\n </div>\n );\n })}\n </nav>\n {footer && <div className={styles.footer}>{footer}</div>}\n </aside>\n );\n}\n"],"mappings":";;;;;;;AA+HA,SAASE,EAAS,GAA2C;CACzD,IAAMC,IAAkB,CAAC,GACrBC;CAEJ,KAAK,IAAMC,KAASC,GAChB,AAAID,EAAM,SAAS,eACfF,EAAO,KAAK;EAAE,MAAM;EAAa,KAAKE,EAAM;CAAI,CAAC,GACjD,IAAO,KAAA,KAGPA,EAAM,SAAS,aACf,IAAO;EAAE,MAAM;EAAS,KAAKA,EAAM;EAAK,SAASA;EAAO,OAAO,CAAC;CAAE,GAClEF,EAAO,KAAKC,CAAI,MAGfA,MACD,IAAO;EAAE,MAAM;EAAS,KAAK,SAASC,EAAM;EAAO,OAAO,CAAC;CAAE,GAC7DF,EAAO,KAAKC,CAAI,IAEpBA,EAAK,MAAM,KAAKC,CAAK;CAEzB,OAAOF;AACX;AAUA,SAASI,EACL,GACA,GACA,GACkB;CAClB,IAAIC,MAAU,SAASC,MAAU,KAAA,GAC7B,OAAOA;CAEX,IAAMC,IAAQJ,EAAQ,QACjB,MACGD,EAAM,SAAS,aAAaA,EAAM,SAAS,WACnD;CACA,OAAOK,EACHC,EACIF,GACAC,EAAM,KAAK,MAASE,EAAK,QAAQA,EAAK,GAAG,CAC7C,EACH,EAAE;AACP;AA+CA,SAAgBC,EAAQ,EACpB,WACA,UACA,UACA,aACA,WAAQ,OACR,WACA,eAAY,IACZ,WAAQ,KACR,oBAAiB,IACjB,cACA,UACA,GAAGC,KACU;CACb,IAAMC,IAASC,EAAM,GACfb,IAASD,EAASQ,CAAK,GACvBO,IAAYV,EAAiBG,GAAOD,GAAOD,CAAK,GAChDU,IACF,QAAQC,IAAYC,IAAiBC,MAAW,WAC1C,GAAGF,IAAYC,IAAiBC,EAAM,MACtCF,IACEC,IACAC;CAEZ,OACI,kBAAC,SAAD;EACI,WAAWC,EAAGtB,EAAO,SAASmB,KAAanB,EAAO,WAAWuB,CAAS;EACtE,OAAO;GAAE,OAAOL;GAAY,GAAGM;EAAM;EACrC,GAAIV;EAHR,UAAA;GAKKW,KAAU,kBAAC,OAAD;IAAK,WAAWzB,EAAO;IAAS,UAAAyB;GAAY,CAAA;GACvD,kBAAC,OAAD;IAAK,WAAWzB,EAAO;IAAK,cAAW;IAClC,UAAAG,EAAO,KAAK,MAAU;KACnB,IAAIuB,EAAM,SAAS,aACf,OAAO,kBAAC,MAAD,EAAoB,WAAW1B,EAAO,UAAY,GAAzC0B,EAAM,GAAmC;KAE7D,IAAI,CAACA,EAAM,SACP,OACI,kBAAC,OAAD;MAAqB,WAAW1B,EAAO;MAClC,UAAA0B,EAAM,MAAM,KAAK,MACd,kBAACC,GAAD;OAEU;OACN,QAAQf,EAAK,QAAQK;OACV;OACX,UAAUW;MACb,GALQhB,EAAK,GAKb,CACJ;KACA,GAVKc,EAAM,GAUX;KAGb,IAAMG,IAAU,GAAGd,EAAO,GAAGW,EAAM;KACnC,OACI,kBAAC,OAAD;MAEI,MAAK;MACL,mBAAiBG;MACjB,WAAW7B,EAAO;MAJtB,UAAA,CAMI,kBAAC,OAAD;OACI,IAAI6B;OACJ,MAAK;OACL,WAAWP,EAAGtB,EAAO,SAASmB,KAAanB,EAAO,gBAAgB;OAEjE,UAAA0B,EAAM,QAAQ;MACd,CAAA,GACJA,EAAM,MAAM,KAAK,MACd,kBAACC,GAAD;OAEU;OACN,QAAQf,EAAK,QAAQK;OACV;OACX,UAAUW;MACb,GALQhB,EAAK,GAKb,CACJ,CACA;KArBI,GAAAc,EAAM,GAqBV;IAEb,CAAC;GACA,CAAA;GACJI,KAAU,kBAAC,OAAD;IAAK,WAAW9B,EAAO;IAAS,UAAA8B;GAAY,CAAA;EACpD;;AAEf"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../../utils/cn.cjs"),t=require("./Sidebar.module.cjs");let
|
|
1
|
+
const e=require("../../utils/cn.cjs"),t=require("../../utils/safe-url.cjs"),n=require("./Sidebar.module.cjs");let r=require("react/jsx-runtime");function i({item:i,active:a,collapsed:o,onSelect:s}){let c=o&&typeof i.label==`string`?i.label:void 0,l=(0,r.jsxs)(r.Fragment,{children:[i.icon&&(0,r.jsx)(`span`,{className:n.default.icon,children:i.icon}),!o&&(0,r.jsx)(`span`,{className:n.default.label,children:i.label}),!o&&i.badge!==void 0&&(0,r.jsx)(`span`,{className:n.default.badge,children:i.badge})]}),u=t.vettedHref(i.href);return u&&!i.disabled?(0,r.jsx)(`a`,{href:u,className:e.cn(n.default.item,a&&n.default.active),"aria-current":a?`page`:void 0,onClick:()=>s?.(i.key),title:c,children:l}):(0,r.jsx)(`button`,{type:`button`,className:e.cn(n.default.item,a&&n.default.active),"aria-current":a?`page`:void 0,disabled:i.disabled,onClick:()=>s?.(i.key),title:c,children:l})}exports.SidebarEntry=i;
|
|
2
2
|
//# sourceMappingURL=SidebarEntry.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SidebarEntry.cjs","names":["_Fragment","styles","cn","require_cn","require_Sidebar_module","react_jsx_runtime","SidebarEntry","title","collapsed","item","body","active","onSelect"],"sources":["../../../src/components/Sidebar/SidebarEntry.tsx"],"sourcesContent":["import { cn } from \"@/utils/cn\";\n\nimport type { SidebarItem } from \"./Sidebar\";\nimport styles from \"./Sidebar.module.css\";\n\n/**\n * One navigation entry: an anchor when it carries an `href`, a button otherwise.\n *\n * A link that navigates has to be a link — middle-click, \"open in new tab\" and\n * the status bar preview all come from the element, not from the handler. A\n * disabled entry falls back to the button, because a disabled anchor does not\n * exist in HTML.\n */\nexport function SidebarEntry({\n item,\n active,\n collapsed,\n onSelect,\n}: {\n item: SidebarItem;\n active: boolean;\n collapsed: boolean;\n onSelect?: (key: string) => void;\n}) {\n const title = collapsed && typeof item.label === \"string\" ? item.label : undefined;\n const body = (\n <>\n {item.icon && <span className={styles.icon}>{item.icon}</span>}\n {!collapsed && <span className={styles.label}>{item.label}</span>}\n {!collapsed && item.badge !== undefined && (\n <span className={styles.badge}>{item.badge}</span>\n )}\n </>\n );\n\n
|
|
1
|
+
{"version":3,"file":"SidebarEntry.cjs","names":["_Fragment","styles","vettedHref","cn","require_cn","require_safe_url","require_Sidebar_module","react_jsx_runtime","SidebarEntry","title","collapsed","item","body","href","active","onSelect"],"sources":["../../../src/components/Sidebar/SidebarEntry.tsx"],"sourcesContent":["import { cn } from \"@/utils/cn\";\nimport { vettedHref } from \"@/utils/safe-url\";\n\nimport type { SidebarItem } from \"./Sidebar\";\nimport styles from \"./Sidebar.module.css\";\n\n/**\n * One navigation entry: an anchor when it carries an `href`, a button otherwise.\n *\n * A link that navigates has to be a link — middle-click, \"open in new tab\" and\n * the status bar preview all come from the element, not from the handler. A\n * disabled entry falls back to the button, because a disabled anchor does not\n * exist in HTML.\n *\n * The `href` is vetted with `safeLinkUrl`'s allowlist; an entry whose URL uses another\n * scheme renders as the button, so a menu built from API data cannot put a\n * `javascript:` link in the sidebar on React 18.\n */\nexport function SidebarEntry({\n item,\n active,\n collapsed,\n onSelect,\n}: {\n item: SidebarItem;\n active: boolean;\n collapsed: boolean;\n onSelect?: (key: string) => void;\n}) {\n const title = collapsed && typeof item.label === \"string\" ? item.label : undefined;\n const body = (\n <>\n {item.icon && <span className={styles.icon}>{item.icon}</span>}\n {!collapsed && <span className={styles.label}>{item.label}</span>}\n {!collapsed && item.badge !== undefined && (\n <span className={styles.badge}>{item.badge}</span>\n )}\n </>\n );\n\n const href = vettedHref(item.href);\n if (href && !item.disabled) {\n return (\n <a\n href={href}\n className={cn(styles.item, active && styles.active)}\n aria-current={active ? \"page\" : undefined}\n onClick={() => onSelect?.(item.key)}\n title={title}\n >\n {body}\n </a>\n );\n }\n\n return (\n <button\n type=\"button\"\n className={cn(styles.item, active && styles.active)}\n aria-current={active ? \"page\" : undefined}\n disabled={item.disabled}\n onClick={() => onSelect?.(item.key)}\n title={title}\n >\n {body}\n </button>\n );\n}\n"],"mappings":"A,iJAkBA,SAAgBQ,EAAa,CACzB,OACA,SACA,YACA,YAMD,CACC,IAAMC,EAAQC,GAAa,OAAOC,EAAK,OAAU,SAAWA,EAAK,MAAQ,IAAA,GACnEC,GACF,EAAAL,EAAA,KAAA,CAAAP,EAAAA,SAAA,CAAA,SAAA,CACKW,EAAK,OAAQ,EAAAJ,EAAA,IAAA,CAAC,OAAD,CAAM,UAAWN,EAAAA,QAAO,KAAO,SAAAU,EAAK,IAAW,CAAA,EAC5D,CAACD,IAAa,EAAAH,EAAA,IAAA,CAAC,OAAD,CAAM,UAAWN,EAAAA,QAAO,MAAQ,SAAAU,EAAK,KAAY,CAAA,EAC/D,CAACD,GAAaC,EAAK,QAAU,IAAA,KAC1B,EAAAJ,EAAA,IAAA,CAAC,OAAD,CAAM,UAAWN,EAAAA,QAAO,MAAQ,SAAAU,EAAK,KAAY,CAAA,CAEvD,CAAA,CAAA,EAGAE,EAAOX,EAAAA,WAAWS,EAAK,IAAI,EAejC,OAdIE,GAAQ,CAACF,EAAK,UAEV,EAAAJ,EAAA,IAAA,CAAC,IAAD,CACU,OACN,UAAWJ,EAAAA,GAAGF,EAAAA,QAAO,KAAMa,GAAUb,EAAAA,QAAO,MAAM,EAClD,eAAca,EAAS,OAAS,IAAA,GAChC,YAAeC,IAAWJ,EAAK,GAAG,EAC3B,QAEN,SAAAC,CACF,CAAA,GAKP,EAAAL,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,UAAWJ,EAAAA,GAAGF,EAAAA,QAAO,KAAMa,GAAUb,EAAAA,QAAO,MAAM,EAClD,eAAca,EAAS,OAAS,IAAA,GAChC,SAAUH,EAAK,SACf,YAAeI,IAAWJ,EAAK,GAAG,EAC3B,QAEN,SAAAC,CACG,CAAA,CAEhB,C"}
|
|
@@ -1,40 +1,41 @@
|
|
|
1
1
|
import { cn as e } from "../../utils/cn.js";
|
|
2
|
-
import t from "
|
|
3
|
-
import
|
|
2
|
+
import { vettedHref as t } from "../../utils/safe-url.js";
|
|
3
|
+
import n from "./Sidebar.module.js";
|
|
4
|
+
import { Fragment as r, jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
4
5
|
//#region src/components/Sidebar/SidebarEntry.tsx
|
|
5
|
-
function
|
|
6
|
-
let
|
|
7
|
-
|
|
8
|
-
className:
|
|
9
|
-
children:
|
|
6
|
+
function o({ item: o, active: s, collapsed: c, onSelect: l }) {
|
|
7
|
+
let u = c && typeof o.label == "string" ? o.label : void 0, d = /* @__PURE__ */ a(r, { children: [
|
|
8
|
+
o.icon && /* @__PURE__ */ i("span", {
|
|
9
|
+
className: n.icon,
|
|
10
|
+
children: o.icon
|
|
10
11
|
}),
|
|
11
|
-
!
|
|
12
|
-
className:
|
|
13
|
-
children:
|
|
12
|
+
!c && /* @__PURE__ */ i("span", {
|
|
13
|
+
className: n.label,
|
|
14
|
+
children: o.label
|
|
14
15
|
}),
|
|
15
|
-
!
|
|
16
|
-
className:
|
|
17
|
-
children:
|
|
16
|
+
!c && o.badge !== void 0 && /* @__PURE__ */ i("span", {
|
|
17
|
+
className: n.badge,
|
|
18
|
+
children: o.badge
|
|
18
19
|
})
|
|
19
|
-
] });
|
|
20
|
-
return
|
|
21
|
-
href:
|
|
22
|
-
className: e(
|
|
23
|
-
"aria-current":
|
|
24
|
-
onClick: () =>
|
|
25
|
-
title:
|
|
26
|
-
children:
|
|
27
|
-
}) : /* @__PURE__ */
|
|
20
|
+
] }), f = t(o.href);
|
|
21
|
+
return f && !o.disabled ? /* @__PURE__ */ i("a", {
|
|
22
|
+
href: f,
|
|
23
|
+
className: e(n.item, s && n.active),
|
|
24
|
+
"aria-current": s ? "page" : void 0,
|
|
25
|
+
onClick: () => l?.(o.key),
|
|
26
|
+
title: u,
|
|
27
|
+
children: d
|
|
28
|
+
}) : /* @__PURE__ */ i("button", {
|
|
28
29
|
type: "button",
|
|
29
|
-
className: e(
|
|
30
|
-
"aria-current":
|
|
31
|
-
disabled:
|
|
32
|
-
onClick: () =>
|
|
33
|
-
title:
|
|
34
|
-
children:
|
|
30
|
+
className: e(n.item, s && n.active),
|
|
31
|
+
"aria-current": s ? "page" : void 0,
|
|
32
|
+
disabled: o.disabled,
|
|
33
|
+
onClick: () => l?.(o.key),
|
|
34
|
+
title: u,
|
|
35
|
+
children: d
|
|
35
36
|
});
|
|
36
37
|
}
|
|
37
38
|
//#endregion
|
|
38
|
-
export {
|
|
39
|
+
export { o as SidebarEntry };
|
|
39
40
|
|
|
40
41
|
//# sourceMappingURL=SidebarEntry.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SidebarEntry.js","names":["_Fragment","styles","Sidebar_module_default","SidebarEntry","title","collapsed","item","body","Fragment","cn","active","onSelect"],"sources":["../../../src/components/Sidebar/SidebarEntry.tsx"],"sourcesContent":["import { cn } from \"@/utils/cn\";\n\nimport type { SidebarItem } from \"./Sidebar\";\nimport styles from \"./Sidebar.module.css\";\n\n/**\n * One navigation entry: an anchor when it carries an `href`, a button otherwise.\n *\n * A link that navigates has to be a link — middle-click, \"open in new tab\" and\n * the status bar preview all come from the element, not from the handler. A\n * disabled entry falls back to the button, because a disabled anchor does not\n * exist in HTML.\n */\nexport function SidebarEntry({\n item,\n active,\n collapsed,\n onSelect,\n}: {\n item: SidebarItem;\n active: boolean;\n collapsed: boolean;\n onSelect?: (key: string) => void;\n}) {\n const title = collapsed && typeof item.label === \"string\" ? item.label : undefined;\n const body = (\n <>\n {item.icon && <span className={styles.icon}>{item.icon}</span>}\n {!collapsed && <span className={styles.label}>{item.label}</span>}\n {!collapsed && item.badge !== undefined && (\n <span className={styles.badge}>{item.badge}</span>\n )}\n </>\n );\n\n
|
|
1
|
+
{"version":3,"file":"SidebarEntry.js","names":["_Fragment","styles","Sidebar_module_default","SidebarEntry","title","collapsed","item","body","Fragment","href","vettedHref","cn","active","onSelect"],"sources":["../../../src/components/Sidebar/SidebarEntry.tsx"],"sourcesContent":["import { cn } from \"@/utils/cn\";\nimport { vettedHref } from \"@/utils/safe-url\";\n\nimport type { SidebarItem } from \"./Sidebar\";\nimport styles from \"./Sidebar.module.css\";\n\n/**\n * One navigation entry: an anchor when it carries an `href`, a button otherwise.\n *\n * A link that navigates has to be a link — middle-click, \"open in new tab\" and\n * the status bar preview all come from the element, not from the handler. A\n * disabled entry falls back to the button, because a disabled anchor does not\n * exist in HTML.\n *\n * The `href` is vetted with `safeLinkUrl`'s allowlist; an entry whose URL uses another\n * scheme renders as the button, so a menu built from API data cannot put a\n * `javascript:` link in the sidebar on React 18.\n */\nexport function SidebarEntry({\n item,\n active,\n collapsed,\n onSelect,\n}: {\n item: SidebarItem;\n active: boolean;\n collapsed: boolean;\n onSelect?: (key: string) => void;\n}) {\n const title = collapsed && typeof item.label === \"string\" ? item.label : undefined;\n const body = (\n <>\n {item.icon && <span className={styles.icon}>{item.icon}</span>}\n {!collapsed && <span className={styles.label}>{item.label}</span>}\n {!collapsed && item.badge !== undefined && (\n <span className={styles.badge}>{item.badge}</span>\n )}\n </>\n );\n\n const href = vettedHref(item.href);\n if (href && !item.disabled) {\n return (\n <a\n href={href}\n className={cn(styles.item, active && styles.active)}\n aria-current={active ? \"page\" : undefined}\n onClick={() => onSelect?.(item.key)}\n title={title}\n >\n {body}\n </a>\n );\n }\n\n return (\n <button\n type=\"button\"\n className={cn(styles.item, active && styles.active)}\n aria-current={active ? \"page\" : undefined}\n disabled={item.disabled}\n onClick={() => onSelect?.(item.key)}\n title={title}\n >\n {body}\n </button>\n );\n}\n"],"mappings":";;;;;AAkBA,SAAgBG,EAAa,EACzB,SACA,WACA,cACA,eAMD;CACC,IAAMC,IAAQC,KAAa,OAAOC,EAAK,SAAU,WAAWA,EAAK,QAAQ,KAAA,GACnEC,IACF,kBAAAP,GAAA,EAAA,UAAA;EACKM,EAAK,QAAQ,kBAAC,QAAD;GAAM,WAAWL,EAAO;GAAO,UAAAK,EAAK;EAAW,CAAA;EAC5D,CAACD,KAAa,kBAAC,QAAD;GAAM,WAAWJ,EAAO;GAAQ,UAAAK,EAAK;EAAY,CAAA;EAC/D,CAACD,KAAaC,EAAK,UAAU,KAAA,KAC1B,kBAAC,QAAD;GAAM,WAAWL,EAAO;GAAQ,UAAAK,EAAK;EAAY,CAAA;CAEvD,EAAA,CAAA,GAGAG,IAAOC,EAAWJ,EAAK,IAAI;CAejC,OAdIG,KAAQ,CAACH,EAAK,WAEV,kBAAC,KAAD;EACU;EACN,WAAWK,EAAGV,EAAO,MAAMW,KAAUX,EAAO,MAAM;EAClD,gBAAcW,IAAS,SAAS,KAAA;EAChC,eAAeC,IAAWP,EAAK,GAAG;EAC3B;EAEN,UAAAC;CACF,CAAA,IAKP,kBAAC,UAAD;EACI,MAAK;EACL,WAAWI,EAAGV,EAAO,MAAMW,KAAUX,EAAO,MAAM;EAClD,gBAAcW,IAAS,SAAS,KAAA;EAChC,UAAUN,EAAK;EACf,eAAeO,IAAWP,EAAK,GAAG;EAC3B;EAEN,UAAAC;CACG,CAAA;AAEhB"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var e=new Set([``,`.`,`..`]);function t(t){if(t==null)throw TypeError(`Invalid record id ${String(t)}: a record id is required.`);let n=String(t);if(e.has(n))throw TypeError(`Invalid record id ${JSON.stringify(n)}: it would resolve as a path dot-segment.`);return encodeURIComponent(n)}var n=(e,t)=>t==null?`/${e}`:`/${e}/${t}`;function r(e,r={}){let{pageParam:i=`page`,sizeParam:a=`size`,sortFieldParam:o=`order_by`,sortOrderParam:s=`ascending`,sortOrderAsBoolean:c=!0,updateMethod:l=`patch`,buildPath:u=n}=r,d={getList(t,n={}){let{pagination:r,sort:l,filters:d}=n,f={};if(r?.page!=null&&(f[i]=r.page),r?.pageSize!=null&&(f[a]=r.pageSize),l?.field){let e=l.order??`asc`;f[o]=l.field,f[s]=c?e===`asc`:e}
|
|
1
|
+
var e=new Set([``,`.`,`..`]);function t(t){if(t==null)throw TypeError(`Invalid record id ${String(t)}: a record id is required.`);let n=String(t);if(e.has(n))throw TypeError(`Invalid record id ${JSON.stringify(n)}: it would resolve as a path dot-segment.`);return encodeURIComponent(n)}var n=(e,t)=>t==null?`/${e}`:`/${e}/${t}`;function r(e,r={}){let{pageParam:i=`page`,sizeParam:a=`size`,sortFieldParam:o=`order_by`,sortOrderParam:s=`ascending`,sortOrderAsBoolean:c=!0,updateMethod:l=`patch`,buildPath:u=n}=r,d={getList(t,n={}){let{pagination:r,sort:l,filters:d}=n,f={...d};if(r?.page!=null&&(f[i]=r.page),r?.pageSize!=null&&(f[a]=r.pageSize),l?.field){let e=l.order??`asc`;f[o]=l.field,f[s]=c?e===`asc`:e}let p={params:f};return e.get(u(t),p)},async getOne(n,r){return e.get(u(n,t(r)))},getMany(e,t){return Promise.all(t.map(t=>d.getOne(e,t)))},create(t,n){return e.post(u(t),{body:n})},async update(n,r,i){let a=u(n,t(r));return l===`put`?e.put(a,{body:i}):e.patch(a,{body:i})},async deleteOne(n,r){return e.delete(u(n,t(r)))}};return d}exports.createDataProvider=r,exports.encodeIdSegment=t;
|
|
2
2
|
//# sourceMappingURL=create-data-provider.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-data-provider.cjs","names":["DOT_SEGMENTS","encodeIdSegment","id","raw","defaultBuildPath","resource","createDataProvider","options","provider","params","query","pagination","pageParam","sizeParam","sort","order","sortFieldParam","sortOrderParam","sortOrderAsBoolean","filters","key","value","requestOptions","client","buildPath","ids","data","path","updateMethod"],"sources":["../../src/data/create-data-provider.ts"],"sourcesContent":["import type { ApiClient, RequestOptions } from \"@/http/types\";\nimport type { OffsetPage } from \"@/query/pagination\";\n\n/** Query-param values accepted by the API client for a list request. */\ntype ParamValue = string | number | boolean | undefined | null;\n\n/** Filters passed to {@link DataProvider.getList}, spread verbatim into the query string. */\nexport type DataFilters = Record<string, ParamValue>;\n\n/** Parameters for a paginated, sorted, filtered list request. */\nexport interface GetListParams {\n /** Offset pagination — 1-based `page` and `pageSize`. */\n pagination?: { page?: number; pageSize?: number };\n /** Single-field sort. `order` defaults to `\"asc\"`. */\n sort?: { field: string; order?: \"asc\" | \"desc\" };\n /** Arbitrary filter query params, spread onto the request. */\n filters?: DataFilters;\n}\n\n/**\n * Refine-style data provider over the Tempest FastAPI SDK CRUD/pagination\n * conventions. Implementations are stateless wrappers around an {@link ApiClient}.\n */\nexport interface DataProvider {\n /**\n * Fetch a paginated list of a resource.\n *\n * @param resource - The resource name (e.g. `\"users\"`).\n * @param params - Pagination, sort and filter parameters.\n * @returns The offset-paginated envelope.\n */\n getList<T>(resource: string, params?: GetListParams): Promise<OffsetPage<T>>;\n /**\n * Fetch a single record by id.\n *\n * @param resource - The resource name.\n * @param id - The record id.\n * @returns The record.\n */\n getOne<T>(resource: string, id: string | number): Promise<T>;\n /**\n * Fetch many records by id (default: parallel {@link DataProvider.getOne}).\n *\n * @param resource - The resource name.\n * @param ids - The record ids.\n * @returns The records, in the same order as `ids`.\n */\n getMany<T>(resource: string, ids: (string | number)[]): Promise<T[]>;\n /**\n * Create a record.\n *\n * @param resource - The resource name.\n * @param data - The creation payload.\n * @returns The created record.\n */\n create<T>(resource: string, data: unknown): Promise<T>;\n /**\n * Update a record (PATCH by default, PUT when configured).\n *\n * @param resource - The resource name.\n * @param id - The record id.\n * @param data - The update payload.\n * @returns The updated record.\n */\n update<T>(resource: string, id: string | number, data: unknown): Promise<T>;\n /**\n * Delete a record by id.\n *\n * @param resource - The resource name.\n * @param id - The record id.\n * @returns The delete response (often the deleted record).\n */\n deleteOne<T>(resource: string, id: string | number): Promise<T>;\n}\n\n/** Options to tailor {@link createDataProvider} to a backend's conventions. */\nexport interface DataProviderOptions {\n /** Query-param name for the page number. Default: `\"page\"`. */\n pageParam?: string;\n /** Query-param name for the page size. Default: `\"size\"`. */\n sizeParam?: string;\n /** Query-param name for the sort field. Default: `\"order_by\"`. */\n sortFieldParam?: string;\n /** Query-param name for the sort order. Default: `\"ascending\"`. */\n sortOrderParam?: string;\n /**\n * When `true` (default), emit the sort order as a boolean\n * (`order:\"asc\"` → `true`). When `false`, emit the literal `\"asc\"`/`\"desc\"`.\n */\n sortOrderAsBoolean?: boolean;\n /** HTTP method used by {@link DataProvider.update}. Default: `\"patch\"`. */\n updateMethod?: \"patch\" | \"put\";\n /**\n * Build the request path for a resource (and optional id).\n * Default: `id == null ? \"/\" + resource : \"/\" + resource + \"/\" + id`.\n *\n * Since 0.77.0 `id` arrives already passed through {@link encodeIdSegment}:\n * percent-encoded as one path segment, and never `\"\"`, `\".\"` or `\"..\"`. An\n * override therefore interpolates it as-is — encoding it again would turn\n * `%2F` into `%252F`.\n */\n buildPath?: (resource: string, id?: string | number) => string;\n}\n\nconst DOT_SEGMENTS: ReadonlySet<string> = new Set([\"\", \".\", \"..\"]);\n\n/**\n * Encode a record id as exactly one URL path segment.\n *\n * The data provider calls this on every id before building a path, because the\n * id is the one part of the URL that may come from outside the app — a search\n * param, a hash, a decoded route param, a value another user stored. Left raw,\n * `\"../../admin/users\"` walks the authenticated request out of the resource,\n * `\"7?role=admin\"` injects query params and `\"7#x\"` truncates the path, all\n * while the request still carries the bearer token (#438).\n *\n * `encodeURIComponent` neutralises `/`, `?`, `#` and `%`. It leaves `.` alone,\n * so `\"\"`, `\".\"` and `\"..\"` are rejected instead: the URL parser resolves them\n * as dot-segments whatever the encoding, and none of them names a record.\n * `null` and `undefined` — what an untyped caller or a not-yet-loaded route\n * param hands over — are rejected too, since `String()` would turn them into\n * the literal segments `\"null\"` and `\"undefined\"`.\n *\n * @param id - The record id, as handed to the provider.\n * @returns The percent-encoded segment, safe to interpolate between two `/`.\n * @throws {TypeError} When the id is `null`, `undefined`, `\"\"`, `\".\"` or `\"..\"`.\n *\n * @example\n * encodeIdSegment(7); // \"7\"\n * encodeIdSegment(\"a/b?x=1\"); // \"a%2Fb%3Fx%3D1\"\n * encodeIdSegment(\"..\"); // throws TypeError\n */\nexport function encodeIdSegment(id: string | number): string {\n if (id == null) {\n throw new TypeError(`Invalid record id ${String(id)}: a record id is required.`);\n }\n const raw = String(id);\n if (DOT_SEGMENTS.has(raw)) {\n throw new TypeError(\n `Invalid record id ${JSON.stringify(raw)}: it would resolve as a path dot-segment.`,\n );\n }\n return encodeURIComponent(raw);\n}\n\nconst defaultBuildPath = (resource: string, id?: string | number): string =>\n id == null ? `/${resource}` : `/${resource}/${id}`;\n\n/**\n * Create a {@link DataProvider} bound to an {@link ApiClient}.\n *\n * Maps Refine-style calls to the Tempest FastAPI SDK conventions: list →\n * `GET /{resource}?page=&size=&order_by=&ascending=&...filters`, one →\n * `GET /{resource}/{id}`, create → `POST`, update → `PATCH`/`PUT`,\n * delete → `DELETE`.\n *\n * Every id goes through {@link encodeIdSegment} before reaching `buildPath`, so\n * an id can never leave its own path segment — not with the default builder,\n * and not with an override that interpolates it.\n *\n * @param client - The HTTP client created by `createApiClient`.\n * @param options - Optional overrides for param names, sort encoding,\n * update method and path building.\n * @returns A stateless data provider.\n *\n * @example\n * const dataProvider = createDataProvider(apiClient);\n * const page = await dataProvider.getList<User>(\"users\", {\n * pagination: { page: 1, pageSize: 20 },\n * sort: { field: \"created_at\", order: \"desc\" },\n * filters: { active: true },\n * });\n */\nexport function createDataProvider(\n client: ApiClient,\n options: DataProviderOptions = {},\n): DataProvider {\n const {\n pageParam = \"page\",\n sizeParam = \"size\",\n sortFieldParam = \"order_by\",\n sortOrderParam = \"ascending\",\n sortOrderAsBoolean = true,\n updateMethod = \"patch\",\n buildPath = defaultBuildPath,\n } = options;\n\n const provider: DataProvider = {\n getList<T>(resource: string, params: GetListParams = {}): Promise<OffsetPage<T>> {\n const { pagination, sort, filters } = params;\n const query: Record<string, ParamValue> = {};\n\n if (pagination?.page != null) query[pageParam] = pagination.page;\n if (pagination?.pageSize != null) query[sizeParam] = pagination.pageSize;\n\n if (sort?.field) {\n const order = sort.order ?? \"asc\";\n query[sortFieldParam] = sort.field;\n query[sortOrderParam] = sortOrderAsBoolean ? order === \"asc\" : order;\n }\n\n if (filters) {\n for (const [key, value] of Object.entries(filters)) {\n query[key] = value;\n }\n }\n\n const requestOptions: RequestOptions = { params: query };\n return client.get<OffsetPage<T>>(buildPath(resource), requestOptions);\n },\n\n async getOne<T>(resource: string, id: string | number): Promise<T> {\n return client.get<T>(buildPath(resource, encodeIdSegment(id)));\n },\n\n getMany<T>(resource: string, ids: (string | number)[]): Promise<T[]> {\n return Promise.all(ids.map((id) => provider.getOne<T>(resource, id)));\n },\n\n create<T>(resource: string, data: unknown): Promise<T> {\n return client.post<T>(buildPath(resource), { body: data });\n },\n\n async update<T>(resource: string, id: string | number, data: unknown): Promise<T> {\n const path = buildPath(resource, encodeIdSegment(id));\n return updateMethod === \"put\"\n ? client.put<T>(path, { body: data })\n : client.patch<T>(path, { body: data });\n },\n\n async deleteOne<T>(resource: string, id: string | number): Promise<T> {\n return client.delete<T>(buildPath(resource, encodeIdSegment(id)));\n },\n };\n\n return provider;\n}\n"],"mappings":"AAwGA,IAAMA,EAAoC,IAAI,IAAI,CAAC,GAAI,IAAK,IAAI,CAAC,EA4BjE,SAAgBC,EAAgB,EAA6B,CACzD,GAAIC,GAAM,KACN,MAAU,UAAU,qBAAqB,OAAOA,CAAE,EAAE,2BAA2B,EAEnF,IAAMC,EAAM,OAAOD,CAAE,EACrB,GAAIF,EAAa,IAAIG,CAAG,EACpB,MAAU,UACN,qBAAqB,KAAK,UAAUA,CAAG,EAAE,0CAC7C,EAEJ,OAAO,mBAAmBA,CAAG,CACjC,CAEA,IAAMC,GAAoB,EAAkB,IACxCF,GAAM,KAAO,IAAIG,IAAa,IAAIA,EAAS,GAAGH,IA2BlD,SAAgBI,EACZ,EACA,EAA+B,CAAC,EACpB,CACZ,GAAM,CACF,YAAY,OACZ,YAAY,OACZ,iBAAiB,WACjB,iBAAiB,YACjB,qBAAqB,GACrB,eAAe,QACf,YAAYF,GACZG,EAEEC,EAAyB,CAC3B,QAAW,EAAkB,EAAwB,CAAC,EAA2B,CAC7E,GAAM,CAAE,aAAY,OAAM,WAAYC,EAChCC,EAAoC,CAAC,EAK3C,GAHIC,GAAY,MAAQ,OAAM,EAAMC,GAAaD,EAAW,MACxDA,GAAY,UAAY,OAAM,EAAME,GAAaF,EAAW,UAE5DG,GAAM,MAAO,CACb,IAAMC,EAAQD,EAAK,OAAS,MAC5B,EAAME,GAAkBF,EAAK,MAC7B,EAAMG,GAAkBC,EAAqBH,IAAU,MAAQA,CACnE,CAEA,GAAII,EACA,IAAK,GAAM,CAACC,EAAKC,KAAU,OAAO,QAAQF,CAAO,EAC7C,EAAMC,GAAOC,EAIrB,IAAMC,EAAiC,CAAE,OAAQZ,CAAM,EACvD,OAAOa,EAAO,IAAmBC,EAAUnB,CAAQ,EAAGiB,CAAc,CACxE,EAEA,MAAM,OAAU,EAAkB,EAAiC,CAC/D,OAAOC,EAAO,IAAOC,EAAUnB,EAAUJ,EAAgBC,CAAE,CAAC,CAAC,CACjE,EAEA,QAAW,EAAkB,EAAwC,CACjE,OAAO,QAAQ,IAAIuB,EAAI,IAAK,GAAOjB,EAAS,OAAUH,EAAUH,CAAE,CAAC,CAAC,CACxE,EAEA,OAAU,EAAkB,EAA2B,CACnD,OAAOqB,EAAO,KAAQC,EAAUnB,CAAQ,EAAG,CAAE,KAAMqB,CAAK,CAAC,CAC7D,EAEA,MAAM,OAAU,EAAkB,EAAqB,EAA2B,CAC9E,IAAMC,EAAOH,EAAUnB,EAAUJ,EAAgBC,CAAE,CAAC,EACpD,OAAO0B,IAAiB,MAClBL,EAAO,IAAOI,EAAM,CAAE,KAAMD,CAAK,CAAC,EAClCH,EAAO,MAASI,EAAM,CAAE,KAAMD,CAAK,CAAC,CAC9C,EAEA,MAAM,UAAa,EAAkB,EAAiC,CAClE,OAAOH,EAAO,OAAUC,EAAUnB,EAAUJ,EAAgBC,CAAE,CAAC,CAAC,CACpE,CACJ,EAEA,OAAOM,CACX,C"}
|
|
1
|
+
{"version":3,"file":"create-data-provider.cjs","names":["DOT_SEGMENTS","encodeIdSegment","id","raw","defaultBuildPath","resource","createDataProvider","options","provider","params","query","filters","pagination","pageParam","sizeParam","sort","order","sortFieldParam","sortOrderParam","sortOrderAsBoolean","requestOptions","client","buildPath","ids","data","path","updateMethod"],"sources":["../../src/data/create-data-provider.ts"],"sourcesContent":["import type { ApiClient, RequestOptions } from \"@/http/types\";\nimport type { OffsetPage } from \"@/query/pagination\";\n\n/** Query-param values accepted by the API client for a list request. */\ntype ParamValue = string | number | boolean | undefined | null;\n\n/** Filters passed to {@link DataProvider.getList}, spread verbatim into the query string. */\nexport type DataFilters = Record<string, ParamValue>;\n\n/** Parameters for a paginated, sorted, filtered list request. */\nexport interface GetListParams {\n /** Offset pagination — 1-based `page` and `pageSize`. */\n pagination?: { page?: number; pageSize?: number };\n /** Single-field sort. `order` defaults to `\"asc\"`. */\n sort?: { field: string; order?: \"asc\" | \"desc\" };\n /**\n * Arbitrary filter query params, spread onto the request **before**\n * `pagination` and `sort`. A filter whose key equals a pagination or sort\n * param name (`page`, `size`, `order_by`, `ascending` by default) is\n * overridden by the structured value when that one is set: until 0.81.0 the\n * filter won, so `filters: { page: 1 }` from a table's filter state silently\n * pinned every request to page 1.\n */\n filters?: DataFilters;\n}\n\n/**\n * Refine-style data provider over the Tempest FastAPI SDK CRUD/pagination\n * conventions. Implementations are stateless wrappers around an {@link ApiClient}.\n */\nexport interface DataProvider {\n /**\n * Fetch a paginated list of a resource.\n *\n * @param resource - The resource name (e.g. `\"users\"`).\n * @param params - Pagination, sort and filter parameters.\n * @returns The offset-paginated envelope.\n */\n getList<T>(resource: string, params?: GetListParams): Promise<OffsetPage<T>>;\n /**\n * Fetch a single record by id.\n *\n * @param resource - The resource name.\n * @param id - The record id.\n * @returns The record.\n */\n getOne<T>(resource: string, id: string | number): Promise<T>;\n /**\n * Fetch many records by id (default: parallel {@link DataProvider.getOne}).\n *\n * @param resource - The resource name.\n * @param ids - The record ids.\n * @returns The records, in the same order as `ids`.\n */\n getMany<T>(resource: string, ids: (string | number)[]): Promise<T[]>;\n /**\n * Create a record.\n *\n * @param resource - The resource name.\n * @param data - The creation payload.\n * @returns The created record.\n */\n create<T>(resource: string, data: unknown): Promise<T>;\n /**\n * Update a record (PATCH by default, PUT when configured).\n *\n * @param resource - The resource name.\n * @param id - The record id.\n * @param data - The update payload.\n * @returns The updated record.\n */\n update<T>(resource: string, id: string | number, data: unknown): Promise<T>;\n /**\n * Delete a record by id.\n *\n * @param resource - The resource name.\n * @param id - The record id.\n * @returns The delete response (often the deleted record).\n */\n deleteOne<T>(resource: string, id: string | number): Promise<T>;\n}\n\n/** Options to tailor {@link createDataProvider} to a backend's conventions. */\nexport interface DataProviderOptions {\n /** Query-param name for the page number. Default: `\"page\"`. */\n pageParam?: string;\n /** Query-param name for the page size. Default: `\"size\"`. */\n sizeParam?: string;\n /** Query-param name for the sort field. Default: `\"order_by\"`. */\n sortFieldParam?: string;\n /** Query-param name for the sort order. Default: `\"ascending\"`. */\n sortOrderParam?: string;\n /**\n * When `true` (default), emit the sort order as a boolean\n * (`order:\"asc\"` → `true`). When `false`, emit the literal `\"asc\"`/`\"desc\"`.\n */\n sortOrderAsBoolean?: boolean;\n /** HTTP method used by {@link DataProvider.update}. Default: `\"patch\"`. */\n updateMethod?: \"patch\" | \"put\";\n /**\n * Build the request path for a resource (and optional id).\n * Default: `id == null ? \"/\" + resource : \"/\" + resource + \"/\" + id`.\n *\n * Since 0.77.0 `id` arrives already passed through {@link encodeIdSegment}:\n * percent-encoded as one path segment, and never `\"\"`, `\".\"` or `\"..\"`. An\n * override therefore interpolates it as-is — encoding it again would turn\n * `%2F` into `%252F`.\n */\n buildPath?: (resource: string, id?: string | number) => string;\n}\n\nconst DOT_SEGMENTS: ReadonlySet<string> = new Set([\"\", \".\", \"..\"]);\n\n/**\n * Encode a record id as exactly one URL path segment.\n *\n * The data provider calls this on every id before building a path, because the\n * id is the one part of the URL that may come from outside the app — a search\n * param, a hash, a decoded route param, a value another user stored. Left raw,\n * `\"../../admin/users\"` walks the authenticated request out of the resource,\n * `\"7?role=admin\"` injects query params and `\"7#x\"` truncates the path, all\n * while the request still carries the bearer token (#438).\n *\n * `encodeURIComponent` neutralises `/`, `?`, `#` and `%`. It leaves `.` alone,\n * so `\"\"`, `\".\"` and `\"..\"` are rejected instead: the URL parser resolves them\n * as dot-segments whatever the encoding, and none of them names a record.\n * `null` and `undefined` — what an untyped caller or a not-yet-loaded route\n * param hands over — are rejected too, since `String()` would turn them into\n * the literal segments `\"null\"` and `\"undefined\"`.\n *\n * @param id - The record id, as handed to the provider.\n * @returns The percent-encoded segment, safe to interpolate between two `/`.\n * @throws {TypeError} When the id is `null`, `undefined`, `\"\"`, `\".\"` or `\"..\"`.\n *\n * @example\n * encodeIdSegment(7); // \"7\"\n * encodeIdSegment(\"a/b?x=1\"); // \"a%2Fb%3Fx%3D1\"\n * encodeIdSegment(\"..\"); // throws TypeError\n */\nexport function encodeIdSegment(id: string | number): string {\n if (id == null) {\n throw new TypeError(`Invalid record id ${String(id)}: a record id is required.`);\n }\n const raw = String(id);\n if (DOT_SEGMENTS.has(raw)) {\n throw new TypeError(\n `Invalid record id ${JSON.stringify(raw)}: it would resolve as a path dot-segment.`,\n );\n }\n return encodeURIComponent(raw);\n}\n\nconst defaultBuildPath = (resource: string, id?: string | number): string =>\n id == null ? `/${resource}` : `/${resource}/${id}`;\n\n/**\n * Create a {@link DataProvider} bound to an {@link ApiClient}.\n *\n * Maps Refine-style calls to the Tempest FastAPI SDK conventions: list →\n * `GET /{resource}?page=&size=&order_by=&ascending=&...filters`, one →\n * `GET /{resource}/{id}`, create → `POST`, update → `PATCH`/`PUT`,\n * delete → `DELETE`.\n *\n * Every id goes through {@link encodeIdSegment} before reaching `buildPath`, so\n * an id can never leave its own path segment — not with the default builder,\n * and not with an override that interpolates it.\n *\n * @param client - The HTTP client created by `createApiClient`.\n * @param options - Optional overrides for param names, sort encoding,\n * update method and path building.\n * @returns A stateless data provider.\n *\n * @example\n * const dataProvider = createDataProvider(apiClient);\n * const page = await dataProvider.getList<User>(\"users\", {\n * pagination: { page: 1, pageSize: 20 },\n * sort: { field: \"created_at\", order: \"desc\" },\n * filters: { active: true },\n * });\n */\nexport function createDataProvider(\n client: ApiClient,\n options: DataProviderOptions = {},\n): DataProvider {\n const {\n pageParam = \"page\",\n sizeParam = \"size\",\n sortFieldParam = \"order_by\",\n sortOrderParam = \"ascending\",\n sortOrderAsBoolean = true,\n updateMethod = \"patch\",\n buildPath = defaultBuildPath,\n } = options;\n\n const provider: DataProvider = {\n getList<T>(resource: string, params: GetListParams = {}): Promise<OffsetPage<T>> {\n const { pagination, sort, filters } = params;\n const query: Record<string, ParamValue> = { ...filters };\n\n if (pagination?.page != null) query[pageParam] = pagination.page;\n if (pagination?.pageSize != null) query[sizeParam] = pagination.pageSize;\n\n if (sort?.field) {\n const order = sort.order ?? \"asc\";\n query[sortFieldParam] = sort.field;\n query[sortOrderParam] = sortOrderAsBoolean ? order === \"asc\" : order;\n }\n\n const requestOptions: RequestOptions = { params: query };\n return client.get<OffsetPage<T>>(buildPath(resource), requestOptions);\n },\n\n async getOne<T>(resource: string, id: string | number): Promise<T> {\n return client.get<T>(buildPath(resource, encodeIdSegment(id)));\n },\n\n getMany<T>(resource: string, ids: (string | number)[]): Promise<T[]> {\n return Promise.all(ids.map((id) => provider.getOne<T>(resource, id)));\n },\n\n create<T>(resource: string, data: unknown): Promise<T> {\n return client.post<T>(buildPath(resource), { body: data });\n },\n\n async update<T>(resource: string, id: string | number, data: unknown): Promise<T> {\n const path = buildPath(resource, encodeIdSegment(id));\n return updateMethod === \"put\"\n ? client.put<T>(path, { body: data })\n : client.patch<T>(path, { body: data });\n },\n\n async deleteOne<T>(resource: string, id: string | number): Promise<T> {\n return client.delete<T>(buildPath(resource, encodeIdSegment(id)));\n },\n };\n\n return provider;\n}\n"],"mappings":"AA+GA,IAAMA,EAAoC,IAAI,IAAI,CAAC,GAAI,IAAK,IAAI,CAAC,EA4BjE,SAAgBC,EAAgB,EAA6B,CACzD,GAAIC,GAAM,KACN,MAAU,UAAU,qBAAqB,OAAOA,CAAE,EAAE,2BAA2B,EAEnF,IAAMC,EAAM,OAAOD,CAAE,EACrB,GAAIF,EAAa,IAAIG,CAAG,EACpB,MAAU,UACN,qBAAqB,KAAK,UAAUA,CAAG,EAAE,0CAC7C,EAEJ,OAAO,mBAAmBA,CAAG,CACjC,CAEA,IAAMC,GAAoB,EAAkB,IACxCF,GAAM,KAAO,IAAIG,IAAa,IAAIA,EAAS,GAAGH,IA2BlD,SAAgBI,EACZ,EACA,EAA+B,CAAC,EACpB,CACZ,GAAM,CACF,YAAY,OACZ,YAAY,OACZ,iBAAiB,WACjB,iBAAiB,YACjB,qBAAqB,GACrB,eAAe,QACf,YAAYF,GACZG,EAEEC,EAAyB,CAC3B,QAAW,EAAkB,EAAwB,CAAC,EAA2B,CAC7E,GAAM,CAAE,aAAY,OAAM,WAAYC,EAChCC,EAAoC,CAAE,GAAGC,CAAQ,EAKvD,GAHIC,GAAY,MAAQ,OAAM,EAAMC,GAAaD,EAAW,MACxDA,GAAY,UAAY,OAAM,EAAME,GAAaF,EAAW,UAE5DG,GAAM,MAAO,CACb,IAAMC,EAAQD,EAAK,OAAS,MAC5B,EAAME,GAAkBF,EAAK,MAC7B,EAAMG,GAAkBC,EAAqBH,IAAU,MAAQA,CACnE,CAEA,IAAMI,EAAiC,CAAE,OAAQV,CAAM,EACvD,OAAOW,EAAO,IAAmBC,EAAUjB,CAAQ,EAAGe,CAAc,CACxE,EAEA,MAAM,OAAU,EAAkB,EAAiC,CAC/D,OAAOC,EAAO,IAAOC,EAAUjB,EAAUJ,EAAgBC,CAAE,CAAC,CAAC,CACjE,EAEA,QAAW,EAAkB,EAAwC,CACjE,OAAO,QAAQ,IAAIqB,EAAI,IAAK,GAAOf,EAAS,OAAUH,EAAUH,CAAE,CAAC,CAAC,CACxE,EAEA,OAAU,EAAkB,EAA2B,CACnD,OAAOmB,EAAO,KAAQC,EAAUjB,CAAQ,EAAG,CAAE,KAAMmB,CAAK,CAAC,CAC7D,EAEA,MAAM,OAAU,EAAkB,EAAqB,EAA2B,CAC9E,IAAMC,EAAOH,EAAUjB,EAAUJ,EAAgBC,CAAE,CAAC,EACpD,OAAOwB,IAAiB,MAClBL,EAAO,IAAOI,EAAM,CAAE,KAAMD,CAAK,CAAC,EAClCH,EAAO,MAASI,EAAM,CAAE,KAAMD,CAAK,CAAC,CAC9C,EAEA,MAAM,UAAa,EAAkB,EAAiC,CAClE,OAAOH,EAAO,OAAUC,EAAUjB,EAAUJ,EAAgBC,CAAE,CAAC,CAAC,CACpE,CACJ,EAEA,OAAOM,CACX,C"}
|
|
@@ -14,12 +14,11 @@ var n = (e, t) => t == null ? `/${e}` : `/${e}/${t}`;
|
|
|
14
14
|
function r(e, r = {}) {
|
|
15
15
|
let { pageParam: i = "page", sizeParam: a = "size", sortFieldParam: o = "order_by", sortOrderParam: s = "ascending", sortOrderAsBoolean: c = !0, updateMethod: l = "patch", buildPath: u = n } = r, d = {
|
|
16
16
|
getList(t, n = {}) {
|
|
17
|
-
let { pagination: r, sort: l, filters: d } = n, f = {};
|
|
17
|
+
let { pagination: r, sort: l, filters: d } = n, f = { ...d };
|
|
18
18
|
if (r?.page != null && (f[i] = r.page), r?.pageSize != null && (f[a] = r.pageSize), l?.field) {
|
|
19
19
|
let e = l.order ?? "asc";
|
|
20
20
|
f[o] = l.field, f[s] = c ? e === "asc" : e;
|
|
21
21
|
}
|
|
22
|
-
if (d) for (let [e, t] of Object.entries(d)) f[e] = t;
|
|
23
22
|
let p = { params: f };
|
|
24
23
|
return e.get(u(t), p);
|
|
25
24
|
},
|