@devalok/shilp-sutra 0.52.0 → 0.54.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/AGENTS.md +7 -1
- package/BREAKING.json +29 -0
- package/MIGRATION.md +61 -0
- package/dist/_chunks/motion-provider.js +7 -6
- package/dist/_chunks/motion-provider.js.map +1 -1
- package/dist/_chunks/success.js +53 -53
- package/dist/ai/command-bar.js +158 -158
- package/dist/ai/command-bar.js.map +1 -1
- package/dist/ai/conversation.js +5 -5
- package/dist/composed/avatar-group.d.ts +11 -9
- package/dist/composed/avatar-group.d.ts.map +1 -1
- package/dist/composed/avatar-group.js +108 -140
- package/dist/composed/avatar-group.js.map +1 -1
- package/dist/composed/bulk-action-bar.d.ts +19 -11
- package/dist/composed/bulk-action-bar.d.ts.map +1 -1
- package/dist/composed/bulk-action-bar.js +81 -110
- package/dist/composed/bulk-action-bar.js.map +1 -1
- package/dist/composed/command-palette.js +17 -17
- package/dist/composed/command-palette.js.map +1 -1
- package/dist/composed/error-boundary.d.ts +15 -3
- package/dist/composed/error-boundary.d.ts.map +1 -1
- package/dist/composed/error-boundary.js +43 -26
- package/dist/composed/error-boundary.js.map +1 -1
- package/dist/composed/loading-skeleton.d.ts +2 -0
- package/dist/composed/loading-skeleton.d.ts.map +1 -1
- package/dist/composed/loading-skeleton.js +79 -56
- package/dist/composed/loading-skeleton.js.map +1 -1
- package/dist/composed/master-detail.d.ts +21 -8
- package/dist/composed/master-detail.d.ts.map +1 -1
- package/dist/composed/master-detail.js +91 -77
- package/dist/composed/master-detail.js.map +1 -1
- package/dist/composed/page-skeletons.d.ts +6 -0
- package/dist/composed/page-skeletons.d.ts.map +1 -1
- package/dist/composed/page-skeletons.js +93 -71
- package/dist/composed/page-skeletons.js.map +1 -1
- package/dist/composed/priority-indicator.d.ts +15 -6
- package/dist/composed/priority-indicator.d.ts.map +1 -1
- package/dist/composed/priority-indicator.js +37 -88
- package/dist/composed/priority-indicator.js.map +1 -1
- package/dist/composed/schedule-view.d.ts +17 -2
- package/dist/composed/schedule-view.d.ts.map +1 -1
- package/dist/composed/schedule-view.js +163 -64
- package/dist/composed/schedule-view.js.map +1 -1
- package/dist/motion/motion-provider.d.ts.map +1 -1
- package/dist/shell/bottom-navbar.d.ts +32 -6
- package/dist/shell/bottom-navbar.d.ts.map +1 -1
- package/dist/shell/bottom-navbar.js +156 -129
- package/dist/shell/bottom-navbar.js.map +1 -1
- package/dist/shell/index.d.ts +5 -3
- package/dist/shell/index.d.ts.map +1 -1
- package/dist/shell/index.js +2 -3
- package/dist/shell/notification-preferences.d.ts.map +1 -1
- package/dist/shell/notification-preferences.js +2 -0
- package/dist/shell/notification-preferences.js.map +1 -1
- package/dist/tokens/primitives.css +6 -2
- package/dist/tokens/utilities.css +6 -0
- package/dist/ui/autocomplete.d.ts +34 -32
- package/dist/ui/autocomplete.d.ts.map +1 -1
- package/dist/ui/autocomplete.js +115 -110
- package/dist/ui/autocomplete.js.map +1 -1
- package/dist/ui/combobox.d.ts +1 -1
- package/dist/ui/combobox.d.ts.map +1 -1
- package/dist/ui/combobox.js +133 -128
- package/dist/ui/combobox.js.map +1 -1
- package/dist/ui/file-upload.d.ts.map +1 -1
- package/dist/ui/file-upload.js +124 -138
- package/dist/ui/file-upload.js.map +1 -1
- package/dist/ui/form.d.ts +7 -0
- package/dist/ui/form.d.ts.map +1 -1
- package/dist/ui/form.js +4 -2
- package/dist/ui/form.js.map +1 -1
- package/dist/ui/icon-button.d.ts +2 -1
- package/dist/ui/icon-button.d.ts.map +1 -1
- package/dist/ui/icon-button.js +1 -1
- package/dist/ui/icon-button.js.map +1 -1
- package/dist/ui/index.js +30 -30
- package/dist/ui/label.d.ts.map +1 -1
- package/dist/ui/label.js +7 -6
- package/dist/ui/label.js.map +1 -1
- package/dist/ui/search-input.js +4 -4
- package/dist/ui/sidebar.js +7 -7
- package/dist/ui/spinner.d.ts.map +1 -1
- package/dist/ui/spinner.js +50 -44
- package/dist/ui/spinner.js.map +1 -1
- package/dist/ui/switch.d.ts.map +1 -1
- package/dist/ui/switch.js +24 -24
- package/dist/ui/switch.js.map +1 -1
- package/dist/ui/table.js +1 -1
- package/dist/ui/table.js.map +1 -1
- package/docs/components/composed/bulk-action-bar.md +9 -3
- package/docs/components/composed/error-boundary.md +18 -2
- package/docs/components/composed/master-detail.md +12 -2
- package/docs/components/composed/page-skeletons.md +2 -2
- package/docs/components/composed/priority-indicator.md +22 -11
- package/docs/components/composed/rich-chat-input.md +18 -6
- package/docs/components/composed/schedule-view.md +20 -5
- package/docs/components/composed/simple-tooltip.md +1 -1
- package/docs/components/shell/app-command-palette.md +2 -1
- package/docs/components/shell/bottom-navbar.md +28 -9
- package/docs/components/shell/command-registry.md +2 -2
- package/docs/components/shell/link-context.md +2 -2
- package/docs/components/shell/top-bar.md +1 -1
- package/docs/components/ui/autocomplete.md +26 -10
- package/docs/components/ui/search-input.md +2 -2
- package/docs/components/ui/slider.md +2 -2
- package/docs/components/ui/split-button.md +3 -3
- package/llms.txt +2 -2
- package/make-kit/Guidelines.md +1 -1
- package/make-kit/components/overview.md +2 -2
- package/make-kit/foundations/surfaces.md +1 -1
- package/mcp-manifest.json +581 -172
- package/package.json +1 -6
- package/skill/SKILL.md +1 -1
- package/skill/chat-skill/README.md +18 -0
- package/skill/chat-skill/SKILL.md +78 -0
- package/skill/chat-skill/chatgpt-instructions.md +26 -0
- package/skill/chat-skill/gemini-instructions.md +25 -0
- package/skill/references/components.md +2 -2
- package/dist/shell/sidebar.d.ts +0 -113
- package/dist/shell/sidebar.d.ts.map +0 -1
- package/dist/shell/sidebar.js +0 -293
- package/dist/shell/sidebar.js.map +0 -1
- package/docs/components/shell/sidebar.md +0 -103
|
@@ -4,158 +4,185 @@ import { springs as t } from "../ui/lib/motion.js";
|
|
|
4
4
|
import { cn as n } from "../ui/lib/utils.js";
|
|
5
5
|
import { Icon as r } from "../ui/icon.js";
|
|
6
6
|
import { normalizeIcon as i } from "../_chunks/normalize-icon.js";
|
|
7
|
-
import {
|
|
7
|
+
import { BadgeCompound as a } from "../_chunks/badge-group.js";
|
|
8
|
+
import { Sheet as o, SheetContent as s, SheetHeader as c, SheetTitle as l, SheetTrigger as u } from "../ui/sheet.js";
|
|
9
|
+
import { useLink as d } from "../_chunks/link-context.js";
|
|
8
10
|
import "./link-context.js";
|
|
9
|
-
import * as
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { IconDots as
|
|
13
|
-
import {
|
|
11
|
+
import * as f from "react";
|
|
12
|
+
import { useState as p } from "react";
|
|
13
|
+
import { jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
14
|
+
import { IconDots as g } from "@tabler/icons-react";
|
|
15
|
+
import { motion as _, useReducedMotion as v } from "framer-motion";
|
|
14
16
|
//#region src/shell/bottom-navbar.tsx
|
|
15
|
-
function y(
|
|
17
|
+
function y(e, t) {
|
|
18
|
+
return e.canView ? e.canView(t) : e.roles && e.roles.length > 0 ? !!t?.role && e.roles.includes(t.role) : !0;
|
|
19
|
+
}
|
|
20
|
+
function b({ count: e }) {
|
|
16
21
|
if (!e || e <= 0) return null;
|
|
17
|
-
let t = e > 99 ? "99+" : String(e)
|
|
18
|
-
return /* @__PURE__ */
|
|
22
|
+
let t = e > 99 ? "99+" : String(e);
|
|
23
|
+
return /* @__PURE__ */ m(a, {
|
|
24
|
+
color: "error",
|
|
25
|
+
variant: "solid",
|
|
26
|
+
size: "xs",
|
|
19
27
|
"aria-label": `${e} notifications`,
|
|
20
|
-
className:
|
|
28
|
+
className: "pointer-events-none absolute -end-1 -top-1 justify-center px-ds-01 tabular-nums motion-safe:animate-in motion-safe:zoom-in-75",
|
|
21
29
|
children: t
|
|
22
30
|
});
|
|
23
31
|
}
|
|
24
|
-
function
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
function x({ indicator: e, layoutId: n, reduced: r }) {
|
|
33
|
+
if (e === "none") return null;
|
|
34
|
+
let i = {
|
|
35
|
+
layoutId: n,
|
|
36
|
+
"aria-hidden": !0,
|
|
37
|
+
transition: r ? { duration: 0 } : t.snappy,
|
|
38
|
+
initial: !r && { opacity: 0 },
|
|
39
|
+
animate: { opacity: 1 }
|
|
40
|
+
};
|
|
41
|
+
return e === "underline" ? /* @__PURE__ */ m(_.span, {
|
|
42
|
+
...i,
|
|
43
|
+
className: "absolute top-0 h-[3px] w-full rounded-b-control-inner bg-accent-9"
|
|
44
|
+
}) : e === "tint" ? /* @__PURE__ */ m(_.span, {
|
|
45
|
+
...i,
|
|
46
|
+
className: "absolute inset-0 rounded-control bg-accent-3"
|
|
47
|
+
}) : /* @__PURE__ */ m(_.span, {
|
|
48
|
+
...i,
|
|
49
|
+
className: "absolute inset-0 rounded-pill bg-accent-3"
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
var S = "relative flex h-16 w-full min-w-0 cursor-pointer flex-col items-center justify-center gap-ds-02 px-ds-01 pt-0 text-body-sm transition-colors duration-fast-02 ease-productive-standard";
|
|
53
|
+
function C({ item: r, isActive: a, indicator: o, indicatorId: s, showLabel: c, reduced: l }) {
|
|
54
|
+
let u = d();
|
|
55
|
+
return /* @__PURE__ */ m(_.div, {
|
|
27
56
|
whileTap: l ? void 0 : { scale: .96 },
|
|
28
57
|
transition: l ? { duration: 0 } : t.snappy,
|
|
29
|
-
className: "flex
|
|
30
|
-
children: /* @__PURE__ */
|
|
58
|
+
className: "flex min-w-0 flex-1 basis-0",
|
|
59
|
+
children: /* @__PURE__ */ h(u, {
|
|
31
60
|
href: r.href,
|
|
32
|
-
onClick: s,
|
|
33
61
|
"aria-label": r.title,
|
|
34
|
-
"aria-current":
|
|
35
|
-
className: n(
|
|
36
|
-
children: /* @__PURE__ */
|
|
37
|
-
|
|
62
|
+
"aria-current": a ? "page" : void 0,
|
|
63
|
+
className: n(S, a ? "font-semibold text-accent-11" : "text-surface-fg-subtle"),
|
|
64
|
+
children: [a && o === "tint" && /* @__PURE__ */ m(x, {
|
|
65
|
+
indicator: "tint",
|
|
66
|
+
layoutId: s,
|
|
67
|
+
reduced: l
|
|
68
|
+
}), /* @__PURE__ */ h("div", {
|
|
69
|
+
className: "relative flex w-full min-w-0 flex-col items-center gap-ds-02",
|
|
38
70
|
children: [
|
|
39
|
-
o && /* @__PURE__ */
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
transition: l ? { duration: 0 } : t.snappy
|
|
71
|
+
a && o === "underline" && /* @__PURE__ */ m(x, {
|
|
72
|
+
indicator: "underline",
|
|
73
|
+
layoutId: s,
|
|
74
|
+
reduced: l
|
|
44
75
|
}),
|
|
45
|
-
/* @__PURE__ */
|
|
46
|
-
className: "relative
|
|
47
|
-
children: [
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
76
|
+
/* @__PURE__ */ h("div", {
|
|
77
|
+
className: "relative px-ds-04 py-ds-01",
|
|
78
|
+
children: [
|
|
79
|
+
a && o === "pill" && /* @__PURE__ */ m(x, {
|
|
80
|
+
indicator: "pill",
|
|
81
|
+
layoutId: s,
|
|
82
|
+
reduced: l
|
|
83
|
+
}),
|
|
84
|
+
/* @__PURE__ */ m("span", {
|
|
85
|
+
className: "relative [&>svg]:h-ico-md [&>svg]:w-ico-md",
|
|
86
|
+
"aria-hidden": "true",
|
|
87
|
+
children: /* @__PURE__ */ m(e, {
|
|
88
|
+
size: "md",
|
|
89
|
+
children: i(a && r.activeIcon ? r.activeIcon : r.icon)
|
|
90
|
+
})
|
|
91
|
+
}),
|
|
92
|
+
r.badge != null && /* @__PURE__ */ m(b, { count: r.badge })
|
|
93
|
+
]
|
|
55
94
|
}),
|
|
56
|
-
/* @__PURE__ */
|
|
57
|
-
className: "text-center",
|
|
95
|
+
c && /* @__PURE__ */ m("span", {
|
|
96
|
+
className: "max-w-full truncate text-center",
|
|
58
97
|
children: r.title
|
|
59
98
|
})
|
|
60
99
|
]
|
|
61
|
-
})
|
|
100
|
+
})]
|
|
62
101
|
})
|
|
63
102
|
});
|
|
64
103
|
}
|
|
65
|
-
var
|
|
66
|
-
let
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
let M = (e, t = !1) => t || e === "/" ? o === e : o.startsWith(e), N = S.some((e) => M(e.href, e.exact));
|
|
71
|
-
return /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(g, { children: O && /* @__PURE__ */ p("div", {
|
|
72
|
-
className: "fixed inset-0 z-overlay md:hidden",
|
|
73
|
-
onClick: () => k(!1),
|
|
74
|
-
children: [/* @__PURE__ */ f("div", { className: "absolute inset-0 bg-overlay" }), /* @__PURE__ */ p(_.div, {
|
|
75
|
-
ref: A,
|
|
76
|
-
role: "dialog",
|
|
77
|
-
"aria-label": "More navigation",
|
|
78
|
-
initial: { y: "100%" },
|
|
79
|
-
animate: { y: 0 },
|
|
80
|
-
exit: { y: "100%" },
|
|
81
|
-
transition: D ? { duration: 0 } : t.smooth,
|
|
82
|
-
className: "absolute bottom-[72px] left-0 right-0 rounded-t-bubble border-t border-surface-border-strong bg-surface-overlay p-ds-05 pb-ds-03",
|
|
83
|
-
onClick: (e) => e.stopPropagation(),
|
|
84
|
-
onKeyDown: (e) => {
|
|
85
|
-
e.stopPropagation(), e.key === "Escape" && j();
|
|
86
|
-
},
|
|
87
|
-
children: [/* @__PURE__ */ p("div", {
|
|
88
|
-
className: "mb-ds-04 flex items-center justify-between",
|
|
89
|
-
children: [/* @__PURE__ */ f("span", {
|
|
90
|
-
className: "text-body-md font-semibold text-surface-fg",
|
|
91
|
-
children: "More"
|
|
92
|
-
}), /* @__PURE__ */ f("button", {
|
|
93
|
-
onClick: () => k(!1),
|
|
94
|
-
"aria-label": "Close more menu",
|
|
95
|
-
className: "flex h-ds-sm w-ds-sm items-center justify-center rounded-pill hover:bg-surface-raised-hover",
|
|
96
|
-
children: /* @__PURE__ */ f(r, {
|
|
97
|
-
icon: h,
|
|
98
|
-
size: "sm",
|
|
99
|
-
className: "text-surface-fg-muted"
|
|
100
|
-
})
|
|
101
|
-
})]
|
|
102
|
-
}), /* @__PURE__ */ f("div", {
|
|
103
|
-
className: "grid grid-cols-4 gap-ds-03",
|
|
104
|
-
children: S.map((t) => /* @__PURE__ */ p(E, {
|
|
105
|
-
href: t.href,
|
|
106
|
-
onClick: () => k(!1),
|
|
107
|
-
className: n("flex flex-col items-center gap-ds-02b rounded-overlay-lg p-ds-04 text-body-sm transition-colors ease-productive-standard", M(t.href, t.exact) ? "bg-surface-raised-hover text-accent-11" : "text-surface-fg-subtle hover:bg-surface-raised-hover"),
|
|
108
|
-
children: [/* @__PURE__ */ f("span", {
|
|
109
|
-
className: "[&>svg]:h-ico-md [&>svg]:w-ico-md",
|
|
110
|
-
children: /* @__PURE__ */ f(e, {
|
|
111
|
-
size: "md",
|
|
112
|
-
children: i(t.icon)
|
|
113
|
-
})
|
|
114
|
-
}), /* @__PURE__ */ f("span", {
|
|
115
|
-
className: "text-center",
|
|
116
|
-
children: t.title
|
|
117
|
-
})]
|
|
118
|
-
}, t.href))
|
|
119
|
-
})]
|
|
120
|
-
})]
|
|
121
|
-
}) }), /* @__PURE__ */ p("nav", {
|
|
122
|
-
...w,
|
|
123
|
-
ref: T,
|
|
104
|
+
var w = f.forwardRef(({ currentPath: a = "/", user: b = null, primaryItems: w = [], moreItems: T = [], indicator: E = "pill", labelVisibility: D = "always", className: O, ...k }, A) => {
|
|
105
|
+
let j = d(), M = v() ?? !1, [N, P] = p(!1), F = `${f.useId()}-nav-indicator`, I = (e, t = !1) => t || e === "/" ? a === e : a.startsWith(e), L = w.filter((e) => y(e, b)), R = T.filter((e) => y(e, b)), z = R.some((e) => I(e.href, e.exact)), B = N || z, V = D === "always" || B;
|
|
106
|
+
return /* @__PURE__ */ h("nav", {
|
|
107
|
+
...k,
|
|
108
|
+
ref: A,
|
|
124
109
|
"aria-label": "Mobile navigation",
|
|
125
|
-
className: n("fixed bottom-0
|
|
126
|
-
children: [
|
|
110
|
+
className: n("fixed bottom-0 start-0 end-0 z-sticky flex w-full flex-row items-stretch justify-between border-t border-surface-border-strong bg-surface-chrome px-ds-05 pb-safe pt-0 md:hidden", O),
|
|
111
|
+
children: [L.map((e) => /* @__PURE__ */ m(C, {
|
|
127
112
|
item: e,
|
|
128
|
-
isActive:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
113
|
+
isActive: I(e.href, e.exact),
|
|
114
|
+
indicator: E,
|
|
115
|
+
indicatorId: F,
|
|
116
|
+
showLabel: D === "always" || I(e.href, e.exact),
|
|
117
|
+
reduced: M
|
|
118
|
+
}, e.href)), R.length > 0 && /* @__PURE__ */ h(o, {
|
|
119
|
+
open: N,
|
|
120
|
+
onOpenChange: P,
|
|
121
|
+
children: [/* @__PURE__ */ m(u, {
|
|
122
|
+
asChild: !0,
|
|
123
|
+
children: /* @__PURE__ */ h(_.button, {
|
|
124
|
+
type: "button",
|
|
125
|
+
"aria-label": "More navigation options",
|
|
126
|
+
whileTap: M ? void 0 : { scale: .96 },
|
|
127
|
+
transition: M ? { duration: 0 } : t.snappy,
|
|
128
|
+
className: n(S, "min-w-0 flex-1 basis-0", B ? "font-semibold text-accent-11" : "text-surface-fg-subtle"),
|
|
129
|
+
children: [B && E === "tint" && /* @__PURE__ */ m(x, {
|
|
130
|
+
indicator: "tint",
|
|
131
|
+
layoutId: F,
|
|
132
|
+
reduced: M
|
|
133
|
+
}), /* @__PURE__ */ h("div", {
|
|
134
|
+
className: "relative flex w-full min-w-0 flex-col items-center gap-ds-02",
|
|
135
|
+
children: [
|
|
136
|
+
B && E === "underline" && /* @__PURE__ */ m(x, {
|
|
137
|
+
indicator: "underline",
|
|
138
|
+
layoutId: F,
|
|
139
|
+
reduced: M
|
|
140
|
+
}),
|
|
141
|
+
/* @__PURE__ */ h("div", {
|
|
142
|
+
className: "relative px-ds-04 py-ds-01",
|
|
143
|
+
children: [B && E === "pill" && /* @__PURE__ */ m(x, {
|
|
144
|
+
indicator: "pill",
|
|
145
|
+
layoutId: F,
|
|
146
|
+
reduced: M
|
|
147
|
+
}), /* @__PURE__ */ m(r, { icon: g })]
|
|
148
|
+
}),
|
|
149
|
+
V && /* @__PURE__ */ m("span", {
|
|
150
|
+
className: "max-w-full truncate text-center",
|
|
151
|
+
children: "More"
|
|
152
|
+
})
|
|
153
|
+
]
|
|
154
|
+
})]
|
|
155
|
+
})
|
|
156
|
+
}), /* @__PURE__ */ h(s, {
|
|
157
|
+
side: "bottom",
|
|
158
|
+
className: "rounded-t-bubble",
|
|
159
|
+
children: [/* @__PURE__ */ m(c, { children: /* @__PURE__ */ m(l, { children: "More" }) }), /* @__PURE__ */ m("div", {
|
|
160
|
+
className: "grid grid-cols-[repeat(auto-fit,minmax(72px,1fr))] gap-ds-03 pt-ds-04",
|
|
161
|
+
children: R.map((t) => {
|
|
162
|
+
let r = I(t.href, t.exact);
|
|
163
|
+
return /* @__PURE__ */ h(j, {
|
|
164
|
+
href: t.href,
|
|
165
|
+
onClick: () => P(!1),
|
|
166
|
+
"aria-current": r ? "page" : void 0,
|
|
167
|
+
className: n("flex min-w-0 flex-col items-center gap-ds-02b rounded-overlay-lg p-ds-04 text-body-sm transition-colors ease-productive-standard", r ? "bg-surface-raised-hover text-accent-11" : "text-surface-fg-subtle hover:bg-surface-raised-hover"),
|
|
168
|
+
children: [/* @__PURE__ */ m("span", {
|
|
169
|
+
className: "[&>svg]:h-ico-md [&>svg]:w-ico-md",
|
|
170
|
+
"aria-hidden": "true",
|
|
171
|
+
children: /* @__PURE__ */ m(e, {
|
|
172
|
+
size: "md",
|
|
173
|
+
children: i(t.icon)
|
|
174
|
+
})
|
|
175
|
+
}), /* @__PURE__ */ m("span", {
|
|
176
|
+
className: "max-w-full truncate text-center",
|
|
177
|
+
children: t.title
|
|
178
|
+
})]
|
|
179
|
+
}, t.href);
|
|
153
180
|
})
|
|
154
|
-
]
|
|
155
|
-
})
|
|
181
|
+
})]
|
|
182
|
+
})]
|
|
156
183
|
})]
|
|
157
|
-
})
|
|
184
|
+
});
|
|
158
185
|
});
|
|
159
|
-
|
|
186
|
+
w.displayName = "BottomNavbar";
|
|
160
187
|
//#endregion
|
|
161
|
-
export {
|
|
188
|
+
export { w as BottomNavbar };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bottom-navbar.js","names":[],"sources":["../../src/shell/bottom-navbar.tsx"],"sourcesContent":["'use client'\n\n/**\n * BottomNavbar -- Mobile bottom navigation bar.\n *\n * Props-driven: accepts currentPath, user, navItems instead of\n * reading from Remix hooks or Zustand stores.\n */\nimport { IconDots, IconX } from '@tabler/icons-react'\nimport { AnimatePresence, motion, useReducedMotion } from 'framer-motion'\nimport * as React from 'react'\nimport { useCallback,useEffect, useRef, useState } from 'react'\n\nimport { Icon } from '../ui/icon'\nimport { IconProvider } from '../ui/icon-context'\nimport type { IconInput } from '../ui/lib/icon-input'\nimport { springs } from '../ui/lib/motion'\nimport { normalizeIcon } from '../ui/lib/normalize-icon'\nimport { cn } from '../ui/lib/utils'\nimport { useLink } from './link-context'\n\n// -----------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------\n\nexport interface BottomNavItem {\n title: string\n href: string\n /** Icon for this nav item. Accepts any `IconInput`. */\n icon: IconInput\n /** When true, the route matches only when the path is exactly equal */\n exact?: boolean\n /** Notification badge count. 0 or undefined = hidden, 1–99 = shown, >99 = \"99+\" */\n badge?: number\n}\n\nexport interface BottomNavbarUser {\n name: string\n role?: string\n}\n\nexport interface BottomNavbarProps\n extends React.HTMLAttributes<HTMLElement> {\n /** Currently active pathname */\n currentPath?: string\n /** User information (used to determine admin status, presence) */\n user?: BottomNavbarUser | null\n /** Primary nav items shown directly in the bottom bar (max 4 recommended) */\n primaryItems?: BottomNavItem[]\n /** Additional items shown in the \"More\" overflow menu */\n moreItems?: BottomNavItem[]\n /** Additional className for the nav element */\n className?: string\n}\n\n// -----------------------------------------------------------------------\n// NavBadge (internal)\n// -----------------------------------------------------------------------\n\nfunction NavBadge({ count }: { count: number }) {\n if (!count || count <= 0) return null\n const display = count > 99 ? '99+' : String(count)\n const isMultiDigit = count >= 10\n return (\n <span\n aria-label={`${count} notifications`}\n className={cn(\n 'absolute -right-1 -top-0.5 flex h-4 min-w-4 items-center justify-center rounded-pill bg-error-9 text-[10px] font-semibold leading-none text-error-fg animate-in zoom-in-75',\n isMultiDigit ? 'px-0.5' : '',\n )}\n >\n {display}\n </span>\n )\n}\n\n// -----------------------------------------------------------------------\n// BottomNavLink (internal)\n// -----------------------------------------------------------------------\n\nfunction BottomNavLink({\n item,\n isActive,\n onClick,\n}: {\n item: BottomNavItem\n isActive: boolean\n onClick?: () => void\n}) {\n const Link = useLink()\n const prefersReducedMotion = useReducedMotion()\n return (\n <motion.div whileTap={prefersReducedMotion ? undefined : { scale: 0.96 }} transition={prefersReducedMotion ? { duration: 0 } : springs.snappy} className=\"flex max-w-[70px] flex-1\">\n <Link\n href={item.href}\n onClick={onClick}\n aria-label={item.title}\n aria-current={isActive ? 'page' : undefined}\n className={cn(\n 'flex h-16 w-full cursor-pointer flex-col items-center gap-ds-02 p-ds-02 pt-0 text-body-sm transition-colors duration-fast-02 ease-productive-standard',\n isActive\n ? 'font-semibold text-accent-11'\n : 'text-surface-fg-subtle',\n )}\n >\n <div className=\"relative flex w-full flex-col items-center gap-ds-02\">\n {isActive && (\n <motion.div\n layoutId=\"bottom-nav-indicator\"\n className=\"absolute top-0 h-[3px] w-full rounded-b-control-inner bg-accent-9 p-0\"\n aria-hidden=\"true\"\n transition={prefersReducedMotion ? { duration: 0 } : springs.snappy}\n />\n )}\n <div className=\"relative p-ds-03\">\n <span className=\"[&>svg]:h-ico-md [&>svg]:w-ico-md\" aria-hidden=\"true\"><IconProvider size=\"md\">{normalizeIcon(item.icon)}</IconProvider></span>\n {item.badge != null && <NavBadge count={item.badge} />}\n </div>\n <span className=\"text-center\">{item.title}</span>\n </div>\n </Link>\n </motion.div>\n )\n}\n\n// -----------------------------------------------------------------------\n// BottomNavbar\n// -----------------------------------------------------------------------\n\nconst BottomNavbar = React.forwardRef<HTMLElement, BottomNavbarProps>(\n (\n {\n currentPath = '/',\n user: _user,\n primaryItems = [],\n moreItems = [],\n className,\n ...props\n },\n ref,\n ) => {\n const Link = useLink()\n const prefersReducedMotion = useReducedMotion()\n const [showMore, setShowMore] = useState(false)\n const overlayRef = useRef<HTMLDivElement>(null)\n\n const closeMore = useCallback(() => setShowMore(false), [])\n\n // Focus first focusable item when overlay opens\n useEffect(() => {\n if (showMore && overlayRef.current) {\n const firstFocusable = overlayRef.current.querySelector<HTMLElement>('a, button')\n firstFocusable?.focus()\n }\n }, [showMore])\n\n const isActive = (path: string, exact = false) => {\n if (exact || path === '/') {\n return currentPath === path\n }\n return currentPath.startsWith(path)\n }\n\n // Check if any \"more\" item is active\n const isMoreActive = moreItems.some((item) =>\n isActive(item.href, item.exact),\n )\n\n return (\n <>\n {/* More Menu Overlay */}\n <AnimatePresence>\n {showMore && (\n // eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events -- backdrop overlay, dismiss via mouse only; keyboard users close via Escape\n <div\n className=\"fixed inset-0 z-overlay md:hidden\"\n onClick={() => setShowMore(false)}\n >\n <div className=\"absolute inset-0 bg-overlay\" />\n { }\n <motion.div\n ref={overlayRef}\n role=\"dialog\"\n aria-label=\"More navigation\"\n initial={{ y: '100%' }}\n animate={{ y: 0 }}\n exit={{ y: '100%' }}\n transition={prefersReducedMotion ? { duration: 0 } : springs.smooth}\n className=\"absolute bottom-[72px] left-0 right-0 rounded-t-bubble border-t border-surface-border-strong bg-surface-overlay p-ds-05 pb-ds-03\"\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => {\n e.stopPropagation()\n if (e.key === 'Escape') closeMore()\n }}\n >\n <div className=\"mb-ds-04 flex items-center justify-between\">\n <span className=\"text-body-md font-semibold text-surface-fg\">\n More\n </span>\n <button\n onClick={() => setShowMore(false)}\n aria-label=\"Close more menu\"\n className=\"flex h-ds-sm w-ds-sm items-center justify-center rounded-pill hover:bg-surface-raised-hover\"\n >\n <Icon icon={IconX} size=\"sm\" className=\"text-surface-fg-muted\" />\n </button>\n </div>\n <div className=\"grid grid-cols-4 gap-ds-03\">\n {moreItems.map((item) => (\n <Link\n key={item.href}\n href={item.href}\n onClick={() => setShowMore(false)}\n className={cn(\n 'flex flex-col items-center gap-ds-02b rounded-overlay-lg p-ds-04 text-body-sm transition-colors ease-productive-standard',\n isActive(item.href, item.exact)\n ? 'bg-surface-raised-hover text-accent-11'\n : 'text-surface-fg-subtle hover:bg-surface-raised-hover',\n )}\n >\n <span className=\"[&>svg]:h-ico-md [&>svg]:w-ico-md\"><IconProvider size=\"md\">{normalizeIcon(item.icon)}</IconProvider></span>\n <span className=\"text-center\">\n {item.title}\n </span>\n </Link>\n ))}\n </div>\n </motion.div>\n </div>\n )}\n </AnimatePresence>\n\n {/* Bottom Navigation Bar */}\n <nav\n {...props}\n ref={ref}\n aria-label=\"Mobile navigation\"\n className={cn(\n 'fixed bottom-0 left-0 right-0 z-sticky flex w-full flex-row items-start justify-between border-t border-surface-border-strong bg-surface-chrome px-ds-05 pb-safe pt-0 md:hidden',\n className,\n )}\n >\n {primaryItems.map((item) => (\n <BottomNavLink\n key={item.href}\n item={item}\n isActive={isActive(item.href, item.exact)}\n />\n ))}\n\n {/* More Button */}\n {moreItems.length > 0 && (\n <motion.button\n type=\"button\"\n onClick={() => setShowMore(!showMore)}\n aria-label=\"More navigation options\"\n aria-expanded={showMore}\n whileTap={prefersReducedMotion ? undefined : { y: -2 }}\n transition={prefersReducedMotion ? { duration: 0 } : springs.snappy}\n className={cn(\n 'flex h-16 max-w-[70px] flex-1 cursor-pointer flex-col items-center gap-ds-02 p-ds-02 pt-0 text-body-sm',\n showMore || isMoreActive\n ? 'font-semibold text-accent-11'\n : 'text-surface-fg-subtle',\n )}\n >\n <div className=\"relative flex w-full flex-col items-center gap-ds-02\">\n {(showMore || isMoreActive) && (\n <motion.div\n layoutId=\"bottom-nav-indicator\"\n className=\"absolute top-0 h-[3px] w-full rounded-b-control-inner bg-accent-9 p-0\"\n aria-hidden=\"true\"\n transition={prefersReducedMotion ? { duration: 0 } : springs.snappy}\n />\n )}\n <div className=\"p-ds-03\">\n <Icon icon={IconDots} />\n </div>\n <span className=\"text-center\">More</span>\n </div>\n </motion.button>\n )}\n </nav>\n </>\n )\n },\n)\nBottomNavbar.displayName = 'BottomNavbar'\n\nexport { BottomNavbar }\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bottom-navbar.js","names":[],"sources":["../../src/shell/bottom-navbar.tsx"],"sourcesContent":["'use client'\n\n/**\n * BottomNavbar -- Mobile bottom navigation bar.\n *\n * Props-driven (currentPath / user / items) and router-agnostic via `useLink`.\n * The \"More\" overflow uses the DS `Sheet` primitive, so it inherits focus trap,\n * scroll lock, return-focus, `aria-modal`, and trigger↔panel ARIA wiring.\n */\nimport { IconDots } from '@tabler/icons-react'\nimport { motion, useReducedMotion } from 'framer-motion'\nimport * as React from 'react'\nimport { useState } from 'react'\n\nimport { Badge } from '../ui/badge'\nimport { Icon } from '../ui/icon'\nimport { IconProvider } from '../ui/icon-context'\nimport type { IconInput } from '../ui/lib/icon-input'\nimport { springs } from '../ui/lib/motion'\nimport { normalizeIcon } from '../ui/lib/normalize-icon'\nimport { cn } from '../ui/lib/utils'\nimport { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '../ui/sheet'\nimport { useLink } from './link-context'\n\n// -----------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------\n\nexport interface BottomNavbarUser {\n name: string\n role?: string\n}\n\nexport interface BottomNavItem {\n title: string\n href: string\n /** Icon for this nav item. Accepts any `IconInput`. */\n icon: IconInput\n /** Icon shown when the item is active — e.g. a filled variant. Falls back to `icon`. */\n activeIcon?: IconInput\n /** When true, the route matches only when the path is exactly equal */\n exact?: boolean\n /** Notification badge count. 0 or undefined = hidden, 1–99 = shown, >99 = \"99+\" */\n badge?: number\n /**\n * Roles allowed to see this item, matched against `user.role`. Omit to show\n * it to everyone. For arbitrary logic, use `canView` instead.\n */\n roles?: string[]\n /**\n * Visibility predicate — full control over whether this item renders.\n * Receives the current `user` (or `null`). Takes precedence over `roles`.\n */\n canView?: (user: BottomNavbarUser | null) => boolean\n}\n\n/**\n * Active-item indicator, animated between items on selection:\n * - `pill` (default) — Material-3 tonal pill behind the icon\n * - `underline` — top accent bar (Material-2 tab style)\n * - `tint` — subtle background tint on the whole active cell\n * - `none` — no shape; rely on the filled `activeIcon` + accent color (iOS)\n */\nexport type BottomNavIndicator = 'pill' | 'underline' | 'tint' | 'none'\n/** Show labels always (default) or only for the selected item (narrow viewports). */\nexport type BottomNavLabelVisibility = 'always' | 'selected'\n\nexport interface BottomNavbarProps extends React.HTMLAttributes<HTMLElement> {\n /** Currently active pathname */\n currentPath?: string\n /** Current user. Drives per-item role gating via `item.roles` / `item.canView`. */\n user?: BottomNavbarUser | null\n /** Primary nav items shown directly in the bottom bar (max 4 recommended) */\n primaryItems?: BottomNavItem[]\n /** Additional items shown in the \"More\" overflow sheet */\n moreItems?: BottomNavItem[]\n /** Active-indicator style. @default 'pill' */\n indicator?: BottomNavIndicator\n /** Label visibility. @default 'always' */\n labelVisibility?: BottomNavLabelVisibility\n /** Additional className for the nav element */\n className?: string\n}\n\n// -----------------------------------------------------------------------\n// Helpers\n// -----------------------------------------------------------------------\n\n/** Per-item role/visibility gate. `canView` wins; else `roles` matched against user.role; else visible. */\nfunction itemVisible(item: BottomNavItem, user: BottomNavbarUser | null): boolean {\n if (item.canView) return item.canView(user)\n if (item.roles && item.roles.length > 0) {\n return !!user?.role && item.roles.includes(user.role)\n }\n return true\n}\n\n// -----------------------------------------------------------------------\n// NavBadge (composes Badge)\n// -----------------------------------------------------------------------\n\nfunction NavBadge({ count }: { count: number }) {\n if (!count || count <= 0) return null\n const display = count > 99 ? '99+' : String(count)\n return (\n <Badge\n color=\"error\"\n variant=\"solid\"\n size=\"xs\"\n aria-label={`${count} notifications`}\n className=\"pointer-events-none absolute -end-1 -top-1 justify-center px-ds-01 tabular-nums motion-safe:animate-in motion-safe:zoom-in-75\"\n >\n {display}\n </Badge>\n )\n}\n\n// -----------------------------------------------------------------------\n// Active indicator (shared-element)\n// -----------------------------------------------------------------------\n\n/**\n * The active-item indicator. A single shared-element (`layoutId`) that SLIDES\n * to the selected item and fades in on first appearance. `none` renders\n * nothing (the filled `activeIcon` + accent color carry the state).\n */\nfunction ActiveIndicator({\n indicator,\n layoutId,\n reduced,\n}: {\n indicator: BottomNavIndicator\n layoutId: string\n reduced: boolean\n}) {\n if (indicator === 'none') return null\n const common = {\n layoutId,\n 'aria-hidden': true as const,\n transition: reduced ? { duration: 0 } : springs.snappy,\n initial: reduced ? false : { opacity: 0 },\n animate: { opacity: 1 },\n }\n if (indicator === 'underline') {\n return <motion.span {...common} className=\"absolute top-0 h-[3px] w-full rounded-b-control-inner bg-accent-9\" />\n }\n if (indicator === 'tint') {\n return <motion.span {...common} className=\"absolute inset-0 rounded-control bg-accent-3\" />\n }\n // pill (default) — Material-3 tonal pill behind the icon\n return <motion.span {...common} className=\"absolute inset-0 rounded-pill bg-accent-3\" />\n}\n\n// -----------------------------------------------------------------------\n// BottomNavLink (internal)\n// -----------------------------------------------------------------------\n\nconst itemClass =\n 'relative flex h-16 w-full min-w-0 cursor-pointer flex-col items-center justify-center gap-ds-02 px-ds-01 pt-0 text-body-sm transition-colors duration-fast-02 ease-productive-standard'\n\nfunction BottomNavLink({\n item,\n isActive,\n indicator,\n indicatorId,\n showLabel,\n reduced,\n}: {\n item: BottomNavItem\n isActive: boolean\n indicator: BottomNavIndicator\n indicatorId: string\n showLabel: boolean\n reduced: boolean\n}) {\n const Link = useLink()\n return (\n <motion.div\n whileTap={reduced ? undefined : { scale: 0.96 }}\n transition={reduced ? { duration: 0 } : springs.snappy}\n className=\"flex min-w-0 flex-1 basis-0\"\n >\n <Link\n href={item.href}\n aria-label={item.title}\n aria-current={isActive ? 'page' : undefined}\n className={cn(itemClass, isActive ? 'font-semibold text-accent-11' : 'text-surface-fg-subtle')}\n >\n {/* tint covers the whole cell — sits behind the content */}\n {isActive && indicator === 'tint' && (\n <ActiveIndicator indicator=\"tint\" layoutId={indicatorId} reduced={reduced} />\n )}\n <div className=\"relative flex w-full min-w-0 flex-col items-center gap-ds-02\">\n {isActive && indicator === 'underline' && (\n <ActiveIndicator indicator=\"underline\" layoutId={indicatorId} reduced={reduced} />\n )}\n <div className=\"relative px-ds-04 py-ds-01\">\n {isActive && indicator === 'pill' && (\n <ActiveIndicator indicator=\"pill\" layoutId={indicatorId} reduced={reduced} />\n )}\n <span className=\"relative [&>svg]:h-ico-md [&>svg]:w-ico-md\" aria-hidden=\"true\">\n <IconProvider size=\"md\">\n {normalizeIcon(isActive && item.activeIcon ? item.activeIcon : item.icon)}\n </IconProvider>\n </span>\n {item.badge != null && <NavBadge count={item.badge} />}\n </div>\n {showLabel && <span className=\"max-w-full truncate text-center\">{item.title}</span>}\n </div>\n </Link>\n </motion.div>\n )\n}\n\n// -----------------------------------------------------------------------\n// BottomNavbar\n// -----------------------------------------------------------------------\n\nconst BottomNavbar = React.forwardRef<HTMLElement, BottomNavbarProps>(\n (\n {\n currentPath = '/',\n user = null,\n primaryItems = [],\n moreItems = [],\n indicator = 'pill',\n labelVisibility = 'always',\n className,\n ...props\n },\n ref,\n ) => {\n const Link = useLink()\n const reduced = useReducedMotion() ?? false\n const [showMore, setShowMore] = useState(false)\n // Scope the shared-element layoutId per instance so two navbars don't animate into each other.\n const instanceId = React.useId()\n const indicatorId = `${instanceId}-nav-indicator`\n\n const isActive = (path: string, exact = false) => {\n if (exact || path === '/') return currentPath === path\n return currentPath.startsWith(path)\n }\n\n // Role/visibility gating\n const visiblePrimary = primaryItems.filter((item) => itemVisible(item, user))\n const visibleMore = moreItems.filter((item) => itemVisible(item, user))\n\n const isMoreActive = visibleMore.some((item) => isActive(item.href, item.exact))\n const moreSelected = showMore || isMoreActive\n const showMoreLabel = labelVisibility === 'always' || moreSelected\n\n return (\n <nav\n {...props}\n ref={ref}\n aria-label=\"Mobile navigation\"\n className={cn(\n 'fixed bottom-0 start-0 end-0 z-sticky flex w-full flex-row items-stretch justify-between border-t border-surface-border-strong bg-surface-chrome px-ds-05 pb-safe pt-0 md:hidden',\n className,\n )}\n >\n {visiblePrimary.map((item) => (\n <BottomNavLink\n key={item.href}\n item={item}\n isActive={isActive(item.href, item.exact)}\n indicator={indicator}\n indicatorId={indicatorId}\n showLabel={labelVisibility === 'always' || isActive(item.href, item.exact)}\n reduced={reduced}\n />\n ))}\n\n {visibleMore.length > 0 && (\n <Sheet open={showMore} onOpenChange={setShowMore}>\n <SheetTrigger asChild>\n <motion.button\n type=\"button\"\n aria-label=\"More navigation options\"\n whileTap={reduced ? undefined : { scale: 0.96 }}\n transition={reduced ? { duration: 0 } : springs.snappy}\n className={cn(\n itemClass,\n 'min-w-0 flex-1 basis-0',\n moreSelected ? 'font-semibold text-accent-11' : 'text-surface-fg-subtle',\n )}\n >\n {moreSelected && indicator === 'tint' && (\n <ActiveIndicator indicator=\"tint\" layoutId={indicatorId} reduced={reduced} />\n )}\n <div className=\"relative flex w-full min-w-0 flex-col items-center gap-ds-02\">\n {moreSelected && indicator === 'underline' && (\n <ActiveIndicator indicator=\"underline\" layoutId={indicatorId} reduced={reduced} />\n )}\n <div className=\"relative px-ds-04 py-ds-01\">\n {moreSelected && indicator === 'pill' && (\n <ActiveIndicator indicator=\"pill\" layoutId={indicatorId} reduced={reduced} />\n )}\n <Icon icon={IconDots} />\n </div>\n {showMoreLabel && <span className=\"max-w-full truncate text-center\">More</span>}\n </div>\n </motion.button>\n </SheetTrigger>\n\n <SheetContent side=\"bottom\" className=\"rounded-t-bubble\">\n <SheetHeader>\n <SheetTitle>More</SheetTitle>\n </SheetHeader>\n <div className=\"grid grid-cols-[repeat(auto-fit,minmax(72px,1fr))] gap-ds-03 pt-ds-04\">\n {visibleMore.map((item) => {\n const active = isActive(item.href, item.exact)\n return (\n <Link\n key={item.href}\n href={item.href}\n onClick={() => setShowMore(false)}\n aria-current={active ? 'page' : undefined}\n className={cn(\n 'flex min-w-0 flex-col items-center gap-ds-02b rounded-overlay-lg p-ds-04 text-body-sm transition-colors ease-productive-standard',\n active\n ? 'bg-surface-raised-hover text-accent-11'\n : 'text-surface-fg-subtle hover:bg-surface-raised-hover',\n )}\n >\n <span className=\"[&>svg]:h-ico-md [&>svg]:w-ico-md\" aria-hidden=\"true\">\n <IconProvider size=\"md\">{normalizeIcon(item.icon)}</IconProvider>\n </span>\n <span className=\"max-w-full truncate text-center\">{item.title}</span>\n </Link>\n )\n })}\n </div>\n </SheetContent>\n </Sheet>\n )}\n </nav>\n )\n },\n)\nBottomNavbar.displayName = 'BottomNavbar'\n\nexport { BottomNavbar }\n"],"mappings":";;;;;;;;;;;;;;;;AAyFA,SAAS,EAAY,GAAqB,GAAwC;CAKhF,OAJI,EAAK,UAAgB,EAAK,QAAQ,CAAI,IACtC,EAAK,SAAS,EAAK,MAAM,SAAS,IAC7B,CAAC,CAAC,GAAM,QAAQ,EAAK,MAAM,SAAS,EAAK,IAAI,IAE/C;AACT;AAMA,SAAS,EAAS,EAAE,YAA4B;CAC9C,IAAI,CAAC,KAAS,KAAS,GAAG,OAAO;CACjC,IAAM,IAAU,IAAQ,KAAK,QAAQ,OAAO,CAAK;CACjD,OACE,kBAAC,GAAD;EACE,OAAM;EACN,SAAQ;EACR,MAAK;EACL,cAAY,GAAG,EAAM;EACrB,WAAU;YAET;CACI,CAAA;AAEX;AAWA,SAAS,EAAgB,EACvB,cACA,aACA,cAKC;CACD,IAAI,MAAc,QAAQ,OAAO;CACjC,IAAM,IAAS;EACb;EACA,eAAe;EACf,YAAY,IAAU,EAAE,UAAU,EAAE,IAAI,EAAQ;EAChD,SAAS,MAAkB,EAAE,SAAS,EAAE;EACxC,SAAS,EAAE,SAAS,EAAE;CACxB;CAQA,OAPI,MAAc,cACT,kBAAC,EAAO,MAAR;EAAa,GAAI;EAAQ,WAAU;CAAqE,CAAA,IAE7G,MAAc,SACT,kBAAC,EAAO,MAAR;EAAa,GAAI;EAAQ,WAAU;CAAgD,CAAA,IAGrF,kBAAC,EAAO,MAAR;EAAa,GAAI;EAAQ,WAAU;CAA6C,CAAA;AACzF;AAMA,IAAM,IACJ;AAEF,SAAS,EAAc,EACrB,SACA,aACA,cACA,gBACA,cACA,cAQC;CACD,IAAM,IAAO,EAAQ;CACrB,OACE,kBAAC,EAAO,KAAR;EACE,UAAU,IAAU,KAAA,IAAY,EAAE,OAAO,IAAK;EAC9C,YAAY,IAAU,EAAE,UAAU,EAAE,IAAI,EAAQ;EAChD,WAAU;YAEV,kBAAC,GAAD;GACE,MAAM,EAAK;GACX,cAAY,EAAK;GACjB,gBAAc,IAAW,SAAS,KAAA;GAClC,WAAW,EAAG,GAAW,IAAW,iCAAiC,wBAAwB;aAJ/F,CAOG,KAAY,MAAc,UACzB,kBAAC,GAAD;IAAiB,WAAU;IAAO,UAAU;IAAsB;GAAU,CAAA,GAE9E,kBAAC,OAAD;IAAK,WAAU;cAAf;KACG,KAAY,MAAc,eACzB,kBAAC,GAAD;MAAiB,WAAU;MAAY,UAAU;MAAsB;KAAU,CAAA;KAEnF,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACG,KAAY,MAAc,UACzB,kBAAC,GAAD;QAAiB,WAAU;QAAO,UAAU;QAAsB;OAAU,CAAA;OAE9E,kBAAC,QAAD;QAAM,WAAU;QAA6C,eAAY;kBACvE,kBAAC,GAAD;SAAc,MAAK;mBAChB,EAAc,KAAY,EAAK,aAAa,EAAK,aAAa,EAAK,IAAI;QAC5D,CAAA;OACV,CAAA;OACL,EAAK,SAAS,QAAQ,kBAAC,GAAD,EAAU,OAAO,EAAK,MAAQ,CAAA;MAClD;;KACJ,KAAa,kBAAC,QAAD;MAAM,WAAU;gBAAmC,EAAK;KAAY,CAAA;IAC/E;KACD;;CACI,CAAA;AAEhB;AAMA,IAAM,IAAe,EAAM,YAEvB,EACE,iBAAc,KACd,UAAO,MACP,kBAAe,CAAC,GAChB,eAAY,CAAC,GACb,eAAY,QACZ,qBAAkB,UAClB,cACA,GAAG,KAEL,MACG;CACH,IAAM,IAAO,EAAQ,GACf,IAAU,EAAiB,KAAK,IAChC,CAAC,GAAU,KAAe,EAAS,EAAK,GAGxC,IAAc,GADD,EAAM,MACF,EAAW,iBAE5B,KAAY,GAAc,IAAQ,OAClC,KAAS,MAAS,MAAY,MAAgB,IAC3C,EAAY,WAAW,CAAI,GAI9B,IAAiB,EAAa,QAAQ,MAAS,EAAY,GAAM,CAAI,CAAC,GACtE,IAAc,EAAU,QAAQ,MAAS,EAAY,GAAM,CAAI,CAAC,GAEhE,IAAe,EAAY,MAAM,MAAS,EAAS,EAAK,MAAM,EAAK,KAAK,CAAC,GACzE,IAAe,KAAY,GAC3B,IAAgB,MAAoB,YAAY;CAEtD,OACE,kBAAC,OAAD;EACE,GAAI;EACC;EACL,cAAW;EACX,WAAW,EACT,oLACA,CACF;YAPF,CASG,EAAe,KAAK,MACnB,kBAAC,GAAD;GAEQ;GACN,UAAU,EAAS,EAAK,MAAM,EAAK,KAAK;GAC7B;GACE;GACb,WAAW,MAAoB,YAAY,EAAS,EAAK,MAAM,EAAK,KAAK;GAChE;EACV,GAPM,EAAK,IAOX,CACF,GAEA,EAAY,SAAS,KACpB,kBAAC,GAAD;GAAO,MAAM;GAAU,cAAc;aAArC,CACE,kBAAC,GAAD;IAAc,SAAA;cACZ,kBAAC,EAAO,QAAR;KACE,MAAK;KACL,cAAW;KACX,UAAU,IAAU,KAAA,IAAY,EAAE,OAAO,IAAK;KAC9C,YAAY,IAAU,EAAE,UAAU,EAAE,IAAI,EAAQ;KAChD,WAAW,EACT,GACA,0BACA,IAAe,iCAAiC,wBAClD;eATF,CAWG,KAAgB,MAAc,UAC7B,kBAAC,GAAD;MAAiB,WAAU;MAAO,UAAU;MAAsB;KAAU,CAAA,GAE9E,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACG,KAAgB,MAAc,eAC7B,kBAAC,GAAD;QAAiB,WAAU;QAAY,UAAU;QAAsB;OAAU,CAAA;OAEnF,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACG,KAAgB,MAAc,UAC7B,kBAAC,GAAD;SAAiB,WAAU;SAAO,UAAU;SAAsB;QAAU,CAAA,GAE9E,kBAAC,GAAD,EAAM,MAAM,EAAW,CAAA,CACpB;;OACJ,KAAiB,kBAAC,QAAD;QAAM,WAAU;kBAAkC;OAAU,CAAA;MAC3E;OACQ;;GACH,CAAA,GAEd,kBAAC,GAAD;IAAc,MAAK;IAAS,WAAU;cAAtC,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAY,OAAgB,CAAA,EACjB,CAAA,GACb,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAY,KAAK,MAAS;MACzB,IAAM,IAAS,EAAS,EAAK,MAAM,EAAK,KAAK;MAC7C,OACE,kBAAC,GAAD;OAEE,MAAM,EAAK;OACX,eAAe,EAAY,EAAK;OAChC,gBAAc,IAAS,SAAS,KAAA;OAChC,WAAW,EACT,oIACA,IACI,2CACA,sDACN;iBAVF,CAYE,kBAAC,QAAD;QAAM,WAAU;QAAoC,eAAY;kBAC9D,kBAAC,GAAD;SAAc,MAAK;mBAAM,EAAc,EAAK,IAAI;QAAgB,CAAA;OAC5D,CAAA,GACN,kBAAC,QAAD;QAAM,WAAU;kBAAmC,EAAK;OAAY,CAAA,CAChE;SAfC,EAAK,IAeN;KAEV,CAAC;IACE,CAAA,CACO;KACT;IAEN;;AAET,CACF;AACA,EAAa,cAAc"}
|
package/dist/shell/index.d.ts
CHANGED
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @module @devalok/shilp-sutra/shell
|
|
4
4
|
*
|
|
5
|
-
* Application shell components:
|
|
5
|
+
* Application shell components: top bar, bottom nav, notifications, and command palette.
|
|
6
6
|
* All shell components require client-side React ("use client").
|
|
7
7
|
*
|
|
8
|
+
* For sidebars, compose the `Sidebar` primitives from `@devalok/shilp-sutra/ui/sidebar`,
|
|
9
|
+
* or start from a ready-made preset (e.g. `sidebar-app`) at
|
|
10
|
+
* https://shilp-sutra.devalok.in/presets.
|
|
11
|
+
*
|
|
8
12
|
* @example
|
|
9
|
-
* import { AppSidebar } from '@devalok/shilp-sutra/shell/sidebar'
|
|
10
13
|
* import { TopBar } from '@devalok/shilp-sutra/shell/top-bar'
|
|
11
14
|
*/
|
|
12
15
|
export { AppCommandPalette, type AppCommandPaletteProps, type AppCommandPaletteUser, type SearchResult, type SearchResultGroup, } from './app-command-palette';
|
|
@@ -15,6 +18,5 @@ export { type CommandPageItem, type CommandRegistry, CommandRegistryProvider, ty
|
|
|
15
18
|
export { LinkProvider, type LinkProviderProps, useLink } from './link-context';
|
|
16
19
|
export { type Notification, NotificationCenter, type NotificationCenterProps, } from './notification-center';
|
|
17
20
|
export { type NotificationPreference, NotificationPreferences, type NotificationPreferencesProps, type NotificationProject, } from './notification-preferences';
|
|
18
|
-
export { AppSidebar, type AppSidebarProps, type NavGroup, type NavItem, type SidebarUser, } from './sidebar';
|
|
19
21
|
export { TopBar, type TopBarProps, type TopBarUser, type UserMenuItem, } from './top-bar';
|
|
20
22
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/shell/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/shell/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EACL,iBAAiB,EACjB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,YAAY,EACjB,KAAK,iBAAiB,GACvB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACL,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,aAAa,GACnB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,uBAAuB,EACvB,KAAK,4BAA4B,EACjC,kBAAkB,GACnB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAC,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAC7E,OAAO,EACL,KAAK,YAAY,EACjB,kBAAkB,EAClB,KAAK,uBAAuB,GAC7B,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACL,KAAK,sBAAsB,EAC3B,uBAAuB,EACvB,KAAK,4BAA4B,EACjC,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAA;AACnC,OAAO,EACL,MAAM,EACN,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,YAAY,GAClB,MAAM,WAAW,CAAA"}
|
package/dist/shell/index.js
CHANGED
|
@@ -6,6 +6,5 @@ import "./link-context.js";
|
|
|
6
6
|
import { BottomNavbar as a } from "./bottom-navbar.js";
|
|
7
7
|
import { NotificationCenter as o } from "./notification-center.js";
|
|
8
8
|
import { NotificationPreferences as s } from "./notification-preferences.js";
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
export { i as AppCommandPalette, c as AppSidebar, a as BottomNavbar, n as CommandRegistryProvider, e as LinkProvider, o as NotificationCenter, s as NotificationPreferences, l as TopBar, r as useCommandRegistry, t as useLink };
|
|
9
|
+
import { TopBar as c } from "./top-bar.js";
|
|
10
|
+
export { i as AppCommandPalette, a as BottomNavbar, n as CommandRegistryProvider, e as LinkProvider, o as NotificationCenter, s as NotificationPreferences, c as TopBar, r as useCommandRegistry, t as useLink };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notification-preferences.d.ts","sourceRoot":"","sources":["../../src/shell/notification-preferences.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AA6B9B,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,OAAO,CAAA;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,4BACf,SAAQ,IAAI,CAAC,KAAK,CAAC,qBAAqB,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC;IAC1D,+BAA+B;IAC/B,WAAW,CAAC,EAAE,sBAAsB,EAAE,CAAA;IACtC,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,mBAAmB,EAAE,CAAA;IAChC,8BAA8B;IAC9B,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,2CAA2C;IAC3C,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE;QACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;QACxB,OAAO,EAAE,MAAM,CAAA;QACf,OAAO,EAAE,MAAM,CAAA;QACf,KAAK,EAAE,OAAO,CAAA;KACf,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1B,4CAA4C;IAC5C,YAAY,CAAC,EAAE,CAAC,UAAU,EAAE,sBAAsB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3E,gDAAgD;IAChD,YAAY,CAAC,EAAE,CACb,UAAU,EAAE,sBAAsB,EAClC,OAAO,EAAE,MAAM,KACZ,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACzB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACzD,2BAA2B;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAqBD,QAAA,MAAM,uBAAuB,
|
|
1
|
+
{"version":3,"file":"notification-preferences.d.ts","sourceRoot":"","sources":["../../src/shell/notification-preferences.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AA6B9B,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,OAAO,CAAA;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,4BACf,SAAQ,IAAI,CAAC,KAAK,CAAC,qBAAqB,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC;IAC1D,+BAA+B;IAC/B,WAAW,CAAC,EAAE,sBAAsB,EAAE,CAAA;IACtC,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,mBAAmB,EAAE,CAAA;IAChC,8BAA8B;IAC9B,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,2CAA2C;IAC3C,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE;QACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;QACxB,OAAO,EAAE,MAAM,CAAA;QACf,OAAO,EAAE,MAAM,CAAA;QACf,KAAK,EAAE,OAAO,CAAA;KACf,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1B,4CAA4C;IAC5C,YAAY,CAAC,EAAE,CAAC,UAAU,EAAE,sBAAsB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3E,gDAAgD;IAChD,YAAY,CAAC,EAAE,CACb,UAAU,EAAE,sBAAsB,EAClC,OAAO,EAAE,MAAM,KACZ,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACzB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACzD,2BAA2B;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAqBD,QAAA,MAAM,uBAAuB,kHAgQ5B,CAAA;AAID,OAAO,EAAE,uBAAuB,EAAE,CAAA"}
|
|
@@ -109,6 +109,7 @@ var k = {
|
|
|
109
109
|
value: t.minTier,
|
|
110
110
|
onValueChange: (e) => N?.(t, e),
|
|
111
111
|
children: [/* @__PURE__ */ C(_, {
|
|
112
|
+
"aria-label": `Minimum notification tier for ${i.label} on ${Q(t.projectId)}`,
|
|
112
113
|
className: "h-ds-xs-plus w-[130px] text-body-sm",
|
|
113
114
|
children: /* @__PURE__ */ C(v, {})
|
|
114
115
|
}), /* @__PURE__ */ C(h, { children: Object.entries(A).map(([e, t]) => /* @__PURE__ */ C(g, {
|
|
@@ -122,6 +123,7 @@ var k = {
|
|
|
122
123
|
className: "text-body-sm text-surface-fg-subtle",
|
|
123
124
|
children: t.muted ? "Muted" : "Active"
|
|
124
125
|
}), /* @__PURE__ */ C(y, {
|
|
126
|
+
"aria-label": `${t.muted ? "Unmute" : "Mute"} ${i.label} for ${Q(t.projectId)}`,
|
|
125
127
|
checked: !t.muted,
|
|
126
128
|
onCheckedChange: () => M?.(t)
|
|
127
129
|
})]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notification-preferences.js","names":[],"sources":["../../src/shell/notification-preferences.tsx"],"sourcesContent":["'use client'\n\n/**\n * NotificationPreferences -- Manage notification rules per channel/project.\n *\n * Props-driven: accepts preferences array, projects, and callbacks.\n * No internal fetching -- the consumer is responsible for data loading.\n */\nimport { IconBell, IconMessage,IconPlus, IconTrash } from '@tabler/icons-react'\nimport * as React from 'react'\nimport { useState } from 'react'\n\nimport { Button } from '../ui/button'\nimport { Card, CardContent,CardHeader, CardTitle } from '../ui/card'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n} from '../ui/dialog'\nimport { Icon } from '../ui/icon'\nimport { IconButton } from '../ui/icon-button'\nimport { cn } from '../ui/lib/utils'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../ui/select'\nimport { Spinner } from '../ui/spinner'\nimport { Switch } from '../ui/switch'\n\n// -----------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------\n\nexport interface NotificationPreference {\n id: string\n userId?: string\n projectId: string | null\n channel: string\n minTier: string\n muted: boolean\n}\n\nexport interface NotificationProject {\n id: string\n title: string\n}\n\nexport interface NotificationPreferencesProps\n extends Omit<React.ComponentPropsWithRef<'div'>, 'onSave'> {\n /** Current preference rules */\n preferences?: NotificationPreference[]\n /** Available projects for scoping rules */\n projects?: NotificationProject[]\n /** Whether data is loading */\n isLoading?: boolean\n /** Called to add or update a preference */\n onSave?: (preference: {\n projectId: string | null\n channel: string\n minTier: string\n muted: boolean\n }) => void | Promise<void>\n /** Called to toggle mute on a preference */\n onToggleMute?: (preference: NotificationPreference) => void | Promise<void>\n /** Called to update the tier on a preference */\n onUpdateTier?: (\n preference: NotificationPreference,\n newTier: string,\n ) => void | Promise<void>\n /** Called to delete a preference */\n onDelete?: (preferenceId: string) => void | Promise<void>\n /** Additional className */\n className?: string\n}\n\n// -----------------------------------------------------------------------\n// Constants\n// -----------------------------------------------------------------------\n\nconst CHANNEL_LABELS: Record<string, { label: string; icon: React.ReactNode }> = {\n IN_APP: { label: 'In-App', icon: <Icon icon={IconBell} size=\"sm\" /> },\n GOOGLE_CHAT: { label: 'Google Chat', icon: <Icon icon={IconMessage} size=\"sm\" /> },\n}\n\nconst TIER_LABELS: Record<string, string> = {\n INFO: 'All (Info+)',\n IMPORTANT: 'Important+',\n CRITICAL: 'Critical only',\n}\n\n// -----------------------------------------------------------------------\n// NotificationPreferences\n// -----------------------------------------------------------------------\n\nconst NotificationPreferences = React.forwardRef<HTMLDivElement, NotificationPreferencesProps>(\n function NotificationPreferences(\n {\n preferences = [],\n projects = [],\n isLoading = false,\n onSave,\n onToggleMute,\n onUpdateTier,\n onDelete,\n className,\n ...props\n },\n ref,\n ) {\n const [showAddDialog, setShowAddDialog] = useState(false)\n const [newProjectId, setNewProjectId] = useState<string>('global')\n const [newChannel, setNewChannel] = useState<string>('IN_APP')\n const [newMinTier, setNewMinTier] = useState<string>('INFO')\n const [newMuted, setNewMuted] = useState(false)\n const [isSaving, setIsSaving] = useState(false)\n\n const handleAdd = async () => {\n if (!onSave) return\n setIsSaving(true)\n try {\n await onSave({\n projectId: newProjectId === 'global' ? null : newProjectId,\n channel: newChannel,\n minTier: newMinTier,\n muted: newMuted,\n })\n setShowAddDialog(false)\n resetForm()\n } catch (error) {\n console.error('[Preferences] Failed to save:', error)\n } finally {\n setIsSaving(false)\n }\n }\n\n const resetForm = () => {\n setNewProjectId('global')\n setNewChannel('IN_APP')\n setNewMinTier('INFO')\n setNewMuted(false)\n }\n\n const getProjectName = (projectId: string | null) => {\n if (!projectId) return 'Global (all projects)'\n const project = projects.find((p) => p.id === projectId)\n return project?.title || 'Unknown project'\n }\n\n return (\n <>\n <Card ref={ref} className={className} {...(props as Omit<typeof props, 'color'>)}>\n <CardHeader className=\"flex flex-row items-center justify-between\">\n <CardTitle className=\"text-body-md font-semibold\">\n Notification Preferences\n </CardTitle>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => setShowAddDialog(true)}\n >\n <Icon icon={IconPlus} size=\"sm\" className=\"mr-ds-02b\" />\n Add Rule\n </Button>\n </CardHeader>\n <CardContent>\n {isLoading ? (\n <div className=\"flex items-center justify-center py-ds-07\">\n <Spinner />\n </div>\n ) : preferences.length === 0 ? (\n <div className=\"py-ds-07 text-center\">\n <p className=\"text-body-md text-surface-fg-subtle\">\n No custom preferences set. All notifications are delivered by\n default.\n </p>\n </div>\n ) : (\n <div className=\"flex flex-col\">\n {preferences.map((pref, i) => {\n const channelInfo =\n CHANNEL_LABELS[pref.channel] || CHANNEL_LABELS.IN_APP\n return (\n <div\n key={pref.id}\n className={cn(\n 'flex items-center gap-ds-05 py-ds-04',\n i < preferences.length - 1 &&\n 'border-b border-surface-border-strong',\n )}\n >\n {/* Channel icon */}\n <div className=\"flex h-ds-sm w-ds-sm shrink-0 items-center justify-center rounded-surface bg-surface-raised\">\n <span className=\"[&>svg]:h-ico-sm [&>svg]:w-ico-sm text-surface-fg-muted\" aria-hidden=\"true\">{channelInfo.icon}</span>\n </div>\n\n {/* Info */}\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-body-md font-medium text-surface-fg\">\n {channelInfo.label}\n </p>\n <p className=\"text-body-sm text-surface-fg-subtle\">\n {getProjectName(pref.projectId)}\n </p>\n </div>\n\n {/* Min tier */}\n <Select\n value={pref.minTier}\n onValueChange={(v) => onUpdateTier?.(pref, v)}\n >\n <SelectTrigger className=\"h-ds-xs-plus w-[130px] text-body-sm\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {Object.entries(TIER_LABELS).map(([key, label]) => (\n <SelectItem key={key} value={key}>\n {label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n {/* Muted toggle */}\n <div className=\"flex items-center gap-ds-02b\">\n <span className=\"text-body-sm text-surface-fg-subtle\">\n {pref.muted ? 'Muted' : 'Active'}\n </span>\n <Switch\n checked={!pref.muted}\n onCheckedChange={() => onToggleMute?.(pref)}\n />\n </div>\n\n {/* Delete */}\n <IconButton\n aria-label={`Delete ${channelInfo.label} notification rule`}\n icon={<Icon icon={IconTrash} />}\n variant=\"ghost\"\n color=\"error\"\n size=\"sm\"\n onClick={() => onDelete?.(pref.id)}\n className=\"shrink-0\"\n />\n </div>\n )\n })}\n </div>\n )}\n </CardContent>\n </Card>\n\n {/* Add Preference Dialog */}\n <Dialog open={showAddDialog} onOpenChange={setShowAddDialog}>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>Add Notification Rule</DialogTitle>\n <DialogDescription>\n Customize how you receive notifications for a specific channel\n and project.\n </DialogDescription>\n </DialogHeader>\n <div className=\"flex flex-col gap-ds-05 pt-ds-03\">\n <div className=\"flex flex-col gap-ds-02b\">\n <label htmlFor=\"pref-scope\" className=\"text-body-sm font-medium text-surface-fg-muted\">\n Scope\n </label>\n <Select value={newProjectId} onValueChange={setNewProjectId}>\n <SelectTrigger id=\"pref-scope\">\n <SelectValue placeholder=\"Select scope\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"global\">\n Global (all projects)\n </SelectItem>\n {projects.map((p) => (\n <SelectItem key={p.id} value={p.id}>\n {p.title}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"flex flex-col gap-ds-02b\">\n <label htmlFor=\"pref-channel\" className=\"text-body-sm font-medium text-surface-fg-muted\">\n Channel\n </label>\n <Select value={newChannel} onValueChange={setNewChannel}>\n <SelectTrigger id=\"pref-channel\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"IN_APP\">In-App</SelectItem>\n <SelectItem value=\"GOOGLE_CHAT\">Google Chat</SelectItem>\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"flex flex-col gap-ds-02b\">\n <label htmlFor=\"pref-min-tier\" className=\"text-body-sm font-medium text-surface-fg-muted\">\n Minimum Tier\n </label>\n <Select value={newMinTier} onValueChange={setNewMinTier}>\n <SelectTrigger id=\"pref-min-tier\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {Object.entries(TIER_LABELS).map(([key, label]) => (\n <SelectItem key={key} value={key}>\n {label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"flex items-center justify-between\">\n <label htmlFor=\"pref-muted\" className=\"text-body-md text-surface-fg\">\n Mute this channel\n </label>\n <Switch id=\"pref-muted\" checked={newMuted} onCheckedChange={setNewMuted} />\n </div>\n\n <div className=\"flex justify-end gap-ds-03 pt-ds-03\">\n <Button\n variant=\"ghost\"\n onClick={() => {\n setShowAddDialog(false)\n resetForm()\n }}\n >\n Cancel\n </Button>\n <Button\n onClick={handleAdd}\n disabled={isSaving}\n >\n {isSaving ? 'Saving...' : 'Save Rule'}\n </Button>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n </>\n )\n },\n)\n\nNotificationPreferences.displayName = 'NotificationPreferences'\n\nexport { NotificationPreferences }\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"notification-preferences.js","names":[],"sources":["../../src/shell/notification-preferences.tsx"],"sourcesContent":["'use client'\n\n/**\n * NotificationPreferences -- Manage notification rules per channel/project.\n *\n * Props-driven: accepts preferences array, projects, and callbacks.\n * No internal fetching -- the consumer is responsible for data loading.\n */\nimport { IconBell, IconMessage,IconPlus, IconTrash } from '@tabler/icons-react'\nimport * as React from 'react'\nimport { useState } from 'react'\n\nimport { Button } from '../ui/button'\nimport { Card, CardContent,CardHeader, CardTitle } from '../ui/card'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n} from '../ui/dialog'\nimport { Icon } from '../ui/icon'\nimport { IconButton } from '../ui/icon-button'\nimport { cn } from '../ui/lib/utils'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../ui/select'\nimport { Spinner } from '../ui/spinner'\nimport { Switch } from '../ui/switch'\n\n// -----------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------\n\nexport interface NotificationPreference {\n id: string\n userId?: string\n projectId: string | null\n channel: string\n minTier: string\n muted: boolean\n}\n\nexport interface NotificationProject {\n id: string\n title: string\n}\n\nexport interface NotificationPreferencesProps\n extends Omit<React.ComponentPropsWithRef<'div'>, 'onSave'> {\n /** Current preference rules */\n preferences?: NotificationPreference[]\n /** Available projects for scoping rules */\n projects?: NotificationProject[]\n /** Whether data is loading */\n isLoading?: boolean\n /** Called to add or update a preference */\n onSave?: (preference: {\n projectId: string | null\n channel: string\n minTier: string\n muted: boolean\n }) => void | Promise<void>\n /** Called to toggle mute on a preference */\n onToggleMute?: (preference: NotificationPreference) => void | Promise<void>\n /** Called to update the tier on a preference */\n onUpdateTier?: (\n preference: NotificationPreference,\n newTier: string,\n ) => void | Promise<void>\n /** Called to delete a preference */\n onDelete?: (preferenceId: string) => void | Promise<void>\n /** Additional className */\n className?: string\n}\n\n// -----------------------------------------------------------------------\n// Constants\n// -----------------------------------------------------------------------\n\nconst CHANNEL_LABELS: Record<string, { label: string; icon: React.ReactNode }> = {\n IN_APP: { label: 'In-App', icon: <Icon icon={IconBell} size=\"sm\" /> },\n GOOGLE_CHAT: { label: 'Google Chat', icon: <Icon icon={IconMessage} size=\"sm\" /> },\n}\n\nconst TIER_LABELS: Record<string, string> = {\n INFO: 'All (Info+)',\n IMPORTANT: 'Important+',\n CRITICAL: 'Critical only',\n}\n\n// -----------------------------------------------------------------------\n// NotificationPreferences\n// -----------------------------------------------------------------------\n\nconst NotificationPreferences = React.forwardRef<HTMLDivElement, NotificationPreferencesProps>(\n function NotificationPreferences(\n {\n preferences = [],\n projects = [],\n isLoading = false,\n onSave,\n onToggleMute,\n onUpdateTier,\n onDelete,\n className,\n ...props\n },\n ref,\n ) {\n const [showAddDialog, setShowAddDialog] = useState(false)\n const [newProjectId, setNewProjectId] = useState<string>('global')\n const [newChannel, setNewChannel] = useState<string>('IN_APP')\n const [newMinTier, setNewMinTier] = useState<string>('INFO')\n const [newMuted, setNewMuted] = useState(false)\n const [isSaving, setIsSaving] = useState(false)\n\n const handleAdd = async () => {\n if (!onSave) return\n setIsSaving(true)\n try {\n await onSave({\n projectId: newProjectId === 'global' ? null : newProjectId,\n channel: newChannel,\n minTier: newMinTier,\n muted: newMuted,\n })\n setShowAddDialog(false)\n resetForm()\n } catch (error) {\n console.error('[Preferences] Failed to save:', error)\n } finally {\n setIsSaving(false)\n }\n }\n\n const resetForm = () => {\n setNewProjectId('global')\n setNewChannel('IN_APP')\n setNewMinTier('INFO')\n setNewMuted(false)\n }\n\n const getProjectName = (projectId: string | null) => {\n if (!projectId) return 'Global (all projects)'\n const project = projects.find((p) => p.id === projectId)\n return project?.title || 'Unknown project'\n }\n\n return (\n <>\n <Card ref={ref} className={className} {...(props as Omit<typeof props, 'color'>)}>\n <CardHeader className=\"flex flex-row items-center justify-between\">\n <CardTitle className=\"text-body-md font-semibold\">\n Notification Preferences\n </CardTitle>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => setShowAddDialog(true)}\n >\n <Icon icon={IconPlus} size=\"sm\" className=\"mr-ds-02b\" />\n Add Rule\n </Button>\n </CardHeader>\n <CardContent>\n {isLoading ? (\n <div className=\"flex items-center justify-center py-ds-07\">\n <Spinner />\n </div>\n ) : preferences.length === 0 ? (\n <div className=\"py-ds-07 text-center\">\n <p className=\"text-body-md text-surface-fg-subtle\">\n No custom preferences set. All notifications are delivered by\n default.\n </p>\n </div>\n ) : (\n <div className=\"flex flex-col\">\n {preferences.map((pref, i) => {\n const channelInfo =\n CHANNEL_LABELS[pref.channel] || CHANNEL_LABELS.IN_APP\n return (\n <div\n key={pref.id}\n className={cn(\n 'flex items-center gap-ds-05 py-ds-04',\n i < preferences.length - 1 &&\n 'border-b border-surface-border-strong',\n )}\n >\n {/* Channel icon */}\n <div className=\"flex h-ds-sm w-ds-sm shrink-0 items-center justify-center rounded-surface bg-surface-raised\">\n <span className=\"[&>svg]:h-ico-sm [&>svg]:w-ico-sm text-surface-fg-muted\" aria-hidden=\"true\">{channelInfo.icon}</span>\n </div>\n\n {/* Info */}\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-body-md font-medium text-surface-fg\">\n {channelInfo.label}\n </p>\n <p className=\"text-body-sm text-surface-fg-subtle\">\n {getProjectName(pref.projectId)}\n </p>\n </div>\n\n {/* Min tier */}\n <Select\n value={pref.minTier}\n onValueChange={(v) => onUpdateTier?.(pref, v)}\n >\n <SelectTrigger\n aria-label={`Minimum notification tier for ${channelInfo.label} on ${getProjectName(pref.projectId)}`}\n className=\"h-ds-xs-plus w-[130px] text-body-sm\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {Object.entries(TIER_LABELS).map(([key, label]) => (\n <SelectItem key={key} value={key}>\n {label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n {/* Muted toggle */}\n <div className=\"flex items-center gap-ds-02b\">\n <span className=\"text-body-sm text-surface-fg-subtle\">\n {pref.muted ? 'Muted' : 'Active'}\n </span>\n <Switch\n aria-label={`${pref.muted ? 'Unmute' : 'Mute'} ${channelInfo.label} for ${getProjectName(pref.projectId)}`}\n checked={!pref.muted}\n onCheckedChange={() => onToggleMute?.(pref)}\n />\n </div>\n\n {/* Delete */}\n <IconButton\n aria-label={`Delete ${channelInfo.label} notification rule`}\n icon={<Icon icon={IconTrash} />}\n variant=\"ghost\"\n color=\"error\"\n size=\"sm\"\n onClick={() => onDelete?.(pref.id)}\n className=\"shrink-0\"\n />\n </div>\n )\n })}\n </div>\n )}\n </CardContent>\n </Card>\n\n {/* Add Preference Dialog */}\n <Dialog open={showAddDialog} onOpenChange={setShowAddDialog}>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>Add Notification Rule</DialogTitle>\n <DialogDescription>\n Customize how you receive notifications for a specific channel\n and project.\n </DialogDescription>\n </DialogHeader>\n <div className=\"flex flex-col gap-ds-05 pt-ds-03\">\n <div className=\"flex flex-col gap-ds-02b\">\n <label htmlFor=\"pref-scope\" className=\"text-body-sm font-medium text-surface-fg-muted\">\n Scope\n </label>\n <Select value={newProjectId} onValueChange={setNewProjectId}>\n <SelectTrigger id=\"pref-scope\">\n <SelectValue placeholder=\"Select scope\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"global\">\n Global (all projects)\n </SelectItem>\n {projects.map((p) => (\n <SelectItem key={p.id} value={p.id}>\n {p.title}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"flex flex-col gap-ds-02b\">\n <label htmlFor=\"pref-channel\" className=\"text-body-sm font-medium text-surface-fg-muted\">\n Channel\n </label>\n <Select value={newChannel} onValueChange={setNewChannel}>\n <SelectTrigger id=\"pref-channel\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"IN_APP\">In-App</SelectItem>\n <SelectItem value=\"GOOGLE_CHAT\">Google Chat</SelectItem>\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"flex flex-col gap-ds-02b\">\n <label htmlFor=\"pref-min-tier\" className=\"text-body-sm font-medium text-surface-fg-muted\">\n Minimum Tier\n </label>\n <Select value={newMinTier} onValueChange={setNewMinTier}>\n <SelectTrigger id=\"pref-min-tier\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {Object.entries(TIER_LABELS).map(([key, label]) => (\n <SelectItem key={key} value={key}>\n {label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"flex items-center justify-between\">\n <label htmlFor=\"pref-muted\" className=\"text-body-md text-surface-fg\">\n Mute this channel\n </label>\n <Switch id=\"pref-muted\" checked={newMuted} onCheckedChange={setNewMuted} />\n </div>\n\n <div className=\"flex justify-end gap-ds-03 pt-ds-03\">\n <Button\n variant=\"ghost\"\n onClick={() => {\n setShowAddDialog(false)\n resetForm()\n }}\n >\n Cancel\n </Button>\n <Button\n onClick={handleAdd}\n disabled={isSaving}\n >\n {isSaving ? 'Saving...' : 'Save Rule'}\n </Button>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n </>\n )\n },\n)\n\nNotificationPreferences.displayName = 'NotificationPreferences'\n\nexport { NotificationPreferences }\n"],"mappings":"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"}
|
|
@@ -49,7 +49,9 @@
|
|
|
49
49
|
|
|
50
50
|
/* ── Neutral — Warm grays — H:350 ── */
|
|
51
51
|
--neutral-0: #ffffff;
|
|
52
|
-
|
|
52
|
+
/* Sunken well. Chroma follows the neutral cadence (was H:360 C:0.008 — an
|
|
53
|
+
outlier that read visibly pink on sunken surfaces). */
|
|
54
|
+
--color-surface-0: oklch(0.945 0.0013 350);
|
|
53
55
|
--neutral-1: oklch(0.99 0.0003 350);
|
|
54
56
|
--neutral-2: oklch(0.97 0.0008 350);
|
|
55
57
|
--neutral-3: oklch(0.93 0.0018 350);
|
|
@@ -262,7 +264,9 @@
|
|
|
262
264
|
--purple-12: oklch(0.88 0.0316 300);
|
|
263
265
|
|
|
264
266
|
/* ── Neutral — Warm grays — H:350 ── */
|
|
265
|
-
|
|
267
|
+
/* Sunken well (deep). Chroma follows the neutral cadence — was H:360 C:0.008,
|
|
268
|
+
an outlier that read tinted rather than neutral. */
|
|
269
|
+
--color-surface-0: oklch(0.07 0.0004 350);
|
|
266
270
|
--neutral-1: oklch(0.11 0.0002 350);
|
|
267
271
|
--neutral-2: oklch(0.17 0.0007 350);
|
|
268
272
|
--neutral-3: oklch(0.23 0.0019 350);
|
|
@@ -340,3 +340,9 @@
|
|
|
340
340
|
─────────────────────────────────────────────────────────────────── */
|
|
341
341
|
|
|
342
342
|
@utility border-card { border-color: var(--color-surface-border-card); }
|
|
343
|
+
/* Stronger card edge (kbd caps, code blocks, skeleton/panel outlines, chip
|
|
344
|
+
borders) — one step up from the faint `border-card` mix. Dark-mode aware via
|
|
345
|
+
the semantic token. Was referenced in ~11 components with no matching utility
|
|
346
|
+
(dead class → border fell back to currentColor); defined here to restore the
|
|
347
|
+
intended hairline. */
|
|
348
|
+
@utility border-card-strong { border-color: var(--color-surface-border); }
|