@burdenoff/microfe-comradecircle 2026.525.1 → 2026.704.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/comradecircle/ComradeCircleRoot.js +75 -95
- package/dist/comradecircle/ComradeCircleRoot.js.map +1 -1
- package/dist/comradecircle/ComradeCircleRoutes.js +58 -37
- package/dist/comradecircle/ComradeCircleRoutes.js.map +1 -1
- package/dist/comradecircle/components/Avatar.js +47 -0
- package/dist/comradecircle/components/Avatar.js.map +1 -0
- package/dist/comradecircle/components/AvatarUploadField.js +80 -0
- package/dist/comradecircle/components/AvatarUploadField.js.map +1 -0
- package/dist/comradecircle/components/CircleCard.js +118 -0
- package/dist/comradecircle/components/CircleCard.js.map +1 -0
- package/dist/comradecircle/components/CommentThread.js +105 -0
- package/dist/comradecircle/components/CommentThread.js.map +1 -0
- package/dist/comradecircle/components/CreateCircleDialog.js +159 -0
- package/dist/comradecircle/components/CreateCircleDialog.js.map +1 -0
- package/dist/comradecircle/components/CreateEventDialog.js +210 -0
- package/dist/comradecircle/components/CreateEventDialog.js.map +1 -0
- package/dist/comradecircle/components/EmptyState.js +35 -0
- package/dist/comradecircle/components/EmptyState.js.map +1 -0
- package/dist/comradecircle/components/EventCard.js +93 -0
- package/dist/comradecircle/components/EventCard.js.map +1 -0
- package/dist/comradecircle/components/FileImage.js +20 -0
- package/dist/comradecircle/components/FileImage.js.map +1 -0
- package/dist/comradecircle/components/FilterTabs.js +27 -0
- package/dist/comradecircle/components/FilterTabs.js.map +1 -0
- package/dist/comradecircle/components/ImageAttachments.js +97 -0
- package/dist/comradecircle/components/ImageAttachments.js.map +1 -0
- package/dist/comradecircle/components/MemberIdentity.js +44 -0
- package/dist/comradecircle/components/MemberIdentity.js.map +1 -0
- package/dist/comradecircle/components/MiniBar.js +22 -0
- package/dist/comradecircle/components/MiniBar.js.map +1 -0
- package/dist/comradecircle/components/PageLayout.js +38 -0
- package/dist/comradecircle/components/PageLayout.js.map +1 -0
- package/dist/comradecircle/components/Pagination.js +50 -0
- package/dist/comradecircle/components/Pagination.js.map +1 -0
- package/dist/comradecircle/components/PollCard.js +97 -0
- package/dist/comradecircle/components/PollCard.js.map +1 -0
- package/dist/comradecircle/components/PostCard.js +163 -0
- package/dist/comradecircle/components/PostCard.js.map +1 -0
- package/dist/comradecircle/components/PostComposer.js +87 -0
- package/dist/comradecircle/components/PostComposer.js.map +1 -0
- package/dist/comradecircle/components/ReactionBar.js +122 -0
- package/dist/comradecircle/components/ReactionBar.js.map +1 -0
- package/dist/comradecircle/components/StateViews.js +49 -0
- package/dist/comradecircle/components/StateViews.js.map +1 -0
- package/dist/comradecircle/components/StatusBadge.js +68 -0
- package/dist/comradecircle/components/StatusBadge.js.map +1 -0
- package/dist/comradecircle/components/Tabs.js +31 -0
- package/dist/comradecircle/components/Tabs.js.map +1 -0
- package/dist/comradecircle/components/TagInput.js +49 -0
- package/dist/comradecircle/components/TagInput.js.map +1 -0
- package/dist/comradecircle/context/ComradeCircleContext.js +85 -0
- package/dist/comradecircle/context/ComradeCircleContext.js.map +1 -0
- package/dist/comradecircle/context/index.js +1 -0
- package/dist/comradecircle/hooks/index.js +3 -0
- package/dist/comradecircle/hooks/useComradeCircleApi.js +419 -0
- package/dist/comradecircle/hooks/useComradeCircleApi.js.map +1 -0
- package/dist/comradecircle/hooks/useCursorPager.js +25 -0
- package/dist/comradecircle/hooks/useCursorPager.js.map +1 -0
- package/dist/comradecircle/hooks/useFiles.js +78 -0
- package/dist/comradecircle/hooks/useFiles.js.map +1 -0
- package/dist/comradecircle/index.js +5 -2
- package/dist/comradecircle/lib/graphql-cache.js +9 -0
- package/dist/comradecircle/lib/graphql-cache.js.map +1 -0
- package/dist/comradecircle/lib/toast.js +39 -0
- package/dist/comradecircle/lib/toast.js.map +1 -0
- package/dist/comradecircle/pages/FeedPage.js +65 -0
- package/dist/comradecircle/pages/FeedPage.js.map +1 -0
- package/dist/comradecircle/pages/circles/CircleDetailPage.js +651 -0
- package/dist/comradecircle/pages/circles/CircleDetailPage.js.map +1 -0
- package/dist/comradecircle/pages/circles/CirclesPage.js +129 -0
- package/dist/comradecircle/pages/circles/CirclesPage.js.map +1 -0
- package/dist/comradecircle/pages/events/EventDetailPage.js +276 -0
- package/dist/comradecircle/pages/events/EventDetailPage.js.map +1 -0
- package/dist/comradecircle/pages/events/EventsPage.js +102 -0
- package/dist/comradecircle/pages/events/EventsPage.js.map +1 -0
- package/dist/comradecircle/pages/members/MemberProfilePage.js +138 -0
- package/dist/comradecircle/pages/members/MemberProfilePage.js.map +1 -0
- package/dist/comradecircle/pages/members/MembersPage.js +153 -0
- package/dist/comradecircle/pages/members/MembersPage.js.map +1 -0
- package/dist/comradecircle/pages/profile/MyProfilePage.js +266 -0
- package/dist/comradecircle/pages/profile/MyProfilePage.js.map +1 -0
- package/dist/comradecircle/utils/cn.js +10 -0
- package/dist/comradecircle/utils/cn.js.map +1 -0
- package/dist/comradecircle/utils/formatters.js +51 -0
- package/dist/comradecircle/utils/formatters.js.map +1 -0
- package/dist/comradecircle/utils/navigation.js +2 -1
- package/dist/comradecircle/utils/navigation.js.map +1 -1
- package/dist/comradecircle/utils/slug.js +8 -0
- package/dist/comradecircle/utils/slug.js.map +1 -0
- package/dist/index.js +6 -4
- package/dist/node_modules/clsx/dist/clsx.js +18 -0
- package/dist/node_modules/clsx/dist/clsx.js.map +1 -0
- package/dist/node_modules/sonner/dist/index.js +163 -0
- package/dist/node_modules/sonner/dist/index.js.map +1 -0
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +1716 -0
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
- package/package.json +3 -2
- package/dist/comradecircle/pages/CommunitiesPage.js +0 -299
- package/dist/comradecircle/pages/CommunitiesPage.js.map +0 -1
- package/dist/comradecircle/pages/CreatorPage.js +0 -399
- package/dist/comradecircle/pages/CreatorPage.js.map +0 -1
- package/dist/comradecircle/pages/DashboardPage.js +0 -265
- package/dist/comradecircle/pages/DashboardPage.js.map +0 -1
- package/dist/comradecircle/providers/ComradeCircleProvider.js +0 -27
- package/dist/comradecircle/providers/ComradeCircleProvider.js.map +0 -1
- package/dist/comradecircle/store/comradecircleStore.js +0 -345
- package/dist/comradecircle/store/comradecircleStore.js.map +0 -1
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { useComradeCircle as e } from "../context/ComradeCircleContext.js";
|
|
2
|
+
import { cn as t } from "../utils/cn.js";
|
|
3
|
+
import "../context/index.js";
|
|
4
|
+
import { useComRemovePost as n, useComUpdatePost as r } from "../hooks/useComradeCircleApi.js";
|
|
5
|
+
import "../hooks/index.js";
|
|
6
|
+
import { MemberIdentity as i } from "./MemberIdentity.js";
|
|
7
|
+
import { describeError as a, notify as o } from "../lib/toast.js";
|
|
8
|
+
import { ReactionBar as s } from "./ReactionBar.js";
|
|
9
|
+
import { formatRelativeTime as c } from "../utils/formatters.js";
|
|
10
|
+
import { CommentThread as l } from "./CommentThread.js";
|
|
11
|
+
import { AttachmentGrid as u } from "./ImageAttachments.js";
|
|
12
|
+
import { useState as d } from "react";
|
|
13
|
+
import { MessageSquare as f, MoreHorizontal as p, Pencil as m, Pin as h, Trash2 as g } from "lucide-react";
|
|
14
|
+
import { jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
15
|
+
import { Button as y, DropdownMenu as b, DropdownMenuContent as x, DropdownMenuItem as S, DropdownMenuSeparator as C, DropdownMenuTrigger as w, GlassCard as T, Textarea as E } from "@burdenoff/fe-libs/ui";
|
|
16
|
+
//#region src/comradecircle/components/PostCard.tsx
|
|
17
|
+
function D({ post: D, defaultExpanded: O = !1 }) {
|
|
18
|
+
let { currentUser: k } = e(), A = r(), j = n(), [M, N] = d(O), [P, F] = d(!1), [I, L] = d(D.body), R = !!(k?.id && k.id === D.authorId);
|
|
19
|
+
return /* @__PURE__ */ v(T, {
|
|
20
|
+
treatment: "plain",
|
|
21
|
+
className: t("p-4", D.isPinned && "ring-1 ring-accent/40"),
|
|
22
|
+
children: [
|
|
23
|
+
/* @__PURE__ */ v("div", {
|
|
24
|
+
className: "flex items-start justify-between gap-3",
|
|
25
|
+
children: [/* @__PURE__ */ _(i, {
|
|
26
|
+
memberId: D.authorId,
|
|
27
|
+
variant: "row",
|
|
28
|
+
size: "md",
|
|
29
|
+
suffix: c(D.createdAt),
|
|
30
|
+
showPresence: !0
|
|
31
|
+
}), /* @__PURE__ */ v("div", {
|
|
32
|
+
className: "flex items-center gap-1",
|
|
33
|
+
children: [D.isPinned && /* @__PURE__ */ v("span", {
|
|
34
|
+
className: "inline-flex items-center gap-1 rounded-full bg-[var(--color-accent-soft)] px-2 py-0.5 text-xs font-medium text-text-primary",
|
|
35
|
+
children: [/* @__PURE__ */ _(h, { className: "h-3 w-3" }), " Pinned"]
|
|
36
|
+
}), R && /* @__PURE__ */ v(b, { children: [/* @__PURE__ */ _(w, {
|
|
37
|
+
asChild: !0,
|
|
38
|
+
children: /* @__PURE__ */ _("button", {
|
|
39
|
+
type: "button",
|
|
40
|
+
"aria-label": "Post actions",
|
|
41
|
+
className: "rounded-lg p-1.5 text-text-tertiary transition-colors hover:bg-bg-sunken hover:text-text-primary",
|
|
42
|
+
children: /* @__PURE__ */ _(p, { className: "h-4 w-4" })
|
|
43
|
+
})
|
|
44
|
+
}), /* @__PURE__ */ v(x, {
|
|
45
|
+
align: "end",
|
|
46
|
+
children: [
|
|
47
|
+
/* @__PURE__ */ v(S, {
|
|
48
|
+
onClick: () => F(!0),
|
|
49
|
+
children: [/* @__PURE__ */ _(m, { className: "mr-2 h-4 w-4" }), " Edit"]
|
|
50
|
+
}),
|
|
51
|
+
/* @__PURE__ */ v(S, {
|
|
52
|
+
onClick: async () => {
|
|
53
|
+
try {
|
|
54
|
+
await A.mutateAsync({
|
|
55
|
+
id: D.id,
|
|
56
|
+
input: { isPinned: !D.isPinned }
|
|
57
|
+
}), o.success(D.isPinned ? "Post unpinned" : "Post pinned");
|
|
58
|
+
} catch (e) {
|
|
59
|
+
o.error("Could not update post", a(e));
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
children: [
|
|
63
|
+
/* @__PURE__ */ _(h, { className: "mr-2 h-4 w-4" }),
|
|
64
|
+
" ",
|
|
65
|
+
D.isPinned ? "Unpin" : "Pin"
|
|
66
|
+
]
|
|
67
|
+
}),
|
|
68
|
+
/* @__PURE__ */ _(C, {}),
|
|
69
|
+
/* @__PURE__ */ v(S, {
|
|
70
|
+
onClick: async () => {
|
|
71
|
+
try {
|
|
72
|
+
await j.mutateAsync({
|
|
73
|
+
id: D.id,
|
|
74
|
+
channelId: D.channelId
|
|
75
|
+
}), o.success("Post removed");
|
|
76
|
+
} catch (e) {
|
|
77
|
+
o.error("Could not remove post", a(e));
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
className: "text-status-error-text focus:text-status-error-text",
|
|
81
|
+
children: [/* @__PURE__ */ _(g, { className: "mr-2 h-4 w-4" }), " Delete"]
|
|
82
|
+
})
|
|
83
|
+
]
|
|
84
|
+
})] })]
|
|
85
|
+
})]
|
|
86
|
+
}),
|
|
87
|
+
P ? /* @__PURE__ */ v("div", {
|
|
88
|
+
className: "mt-3",
|
|
89
|
+
children: [/* @__PURE__ */ _(E, {
|
|
90
|
+
value: I,
|
|
91
|
+
onChange: (e) => L(e.target.value),
|
|
92
|
+
rows: 3,
|
|
93
|
+
className: "resize-none"
|
|
94
|
+
}), /* @__PURE__ */ v("div", {
|
|
95
|
+
className: "mt-2 flex justify-end gap-2",
|
|
96
|
+
children: [/* @__PURE__ */ _(y, {
|
|
97
|
+
variant: "outline",
|
|
98
|
+
size: "sm",
|
|
99
|
+
onClick: () => {
|
|
100
|
+
L(D.body), F(!1);
|
|
101
|
+
},
|
|
102
|
+
children: "Cancel"
|
|
103
|
+
}), /* @__PURE__ */ _(y, {
|
|
104
|
+
size: "sm",
|
|
105
|
+
onClick: async () => {
|
|
106
|
+
let e = I.trim();
|
|
107
|
+
if (!e || e === D.body) {
|
|
108
|
+
F(!1);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
await A.mutateAsync({
|
|
113
|
+
id: D.id,
|
|
114
|
+
input: { body: e }
|
|
115
|
+
}), F(!1), o.success("Post updated");
|
|
116
|
+
} catch (e) {
|
|
117
|
+
o.error("Could not update post", a(e));
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
disabled: A.isPending,
|
|
121
|
+
children: "Save"
|
|
122
|
+
})]
|
|
123
|
+
})]
|
|
124
|
+
}) : /* @__PURE__ */ _("p", {
|
|
125
|
+
className: "mt-3 whitespace-pre-wrap break-words text-sm text-text-primary",
|
|
126
|
+
children: D.body
|
|
127
|
+
}),
|
|
128
|
+
D.tags.length > 0 && /* @__PURE__ */ _("div", {
|
|
129
|
+
className: "mt-2 flex flex-wrap gap-1.5",
|
|
130
|
+
children: D.tags.map((e) => /* @__PURE__ */ v("span", {
|
|
131
|
+
className: "rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary",
|
|
132
|
+
children: ["#", e]
|
|
133
|
+
}, e))
|
|
134
|
+
}),
|
|
135
|
+
D.attachmentFileIds.length > 0 && /* @__PURE__ */ _(u, { fileIds: D.attachmentFileIds }),
|
|
136
|
+
/* @__PURE__ */ v("div", {
|
|
137
|
+
className: "mt-3 flex items-center justify-between gap-3 border-t border-border-seam pt-3",
|
|
138
|
+
children: [/* @__PURE__ */ _(s, { post: D }), /* @__PURE__ */ v("button", {
|
|
139
|
+
type: "button",
|
|
140
|
+
onClick: () => N((e) => !e),
|
|
141
|
+
"aria-expanded": M,
|
|
142
|
+
className: t("inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-xs font-medium transition-colors", M ? "bg-[var(--color-accent-soft)] text-text-primary" : "text-text-secondary hover:bg-bg-sunken hover:text-text-primary"),
|
|
143
|
+
children: [
|
|
144
|
+
/* @__PURE__ */ _(f, { className: "h-3.5 w-3.5" }),
|
|
145
|
+
/* @__PURE__ */ _("span", {
|
|
146
|
+
className: "tabular-nums",
|
|
147
|
+
children: D.commentCount
|
|
148
|
+
}),
|
|
149
|
+
/* @__PURE__ */ _("span", {
|
|
150
|
+
className: "hidden sm:inline",
|
|
151
|
+
children: D.commentCount === 1 ? "comment" : "comments"
|
|
152
|
+
})
|
|
153
|
+
]
|
|
154
|
+
})]
|
|
155
|
+
}),
|
|
156
|
+
M && /* @__PURE__ */ _(l, { postId: D.id })
|
|
157
|
+
]
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
//#endregion
|
|
161
|
+
export { D as PostCard };
|
|
162
|
+
|
|
163
|
+
//# sourceMappingURL=PostCard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PostCard.js","names":[],"sources":["../../../src/comradecircle/components/PostCard.tsx"],"sourcesContent":["/**\n * PostCard — a single post with reactions and a collapsible comment thread.\n * @module comradecircle/components\n *\n * Reads its data from a `ComPost`; writes via `comUpdatePost` (edit / pin) and\n * `comRemovePost` (delete). Reactions + comments are delegated to\n * {@link ReactionBar} and {@link CommentThread}. Author-only actions (edit, pin,\n * delete) are gated on `authorId === currentUser.id`.\n */\n\nimport { useState } from 'react';\nimport { MessageSquare, MoreHorizontal, Pin, Pencil, Trash2 } from 'lucide-react';\nimport {\n GlassCard,\n Button,\n Textarea,\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n} from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport { MemberIdentity } from './MemberIdentity';\nimport { ReactionBar } from './ReactionBar';\nimport { CommentThread } from './CommentThread';\nimport { AttachmentGrid } from './ImageAttachments';\nimport { useComUpdatePost, useComRemovePost } from '../hooks';\nimport { useComradeCircle } from '../context';\nimport { formatRelativeTime } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { ComPost } from '../types';\n\ninterface PostCardProps {\n post: ComPost;\n /** Start with the comment thread open. */\n defaultExpanded?: boolean;\n}\n\nexport function PostCard({ post, defaultExpanded = false }: PostCardProps) {\n const { currentUser } = useComradeCircle();\n const updatePost = useComUpdatePost();\n const removePost = useComRemovePost();\n const [expanded, setExpanded] = useState(defaultExpanded);\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState(post.body);\n\n const isAuthor = Boolean(currentUser?.id && currentUser.id === post.authorId);\n\n const saveEdit = async () => {\n const text = draft.trim();\n if (!text || text === post.body) {\n setEditing(false);\n return;\n }\n try {\n await updatePost.mutateAsync({ id: post.id, input: { body: text } });\n setEditing(false);\n notify.success('Post updated');\n } catch (err) {\n notify.error('Could not update post', describeError(err));\n }\n };\n\n const togglePin = async () => {\n try {\n await updatePost.mutateAsync({ id: post.id, input: { isPinned: !post.isPinned } });\n notify.success(post.isPinned ? 'Post unpinned' : 'Post pinned');\n } catch (err) {\n notify.error('Could not update post', describeError(err));\n }\n };\n\n const remove = async () => {\n try {\n await removePost.mutateAsync({ id: post.id, channelId: post.channelId });\n notify.success('Post removed');\n } catch (err) {\n notify.error('Could not remove post', describeError(err));\n }\n };\n\n return (\n <GlassCard treatment=\"plain\" className={cn('p-4', post.isPinned && 'ring-1 ring-accent/40')}>\n <div className=\"flex items-start justify-between gap-3\">\n <MemberIdentity\n memberId={post.authorId}\n variant=\"row\"\n size=\"md\"\n suffix={formatRelativeTime(post.createdAt)}\n showPresence\n />\n <div className=\"flex items-center gap-1\">\n {post.isPinned && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-[var(--color-accent-soft)] px-2 py-0.5 text-xs font-medium text-text-primary\">\n <Pin className=\"h-3 w-3\" /> Pinned\n </span>\n )}\n {isAuthor && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n aria-label=\"Post actions\"\n className=\"rounded-lg p-1.5 text-text-tertiary transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n >\n <MoreHorizontal className=\"h-4 w-4\" />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem onClick={() => setEditing(true)}>\n <Pencil className=\"mr-2 h-4 w-4\" /> Edit\n </DropdownMenuItem>\n <DropdownMenuItem onClick={togglePin}>\n <Pin className=\"mr-2 h-4 w-4\" /> {post.isPinned ? 'Unpin' : 'Pin'}\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n onClick={remove}\n className=\"text-status-error-text focus:text-status-error-text\"\n >\n <Trash2 className=\"mr-2 h-4 w-4\" /> Delete\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </div>\n </div>\n\n {editing ? (\n <div className=\"mt-3\">\n <Textarea\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n rows={3}\n className=\"resize-none\"\n />\n <div className=\"mt-2 flex justify-end gap-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => {\n setDraft(post.body);\n setEditing(false);\n }}\n >\n Cancel\n </Button>\n <Button size=\"sm\" onClick={saveEdit} disabled={updatePost.isPending}>\n Save\n </Button>\n </div>\n </div>\n ) : (\n <p className=\"mt-3 whitespace-pre-wrap break-words text-sm text-text-primary\">\n {post.body}\n </p>\n )}\n\n {post.tags.length > 0 && (\n <div className=\"mt-2 flex flex-wrap gap-1.5\">\n {post.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary\"\n >\n #{tag}\n </span>\n ))}\n </div>\n )}\n\n {post.attachmentFileIds.length > 0 && <AttachmentGrid fileIds={post.attachmentFileIds} />}\n\n <div className=\"mt-3 flex items-center justify-between gap-3 border-t border-border-seam pt-3\">\n <ReactionBar post={post} />\n <button\n type=\"button\"\n onClick={() => setExpanded((s) => !s)}\n aria-expanded={expanded}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-xs font-medium transition-colors',\n expanded\n ? 'bg-[var(--color-accent-soft)] text-text-primary'\n : 'text-text-secondary hover:bg-bg-sunken hover:text-text-primary'\n )}\n >\n <MessageSquare className=\"h-3.5 w-3.5\" />\n <span className=\"tabular-nums\">{post.commentCount}</span>\n <span className=\"hidden sm:inline\">\n {post.commentCount === 1 ? 'comment' : 'comments'}\n </span>\n </button>\n </div>\n\n {expanded && <CommentThread postId={post.id} />}\n </GlassCard>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { useComradeCircle as e } from "../context/ComradeCircleContext.js";
|
|
2
|
+
import "../context/index.js";
|
|
3
|
+
import { useComCreatePost as t } from "../hooks/useComradeCircleApi.js";
|
|
4
|
+
import "../hooks/index.js";
|
|
5
|
+
import { Avatar as n } from "./Avatar.js";
|
|
6
|
+
import { describeError as r, notify as i } from "../lib/toast.js";
|
|
7
|
+
import { AttachmentPicker as a } from "./ImageAttachments.js";
|
|
8
|
+
import { TagInput as o } from "./TagInput.js";
|
|
9
|
+
import { useState as s } from "react";
|
|
10
|
+
import { Send as c } from "lucide-react";
|
|
11
|
+
import { jsx as l, jsxs as u } from "react/jsx-runtime";
|
|
12
|
+
import { Button as d, GlassCard as f, Textarea as p } from "@burdenoff/fe-libs/ui";
|
|
13
|
+
//#region src/comradecircle/components/PostComposer.tsx
|
|
14
|
+
function m({ channelId: m, channelName: h, onPosted: g }) {
|
|
15
|
+
let { currentUser: _ } = e(), v = t(), [y, b] = s(""), [x, S] = s([]), [C, w] = s(!1), [T, E] = s([]), D = async () => {
|
|
16
|
+
let e = y.trim();
|
|
17
|
+
if (!(!e && T.length === 0)) try {
|
|
18
|
+
await v.mutateAsync({
|
|
19
|
+
channelId: m,
|
|
20
|
+
body: e,
|
|
21
|
+
tags: x.length ? x : null,
|
|
22
|
+
attachmentFileIds: T.length ? T : null
|
|
23
|
+
}), b(""), S([]), E([]), w(!1), i.success("Posted", h ? `Shared to #${h}` : void 0), g?.();
|
|
24
|
+
} catch (e) {
|
|
25
|
+
i.error("Could not publish post", r(e));
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
return /* @__PURE__ */ l(f, {
|
|
29
|
+
treatment: "plain",
|
|
30
|
+
className: "p-4",
|
|
31
|
+
children: /* @__PURE__ */ u("div", {
|
|
32
|
+
className: "flex gap-3",
|
|
33
|
+
children: [/* @__PURE__ */ l(n, {
|
|
34
|
+
name: _?.name ?? _?.email ?? "You",
|
|
35
|
+
size: "md"
|
|
36
|
+
}), /* @__PURE__ */ u("div", {
|
|
37
|
+
className: "flex-1",
|
|
38
|
+
children: [
|
|
39
|
+
/* @__PURE__ */ l(p, {
|
|
40
|
+
value: y,
|
|
41
|
+
onChange: (e) => b(e.target.value),
|
|
42
|
+
placeholder: h ? `Share something in #${h}…` : "Share something…",
|
|
43
|
+
rows: 2,
|
|
44
|
+
className: "resize-none border-0 bg-transparent px-0 shadow-none focus-visible:ring-0",
|
|
45
|
+
onKeyDown: (e) => {
|
|
46
|
+
e.key === "Enter" && (e.metaKey || e.ctrlKey) && (e.preventDefault(), D());
|
|
47
|
+
}
|
|
48
|
+
}),
|
|
49
|
+
C && /* @__PURE__ */ l("div", {
|
|
50
|
+
className: "mt-2",
|
|
51
|
+
children: /* @__PURE__ */ l(o, {
|
|
52
|
+
value: x,
|
|
53
|
+
onChange: S,
|
|
54
|
+
placeholder: "Add topics…"
|
|
55
|
+
})
|
|
56
|
+
}),
|
|
57
|
+
/* @__PURE__ */ l("div", {
|
|
58
|
+
className: "mt-2",
|
|
59
|
+
children: /* @__PURE__ */ l(a, {
|
|
60
|
+
fileIds: T,
|
|
61
|
+
onChange: E,
|
|
62
|
+
disabled: v.isPending
|
|
63
|
+
})
|
|
64
|
+
}),
|
|
65
|
+
/* @__PURE__ */ u("div", {
|
|
66
|
+
className: "mt-3 flex items-center justify-between",
|
|
67
|
+
children: [/* @__PURE__ */ l("button", {
|
|
68
|
+
type: "button",
|
|
69
|
+
onClick: () => w((e) => !e),
|
|
70
|
+
className: "text-xs font-medium text-text-secondary transition-colors hover:text-text-primary",
|
|
71
|
+
children: C ? "Hide topics" : "# Add topics"
|
|
72
|
+
}), /* @__PURE__ */ u(d, {
|
|
73
|
+
size: "sm",
|
|
74
|
+
onClick: D,
|
|
75
|
+
disabled: !y.trim() && T.length === 0 || v.isPending,
|
|
76
|
+
children: [/* @__PURE__ */ l(c, { className: "mr-1.5 h-4 w-4" }), " Post"]
|
|
77
|
+
})]
|
|
78
|
+
})
|
|
79
|
+
]
|
|
80
|
+
})]
|
|
81
|
+
})
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
85
|
+
export { m as PostComposer };
|
|
86
|
+
|
|
87
|
+
//# sourceMappingURL=PostComposer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PostComposer.js","names":[],"sources":["../../../src/comradecircle/components/PostComposer.tsx"],"sourcesContent":["/**\n * PostComposer — compose and publish a post to a channel.\n * @module comradecircle/components\n *\n * Writes `comCreatePost({ channelId, body, tags })`. Text + tags only (file\n * attachments are a future enhancement — the svc accepts attachmentFileIds but\n * file upload/preview is out of scope for v1).\n */\n\nimport { useState } from 'react';\nimport { Send } from 'lucide-react';\nimport { Button, Textarea, GlassCard } from '@burdenoff/fe-libs/ui';\nimport { TagInput } from './TagInput';\nimport { Avatar } from './Avatar';\nimport { AttachmentPicker } from './ImageAttachments';\nimport { useComCreatePost } from '../hooks';\nimport { useComradeCircle } from '../context';\nimport { notify, describeError } from '../lib/toast';\n\ninterface PostComposerProps {\n channelId: string;\n channelName?: string;\n onPosted?: () => void;\n}\n\nexport function PostComposer({ channelId, channelName, onPosted }: PostComposerProps) {\n const { currentUser } = useComradeCircle();\n const createPost = useComCreatePost();\n const [body, setBody] = useState('');\n const [tags, setTags] = useState<string[]>([]);\n const [showTags, setShowTags] = useState(false);\n const [attachmentFileIds, setAttachmentFileIds] = useState<string[]>([]);\n\n const submit = async () => {\n const text = body.trim();\n if (!text && attachmentFileIds.length === 0) return;\n try {\n await createPost.mutateAsync({\n channelId,\n body: text,\n tags: tags.length ? tags : null,\n attachmentFileIds: attachmentFileIds.length ? attachmentFileIds : null,\n });\n setBody('');\n setTags([]);\n setAttachmentFileIds([]);\n setShowTags(false);\n notify.success('Posted', channelName ? `Shared to #${channelName}` : undefined);\n onPosted?.();\n } catch (err) {\n notify.error('Could not publish post', describeError(err));\n }\n };\n\n return (\n <GlassCard treatment=\"plain\" className=\"p-4\">\n <div className=\"flex gap-3\">\n <Avatar name={currentUser?.name ?? currentUser?.email ?? 'You'} size=\"md\" />\n <div className=\"flex-1\">\n <Textarea\n value={body}\n onChange={(e) => setBody(e.target.value)}\n placeholder={channelName ? `Share something in #${channelName}…` : 'Share something…'}\n rows={2}\n className=\"resize-none border-0 bg-transparent px-0 shadow-none focus-visible:ring-0\"\n onKeyDown={(e) => {\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n void submit();\n }\n }}\n />\n {showTags && (\n <div className=\"mt-2\">\n <TagInput value={tags} onChange={setTags} placeholder=\"Add topics…\" />\n </div>\n )}\n <div className=\"mt-2\">\n <AttachmentPicker\n fileIds={attachmentFileIds}\n onChange={setAttachmentFileIds}\n disabled={createPost.isPending}\n />\n </div>\n <div className=\"mt-3 flex items-center justify-between\">\n <button\n type=\"button\"\n onClick={() => setShowTags((s) => !s)}\n className=\"text-xs font-medium text-text-secondary transition-colors hover:text-text-primary\"\n >\n {showTags ? 'Hide topics' : '# Add topics'}\n </button>\n <Button\n size=\"sm\"\n onClick={submit}\n disabled={(!body.trim() && attachmentFileIds.length === 0) || createPost.isPending}\n >\n <Send className=\"mr-1.5 h-4 w-4\" /> Post\n </Button>\n </div>\n </div>\n </div>\n </GlassCard>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAyBA,SAAgB,EAAa,EAAE,cAAW,gBAAa,eAA+B;CACpF,IAAM,EAAE,mBAAgB,GAAkB,EACpC,IAAa,GAAkB,EAC/B,CAAC,GAAM,KAAW,EAAS,GAAG,EAC9B,CAAC,GAAM,KAAW,EAAmB,EAAE,CAAC,EACxC,CAAC,GAAU,KAAe,EAAS,GAAM,EACzC,CAAC,GAAmB,KAAwB,EAAmB,EAAE,CAAC,EAElE,IAAS,YAAY;EACzB,IAAM,IAAO,EAAK,MAAM;AACpB,SAAC,KAAQ,EAAkB,WAAW,GAC1C,KAAI;AAYF,GAXA,MAAM,EAAW,YAAY;IAC3B;IACA,MAAM;IACN,MAAM,EAAK,SAAS,IAAO;IAC3B,mBAAmB,EAAkB,SAAS,IAAoB;IACnE,CAAC,EACF,EAAQ,GAAG,EACX,EAAQ,EAAE,CAAC,EACX,EAAqB,EAAE,CAAC,EACxB,EAAY,GAAM,EAClB,EAAO,QAAQ,UAAU,IAAc,cAAc,MAAgB,KAAA,EAAU,EAC/E,KAAY;WACL,GAAK;AACZ,KAAO,MAAM,0BAA0B,EAAc,EAAI,CAAC;;;AAI9D,QACE,kBAAC,GAAD;EAAW,WAAU;EAAQ,WAAU;YACrC,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD;IAAQ,MAAM,GAAa,QAAQ,GAAa,SAAS;IAAO,MAAK;IAAO,CAAA,EAC5E,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MACE,OAAO;MACP,WAAW,MAAM,EAAQ,EAAE,OAAO,MAAM;MACxC,aAAa,IAAc,uBAAuB,EAAY,KAAK;MACnE,MAAM;MACN,WAAU;MACV,YAAY,MAAM;AAChB,OAAI,EAAE,QAAQ,YAAY,EAAE,WAAW,EAAE,aACvC,EAAE,gBAAgB,EACb,GAAQ;;MAGjB,CAAA;KACD,KACC,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD;OAAU,OAAO;OAAM,UAAU;OAAS,aAAY;OAAgB,CAAA;MAClE,CAAA;KAER,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD;OACE,SAAS;OACT,UAAU;OACV,UAAU,EAAW;OACrB,CAAA;MACE,CAAA;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,GAAa,MAAM,CAAC,EAAE;OACrC,WAAU;iBAET,IAAW,gBAAgB;OACrB,CAAA,EACT,kBAAC,GAAD;OACE,MAAK;OACL,SAAS;OACT,UAAW,CAAC,EAAK,MAAM,IAAI,EAAkB,WAAW,KAAM,EAAW;iBAH3E,CAKE,kBAAC,GAAD,EAAM,WAAU,kBAAmB,CAAA,EAAA,QAC5B;SACL;;KACF;MACF;;EACI,CAAA"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { useComradeCircle as e } from "../context/ComradeCircleContext.js";
|
|
2
|
+
import { cn as t } from "../utils/cn.js";
|
|
3
|
+
import "../context/index.js";
|
|
4
|
+
import { useComReactToPost as n } from "../hooks/useComradeCircleApi.js";
|
|
5
|
+
import "../hooks/index.js";
|
|
6
|
+
import { describeError as r, notify as i } from "../lib/toast.js";
|
|
7
|
+
import { useState as a } from "react";
|
|
8
|
+
import { SmilePlus as o } from "lucide-react";
|
|
9
|
+
import { jsx as s, jsxs as c } from "react/jsx-runtime";
|
|
10
|
+
import { Popover as l, PopoverContent as u, PopoverTrigger as d } from "@burdenoff/fe-libs/ui";
|
|
11
|
+
//#region src/comradecircle/components/ReactionBar.tsx
|
|
12
|
+
var f = [
|
|
13
|
+
{
|
|
14
|
+
type: "LIKE",
|
|
15
|
+
emoji: "👍",
|
|
16
|
+
label: "Like"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
type: "LOVE",
|
|
20
|
+
emoji: "❤️",
|
|
21
|
+
label: "Love"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
type: "CELEBRATE",
|
|
25
|
+
emoji: "🎉",
|
|
26
|
+
label: "Celebrate"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
type: "INSIGHTFUL",
|
|
30
|
+
emoji: "💡",
|
|
31
|
+
label: "Insightful"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
type: "LAUGH",
|
|
35
|
+
emoji: "😄",
|
|
36
|
+
label: "Funny"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
type: "SUPPORT",
|
|
40
|
+
emoji: "🤝",
|
|
41
|
+
label: "Support"
|
|
42
|
+
}
|
|
43
|
+
];
|
|
44
|
+
function p({ post: p }) {
|
|
45
|
+
let { currentUser: m } = e(), h = n(), [g, _] = a(!1), v = m?.id, y = /* @__PURE__ */ new Map();
|
|
46
|
+
for (let e of p.reactions ?? []) {
|
|
47
|
+
let t = y.get(e.type) ?? {
|
|
48
|
+
count: 0,
|
|
49
|
+
mine: !1
|
|
50
|
+
};
|
|
51
|
+
t.count += 1, v && e.memberId === v && (t.mine = !0), y.set(e.type, t);
|
|
52
|
+
}
|
|
53
|
+
let b = async (e) => {
|
|
54
|
+
_(!1);
|
|
55
|
+
try {
|
|
56
|
+
await h.mutateAsync({
|
|
57
|
+
postId: p.id,
|
|
58
|
+
type: e
|
|
59
|
+
});
|
|
60
|
+
} catch (e) {
|
|
61
|
+
i.error("Could not react", r(e));
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
return /* @__PURE__ */ c("div", {
|
|
65
|
+
className: "flex flex-wrap items-center gap-1.5",
|
|
66
|
+
children: [f.filter((e) => (y.get(e.type)?.count ?? 0) > 0).map((e) => {
|
|
67
|
+
let n = y.get(e.type);
|
|
68
|
+
return /* @__PURE__ */ c("button", {
|
|
69
|
+
type: "button",
|
|
70
|
+
onClick: () => b(e.type),
|
|
71
|
+
disabled: h.isPending,
|
|
72
|
+
"aria-pressed": n.mine,
|
|
73
|
+
"aria-label": `${e.label} (${n.count})`,
|
|
74
|
+
className: t("inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium transition-colors", n.mine ? "border-accent bg-[var(--color-accent-soft)] text-text-primary" : "border-border-seam bg-bg-surface text-text-secondary hover:bg-bg-sunken"),
|
|
75
|
+
children: [/* @__PURE__ */ s("span", {
|
|
76
|
+
"aria-hidden": "true",
|
|
77
|
+
children: e.emoji
|
|
78
|
+
}), /* @__PURE__ */ s("span", {
|
|
79
|
+
className: "tabular-nums",
|
|
80
|
+
children: n.count
|
|
81
|
+
})]
|
|
82
|
+
}, e.type);
|
|
83
|
+
}), /* @__PURE__ */ c(l, {
|
|
84
|
+
open: g,
|
|
85
|
+
onOpenChange: _,
|
|
86
|
+
children: [/* @__PURE__ */ s(d, {
|
|
87
|
+
asChild: !0,
|
|
88
|
+
children: /* @__PURE__ */ s("button", {
|
|
89
|
+
type: "button",
|
|
90
|
+
"aria-label": "Add reaction",
|
|
91
|
+
className: "inline-flex items-center gap-1 rounded-full border border-border-seam bg-bg-surface px-2 py-0.5 text-xs text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary",
|
|
92
|
+
children: /* @__PURE__ */ s(o, { className: "h-3.5 w-3.5" })
|
|
93
|
+
})
|
|
94
|
+
}), /* @__PURE__ */ s(u, {
|
|
95
|
+
className: "w-auto p-1.5",
|
|
96
|
+
align: "start",
|
|
97
|
+
children: /* @__PURE__ */ s("div", {
|
|
98
|
+
className: "flex items-center gap-0.5",
|
|
99
|
+
children: f.map((e) => {
|
|
100
|
+
let n = y.get(e.type)?.mine ?? !1;
|
|
101
|
+
return /* @__PURE__ */ s("button", {
|
|
102
|
+
type: "button",
|
|
103
|
+
onClick: () => b(e.type),
|
|
104
|
+
disabled: h.isPending,
|
|
105
|
+
title: e.label,
|
|
106
|
+
"aria-label": e.label,
|
|
107
|
+
className: t("flex h-9 w-9 items-center justify-center rounded-lg text-lg transition-transform hover:scale-110 hover:bg-bg-sunken", n && "bg-[var(--color-accent-soft)]"),
|
|
108
|
+
children: /* @__PURE__ */ s("span", {
|
|
109
|
+
"aria-hidden": "true",
|
|
110
|
+
children: e.emoji
|
|
111
|
+
})
|
|
112
|
+
}, e.type);
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
})]
|
|
116
|
+
})]
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
//#endregion
|
|
120
|
+
export { p as ReactionBar };
|
|
121
|
+
|
|
122
|
+
//# sourceMappingURL=ReactionBar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReactionBar.js","names":[],"sources":["../../../src/comradecircle/components/ReactionBar.tsx"],"sourcesContent":["/**\n * ReactionBar — Slack-style multi-reaction control for a post.\n * @module comradecircle/components\n *\n * `comReactToPost(postId, type)` is an idempotent toggle for one typed reaction,\n * so a member may hold several distinct reactions on a post. We render a chip\n * per reaction type that has a count (highlighted when the caller reacted) plus\n * a popover to add any of the six types.\n */\n\nimport { useState } from 'react';\nimport { SmilePlus } from 'lucide-react';\nimport { Popover, PopoverContent, PopoverTrigger } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport { useComReactToPost } from '../hooks';\nimport { useComradeCircle } from '../context';\nimport { notify, describeError } from '../lib/toast';\nimport type { ComPost, ComReactionType } from '../types';\n\nconst REACTIONS: { type: ComReactionType; emoji: string; label: string }[] = [\n { type: 'LIKE', emoji: '👍', label: 'Like' },\n { type: 'LOVE', emoji: '❤️', label: 'Love' },\n { type: 'CELEBRATE', emoji: '🎉', label: 'Celebrate' },\n { type: 'INSIGHTFUL', emoji: '💡', label: 'Insightful' },\n { type: 'LAUGH', emoji: '😄', label: 'Funny' },\n { type: 'SUPPORT', emoji: '🤝', label: 'Support' },\n];\n\ninterface ReactionBarProps {\n post: ComPost;\n}\n\nexport function ReactionBar({ post }: ReactionBarProps) {\n const { currentUser } = useComradeCircle();\n const react = useComReactToPost();\n const [open, setOpen] = useState(false);\n\n const meId = currentUser?.id;\n const counts = new Map<ComReactionType, { count: number; mine: boolean }>();\n for (const r of post.reactions ?? []) {\n const cur = counts.get(r.type) ?? { count: 0, mine: false };\n cur.count += 1;\n if (meId && r.memberId === meId) cur.mine = true;\n counts.set(r.type, cur);\n }\n\n const toggle = async (type: ComReactionType) => {\n setOpen(false);\n try {\n await react.mutateAsync({ postId: post.id, type });\n } catch (err) {\n notify.error('Could not react', describeError(err));\n }\n };\n\n const active = REACTIONS.filter((r) => (counts.get(r.type)?.count ?? 0) > 0);\n\n return (\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {active.map((r) => {\n const info = counts.get(r.type)!;\n return (\n <button\n key={r.type}\n type=\"button\"\n onClick={() => toggle(r.type)}\n disabled={react.isPending}\n aria-pressed={info.mine}\n aria-label={`${r.label} (${info.count})`}\n className={cn(\n 'inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium transition-colors',\n info.mine\n ? 'border-accent bg-[var(--color-accent-soft)] text-text-primary'\n : 'border-border-seam bg-bg-surface text-text-secondary hover:bg-bg-sunken'\n )}\n >\n <span aria-hidden=\"true\">{r.emoji}</span>\n <span className=\"tabular-nums\">{info.count}</span>\n </button>\n );\n })}\n\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n aria-label=\"Add reaction\"\n className=\"inline-flex items-center gap-1 rounded-full border border-border-seam bg-bg-surface px-2 py-0.5 text-xs text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n >\n <SmilePlus className=\"h-3.5 w-3.5\" />\n </button>\n </PopoverTrigger>\n <PopoverContent className=\"w-auto p-1.5\" align=\"start\">\n <div className=\"flex items-center gap-0.5\">\n {REACTIONS.map((r) => {\n const mine = counts.get(r.type)?.mine ?? false;\n return (\n <button\n key={r.type}\n type=\"button\"\n onClick={() => toggle(r.type)}\n disabled={react.isPending}\n title={r.label}\n aria-label={r.label}\n className={cn(\n 'flex h-9 w-9 items-center justify-center rounded-lg text-lg transition-transform hover:scale-110 hover:bg-bg-sunken',\n mine && 'bg-[var(--color-accent-soft)]'\n )}\n >\n <span aria-hidden=\"true\">{r.emoji}</span>\n </button>\n );\n })}\n </div>\n </PopoverContent>\n </Popover>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;AAmBA,IAAM,IAAuE;CAC3E;EAAE,MAAM;EAAQ,OAAO;EAAM,OAAO;EAAQ;CAC5C;EAAE,MAAM;EAAQ,OAAO;EAAM,OAAO;EAAQ;CAC5C;EAAE,MAAM;EAAa,OAAO;EAAM,OAAO;EAAa;CACtD;EAAE,MAAM;EAAc,OAAO;EAAM,OAAO;EAAc;CACxD;EAAE,MAAM;EAAS,OAAO;EAAM,OAAO;EAAS;CAC9C;EAAE,MAAM;EAAW,OAAO;EAAM,OAAO;EAAW;CACnD;AAMD,SAAgB,EAAY,EAAE,WAA0B;CACtD,IAAM,EAAE,mBAAgB,GAAkB,EACpC,IAAQ,GAAmB,EAC3B,CAAC,GAAM,KAAW,EAAS,GAAM,EAEjC,IAAO,GAAa,IACpB,oBAAS,IAAI,KAAwD;AAC3E,MAAK,IAAM,KAAK,EAAK,aAAa,EAAE,EAAE;EACpC,IAAM,IAAM,EAAO,IAAI,EAAE,KAAK,IAAI;GAAE,OAAO;GAAG,MAAM;GAAO;AAG3D,EAFA,EAAI,SAAS,GACT,KAAQ,EAAE,aAAa,MAAM,EAAI,OAAO,KAC5C,EAAO,IAAI,EAAE,MAAM,EAAI;;CAGzB,IAAM,IAAS,OAAO,MAA0B;AAC9C,IAAQ,GAAM;AACd,MAAI;AACF,SAAM,EAAM,YAAY;IAAE,QAAQ,EAAK;IAAI;IAAM,CAAC;WAC3C,GAAK;AACZ,KAAO,MAAM,mBAAmB,EAAc,EAAI,CAAC;;;AAMvD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CAHa,EAAU,QAAQ,OAAO,EAAO,IAAI,EAAE,KAAK,EAAE,SAAS,KAAK,EAAE,CAIhE,KAAK,MAAM;GACjB,IAAM,IAAO,EAAO,IAAI,EAAE,KAAK;AAC/B,UACE,kBAAC,UAAD;IAEE,MAAK;IACL,eAAe,EAAO,EAAE,KAAK;IAC7B,UAAU,EAAM;IAChB,gBAAc,EAAK;IACnB,cAAY,GAAG,EAAE,MAAM,IAAI,EAAK,MAAM;IACtC,WAAW,EACT,wGACA,EAAK,OACD,kEACA,0EACL;cAZH,CAcE,kBAAC,QAAD;KAAM,eAAY;eAAQ,EAAE;KAAa,CAAA,EACzC,kBAAC,QAAD;KAAM,WAAU;eAAgB,EAAK;KAAa,CAAA,CAC3C;MAfF,EAAE,KAeA;IAEX,EAEF,kBAAC,GAAD;GAAe;GAAM,cAAc;aAAnC,CACE,kBAAC,GAAD;IAAgB,SAAA;cACd,kBAAC,UAAD;KACE,MAAK;KACL,cAAW;KACX,WAAU;eAEV,kBAAC,GAAD,EAAW,WAAU,eAAgB,CAAA;KAC9B,CAAA;IACM,CAAA,EACjB,kBAAC,GAAD;IAAgB,WAAU;IAAe,OAAM;cAC7C,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAU,KAAK,MAAM;MACpB,IAAM,IAAO,EAAO,IAAI,EAAE,KAAK,EAAE,QAAQ;AACzC,aACE,kBAAC,UAAD;OAEE,MAAK;OACL,eAAe,EAAO,EAAE,KAAK;OAC7B,UAAU,EAAM;OAChB,OAAO,EAAE;OACT,cAAY,EAAE;OACd,WAAW,EACT,uHACA,KAAQ,gCACT;iBAED,kBAAC,QAAD;QAAM,eAAY;kBAAQ,EAAE;QAAa,CAAA;OAClC,EAZF,EAAE,KAYA;OAEX;KACE,CAAA;IACS,CAAA,CACT;KACN"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { cn as e } from "../utils/cn.js";
|
|
2
|
+
import { AlertCircle as t } from "lucide-react";
|
|
3
|
+
import { jsx as n, jsxs as r } from "react/jsx-runtime";
|
|
4
|
+
import { Spinner as i } from "@burdenoff/fe-libs/ui";
|
|
5
|
+
//#region src/comradecircle/components/StateViews.tsx
|
|
6
|
+
var a = ({ label: t = "Loading…", className: a }) => /* @__PURE__ */ r("div", {
|
|
7
|
+
className: e("flex min-h-[40vh] flex-col items-center justify-center gap-3", a),
|
|
8
|
+
children: [/* @__PURE__ */ n(i, { size: "lg" }), /* @__PURE__ */ n("p", {
|
|
9
|
+
className: "text-sm text-text-secondary",
|
|
10
|
+
children: t
|
|
11
|
+
})]
|
|
12
|
+
}), o = ({ title: i = "Something went wrong", message: a, onRetry: o, className: s }) => /* @__PURE__ */ r("div", {
|
|
13
|
+
className: e("flex min-h-[40vh] flex-col items-center justify-center gap-4 text-center", s),
|
|
14
|
+
children: [
|
|
15
|
+
/* @__PURE__ */ n(t, { className: "h-10 w-10 text-status-error-text" }),
|
|
16
|
+
/* @__PURE__ */ r("div", { children: [/* @__PURE__ */ n("h2", {
|
|
17
|
+
className: "text-lg font-semibold text-text-primary",
|
|
18
|
+
children: i
|
|
19
|
+
}), a && /* @__PURE__ */ n("p", {
|
|
20
|
+
className: "mt-1 max-w-md text-sm text-text-secondary",
|
|
21
|
+
children: a
|
|
22
|
+
})] }),
|
|
23
|
+
o && /* @__PURE__ */ n("button", {
|
|
24
|
+
type: "button",
|
|
25
|
+
onClick: o,
|
|
26
|
+
className: "rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90",
|
|
27
|
+
children: "Retry"
|
|
28
|
+
})
|
|
29
|
+
]
|
|
30
|
+
}), s = ({ title: t, description: i, action: a, children: o, className: s }) => /* @__PURE__ */ r("section", {
|
|
31
|
+
className: e("mt-8 first:mt-0", s),
|
|
32
|
+
children: [/* @__PURE__ */ r("div", {
|
|
33
|
+
className: "mb-3 flex items-end justify-between gap-3",
|
|
34
|
+
children: [/* @__PURE__ */ r("div", { children: [/* @__PURE__ */ n("h2", {
|
|
35
|
+
className: "text-base font-semibold text-text-primary",
|
|
36
|
+
children: t
|
|
37
|
+
}), i && /* @__PURE__ */ n("p", {
|
|
38
|
+
className: "mt-0.5 text-sm text-text-secondary",
|
|
39
|
+
children: i
|
|
40
|
+
})] }), a && /* @__PURE__ */ n("div", {
|
|
41
|
+
className: "shrink-0",
|
|
42
|
+
children: a
|
|
43
|
+
})]
|
|
44
|
+
}), o]
|
|
45
|
+
});
|
|
46
|
+
//#endregion
|
|
47
|
+
export { o as ErrorView, a as LoadingView, s as Section };
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=StateViews.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StateViews.js","names":[],"sources":["../../../src/comradecircle/components/StateViews.tsx"],"sourcesContent":["/**\n * Shared loading / error / section state views\n * @module comradecircle/components\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { AlertCircle } from 'lucide-react';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\n\ninterface LoadingViewProps {\n label?: string;\n className?: string;\n}\n\nexport const LoadingView: FC<LoadingViewProps> = ({ label = 'Loading…', className }) => (\n <div className={cn('flex min-h-[40vh] flex-col items-center justify-center gap-3', className)}>\n <Spinner size=\"lg\" />\n <p className=\"text-sm text-text-secondary\">{label}</p>\n </div>\n);\n\ninterface ErrorViewProps {\n title?: string;\n message?: string;\n onRetry?: () => void;\n className?: string;\n}\n\nexport const ErrorView: FC<ErrorViewProps> = ({\n title = 'Something went wrong',\n message,\n onRetry,\n className,\n}) => (\n <div\n className={cn(\n 'flex min-h-[40vh] flex-col items-center justify-center gap-4 text-center',\n className\n )}\n >\n <AlertCircle className=\"h-10 w-10 text-status-error-text\" />\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">{title}</h2>\n {message && <p className=\"mt-1 max-w-md text-sm text-text-secondary\">{message}</p>}\n </div>\n {onRetry && (\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n Retry\n </button>\n )}\n </div>\n);\n\ninterface SectionProps {\n title: string;\n description?: string;\n action?: ReactNode;\n children: ReactNode;\n className?: string;\n}\n\nexport const Section: FC<SectionProps> = ({ title, description, action, children, className }) => (\n <section className={cn('mt-8 first:mt-0', className)}>\n <div className=\"mb-3 flex items-end justify-between gap-3\">\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">{title}</h2>\n {description && <p className=\"mt-0.5 text-sm text-text-secondary\">{description}</p>}\n </div>\n {action && <div className=\"shrink-0\">{action}</div>}\n </div>\n {children}\n </section>\n);\n"],"mappings":";;;;;AAeA,IAAa,KAAqC,EAAE,WAAQ,YAAY,mBACtE,kBAAC,OAAD;CAAK,WAAW,EAAG,gEAAgE,EAAU;WAA7F,CACE,kBAAC,GAAD,EAAS,MAAK,MAAO,CAAA,EACrB,kBAAC,KAAD;EAAG,WAAU;YAA+B;EAAU,CAAA,CAClD;IAUK,KAAiC,EAC5C,WAAQ,wBACR,YACA,YACA,mBAEA,kBAAC,OAAD;CACE,WAAW,EACT,4EACA,EACD;WAJH;EAME,kBAAC,GAAD,EAAa,WAAU,oCAAqC,CAAA;EAC5D,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;GAAI,WAAU;aAA2C;GAAW,CAAA,EACnE,KAAW,kBAAC,KAAD;GAAG,WAAU;aAA6C;GAAY,CAAA,CAC9E,EAAA,CAAA;EACL,KACC,kBAAC,UAAD;GACE,MAAK;GACL,SAAS;GACT,WAAU;aACX;GAEQ,CAAA;EAEP;IAWK,KAA6B,EAAE,UAAO,gBAAa,WAAQ,aAAU,mBAChF,kBAAC,WAAD;CAAS,WAAW,EAAG,mBAAmB,EAAU;WAApD,CACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;GAAI,WAAU;aAA6C;GAAW,CAAA,EACrE,KAAe,kBAAC,KAAD;GAAG,WAAU;aAAsC;GAAgB,CAAA,CAC/E,EAAA,CAAA,EACL,KAAU,kBAAC,OAAD;GAAK,WAAU;aAAY;GAAa,CAAA,CAC/C;KACL,EACO"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { cn as e } from "../utils/cn.js";
|
|
2
|
+
import { formatEnumLabel as t } from "../utils/formatters.js";
|
|
3
|
+
import { jsx as n, jsxs as r } from "react/jsx-runtime";
|
|
4
|
+
//#region src/comradecircle/components/StatusBadge.tsx
|
|
5
|
+
var i = {
|
|
6
|
+
success: "bg-status-success-bg-subtle text-status-success-text",
|
|
7
|
+
warning: "bg-status-warning-bg-subtle text-status-warning-text",
|
|
8
|
+
destructive: "bg-status-error-bg-subtle text-status-error-text",
|
|
9
|
+
secondary: "bg-bg-sunken text-text-secondary",
|
|
10
|
+
info: "bg-[var(--color-accent-soft)] text-text-primary",
|
|
11
|
+
outline: "border border-border-seam text-text-primary"
|
|
12
|
+
}, a = new Set([
|
|
13
|
+
"LIVE",
|
|
14
|
+
"ONLINE",
|
|
15
|
+
"ACTIVE"
|
|
16
|
+
]), o = {
|
|
17
|
+
ACTIVE: "success",
|
|
18
|
+
ARCHIVED: "outline",
|
|
19
|
+
SUSPENDED: "destructive",
|
|
20
|
+
PUBLIC: "success",
|
|
21
|
+
PRIVATE: "secondary",
|
|
22
|
+
SECRET: "warning",
|
|
23
|
+
CIRCLE: "info",
|
|
24
|
+
ANNOUNCEMENT: "warning",
|
|
25
|
+
FORUM: "info",
|
|
26
|
+
TEXT: "secondary",
|
|
27
|
+
VOICE: "info",
|
|
28
|
+
SCHEDULED: "info",
|
|
29
|
+
LIVE: "success",
|
|
30
|
+
COMPLETED: "secondary",
|
|
31
|
+
CANCELLED: "destructive",
|
|
32
|
+
ONLINE: "info",
|
|
33
|
+
IN_PERSON: "success",
|
|
34
|
+
HYBRID: "warning",
|
|
35
|
+
PENDING: "warning",
|
|
36
|
+
BANNED: "destructive",
|
|
37
|
+
LEFT: "outline",
|
|
38
|
+
OWNER: "info",
|
|
39
|
+
ADMIN: "warning",
|
|
40
|
+
MODERATOR: "info",
|
|
41
|
+
MEMBER: "secondary",
|
|
42
|
+
OPEN: "success",
|
|
43
|
+
CLOSED: "outline",
|
|
44
|
+
PUBLISHED: "success",
|
|
45
|
+
DRAFT: "secondary",
|
|
46
|
+
HIDDEN: "warning",
|
|
47
|
+
REMOVED: "destructive",
|
|
48
|
+
AWAY: "warning",
|
|
49
|
+
BUSY: "destructive",
|
|
50
|
+
OFFLINE: "secondary",
|
|
51
|
+
GOING: "success",
|
|
52
|
+
INTERESTED: "info",
|
|
53
|
+
WAITLISTED: "warning",
|
|
54
|
+
NOT_GOING: "outline"
|
|
55
|
+
}, s = ({ status: s, className: c }) => {
|
|
56
|
+
let l = s.toUpperCase(), u = o[l] ?? "secondary", d = a.has(l);
|
|
57
|
+
return /* @__PURE__ */ r("span", {
|
|
58
|
+
className: e("inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium", i[u], c),
|
|
59
|
+
children: [d && /* @__PURE__ */ n("span", {
|
|
60
|
+
"aria-hidden": "true",
|
|
61
|
+
className: "size-1.5 rounded-full bg-current motion-safe:animate-vc-glow-pulse"
|
|
62
|
+
}), t(s)]
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
//#endregion
|
|
66
|
+
export { s as StatusBadge };
|
|
67
|
+
|
|
68
|
+
//# sourceMappingURL=StatusBadge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StatusBadge.js","names":[],"sources":["../../../src/comradecircle/components/StatusBadge.tsx"],"sourcesContent":["/**\n * Status Badge Component\n * @module comradecircle/components\n *\n * A colored badge for ComradeCircle domain statuses (circle status/visibility,\n * event status/mode, membership status, member role, poll status, post status,\n * presence, RSVP status) with semantic color mapping. Accepts the raw server\n * enum value.\n */\n\nimport type { FC } from 'react';\nimport { cn } from '../utils/cn';\nimport { formatEnumLabel } from '../utils/formatters';\n\ninterface StatusBadgeProps {\n status: string;\n className?: string;\n}\n\ntype BadgeVariant = 'success' | 'warning' | 'destructive' | 'secondary' | 'info' | 'outline';\n\nconst variantStyles: Record<BadgeVariant, string> = {\n success: 'bg-status-success-bg-subtle text-status-success-text',\n warning: 'bg-status-warning-bg-subtle text-status-warning-text',\n destructive: 'bg-status-error-bg-subtle text-status-error-text',\n secondary: 'bg-bg-sunken text-text-secondary',\n info: 'bg-[var(--color-accent-soft)] text-text-primary',\n outline: 'border border-border-seam text-text-primary',\n};\n\n// Statuses that represent a \"live\" / active condition get a subtle opacity-only\n// glow pulse (reduced-motion safe via motion-safe:).\nconst liveStatuses = new Set(['LIVE', 'ONLINE', 'ACTIVE']);\n\nconst statusVariantMap: Record<string, BadgeVariant> = {\n // Circle status\n ACTIVE: 'success',\n ARCHIVED: 'outline',\n SUSPENDED: 'destructive',\n\n // Circle visibility\n PUBLIC: 'success',\n PRIVATE: 'secondary',\n SECRET: 'warning',\n\n // Channel visibility\n CIRCLE: 'info',\n\n // Channel type\n ANNOUNCEMENT: 'warning',\n FORUM: 'info',\n TEXT: 'secondary',\n VOICE: 'info',\n\n // Event status\n SCHEDULED: 'info',\n LIVE: 'success',\n COMPLETED: 'secondary',\n CANCELLED: 'destructive',\n\n // Event mode\n ONLINE: 'info',\n IN_PERSON: 'success',\n HYBRID: 'warning',\n\n // Membership status\n PENDING: 'warning',\n BANNED: 'destructive',\n LEFT: 'outline',\n\n // Member role\n OWNER: 'info',\n ADMIN: 'warning',\n MODERATOR: 'info',\n MEMBER: 'secondary',\n\n // Poll status\n OPEN: 'success',\n CLOSED: 'outline',\n\n // Post status\n PUBLISHED: 'success',\n DRAFT: 'secondary',\n HIDDEN: 'warning',\n REMOVED: 'destructive',\n\n // Presence\n AWAY: 'warning',\n BUSY: 'destructive',\n OFFLINE: 'secondary',\n\n // RSVP status\n GOING: 'success',\n INTERESTED: 'info',\n WAITLISTED: 'warning',\n NOT_GOING: 'outline',\n};\n\nexport const StatusBadge: FC<StatusBadgeProps> = ({ status, className }) => {\n const key = status.toUpperCase();\n const variant = statusVariantMap[key] ?? 'secondary';\n const isLive = liveStatuses.has(key);\n\n return (\n <span\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium',\n variantStyles[variant],\n className\n )}\n >\n {isLive && (\n <span\n aria-hidden=\"true\"\n className=\"size-1.5 rounded-full bg-current motion-safe:animate-vc-glow-pulse\"\n />\n )}\n {formatEnumLabel(status)}\n </span>\n );\n};\n"],"mappings":";;;;AAqBA,IAAM,IAA8C;CAClD,SAAS;CACT,SAAS;CACT,aAAa;CACb,WAAW;CACX,MAAM;CACN,SAAS;CACV,EAIK,IAAe,IAAI,IAAI;CAAC;CAAQ;CAAU;CAAS,CAAC,EAEpD,IAAiD;CAErD,QAAQ;CACR,UAAU;CACV,WAAW;CAGX,QAAQ;CACR,SAAS;CACT,QAAQ;CAGR,QAAQ;CAGR,cAAc;CACd,OAAO;CACP,MAAM;CACN,OAAO;CAGP,WAAW;CACX,MAAM;CACN,WAAW;CACX,WAAW;CAGX,QAAQ;CACR,WAAW;CACX,QAAQ;CAGR,SAAS;CACT,QAAQ;CACR,MAAM;CAGN,OAAO;CACP,OAAO;CACP,WAAW;CACX,QAAQ;CAGR,MAAM;CACN,QAAQ;CAGR,WAAW;CACX,OAAO;CACP,QAAQ;CACR,SAAS;CAGT,MAAM;CACN,MAAM;CACN,SAAS;CAGT,OAAO;CACP,YAAY;CACZ,YAAY;CACZ,WAAW;CACZ,EAEY,KAAqC,EAAE,WAAQ,mBAAgB;CAC1E,IAAM,IAAM,EAAO,aAAa,EAC1B,IAAU,EAAiB,MAAQ,aACnC,IAAS,EAAa,IAAI,EAAI;AAEpC,QACE,kBAAC,QAAD;EACE,WAAW,EACT,mFACA,EAAc,IACd,EACD;YALH,CAOG,KACC,kBAAC,QAAD;GACE,eAAY;GACZ,WAAU;GACV,CAAA,EAEH,EAAgB,EAAO,CACnB"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { cn as e } from "../utils/cn.js";
|
|
2
|
+
import { jsx as t, jsxs as n } from "react/jsx-runtime";
|
|
3
|
+
//#region src/comradecircle/components/Tabs.tsx
|
|
4
|
+
function r({ items: r, value: i, onChange: a, className: o }) {
|
|
5
|
+
return /* @__PURE__ */ t("div", {
|
|
6
|
+
role: "tablist",
|
|
7
|
+
className: e("flex flex-wrap items-center gap-1 border-b border-border-seam", o),
|
|
8
|
+
children: r.map((r) => {
|
|
9
|
+
let o = r.value === i;
|
|
10
|
+
return /* @__PURE__ */ n("button", {
|
|
11
|
+
type: "button",
|
|
12
|
+
role: "tab",
|
|
13
|
+
"aria-selected": o,
|
|
14
|
+
onClick: () => a(r.value),
|
|
15
|
+
className: e("relative -mb-px inline-flex items-center gap-2 border-b-2 px-4 py-2.5 text-sm font-medium transition-colors", o ? "border-accent text-text-primary" : "border-transparent text-text-secondary hover:text-text-primary"),
|
|
16
|
+
children: [
|
|
17
|
+
r.icon,
|
|
18
|
+
r.label,
|
|
19
|
+
typeof r.count == "number" && /* @__PURE__ */ t("span", {
|
|
20
|
+
className: e("rounded-full px-1.5 py-0.5 text-xs tabular-nums", o ? "bg-[var(--color-accent-soft)] text-text-primary" : "bg-bg-sunken text-text-secondary"),
|
|
21
|
+
children: r.count
|
|
22
|
+
})
|
|
23
|
+
]
|
|
24
|
+
}, r.value);
|
|
25
|
+
})
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { r as Tabs };
|
|
30
|
+
|
|
31
|
+
//# sourceMappingURL=Tabs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Tabs.js","names":[],"sources":["../../../src/comradecircle/components/Tabs.tsx"],"sourcesContent":["/**\n * Tabs — a top-level segmented navigation control for multi-section pages\n * (Monitoring: Sensors/Measurements/Thresholds; Carbon: Ledger/Factors/Targets).\n * @module comradecircle/components\n *\n * Visually heavier than {@link FilterTabs} (underline-style page tabs vs a pill\n * filter). Purely presentational — the parent owns the active-tab state.\n */\n\nimport type { ReactNode } from 'react';\nimport { cn } from '../utils/cn';\n\nexport interface TabItem<V extends string> {\n value: V;\n label: string;\n icon?: ReactNode;\n count?: number;\n}\n\ninterface TabsProps<V extends string> {\n items: TabItem<V>[];\n value: V;\n onChange: (value: V) => void;\n className?: string;\n}\n\nexport function Tabs<V extends string>({ items, value, onChange, className }: TabsProps<V>) {\n return (\n <div\n role=\"tablist\"\n className={cn('flex flex-wrap items-center gap-1 border-b border-border-seam', className)}\n >\n {items.map((item) => {\n const active = item.value === value;\n return (\n <button\n key={item.value}\n type=\"button\"\n role=\"tab\"\n aria-selected={active}\n onClick={() => onChange(item.value)}\n className={cn(\n 'relative -mb-px inline-flex items-center gap-2 border-b-2 px-4 py-2.5 text-sm font-medium transition-colors',\n active\n ? 'border-accent text-text-primary'\n : 'border-transparent text-text-secondary hover:text-text-primary'\n )}\n >\n {item.icon}\n {item.label}\n {typeof item.count === 'number' && (\n <span\n className={cn(\n 'rounded-full px-1.5 py-0.5 text-xs tabular-nums',\n active\n ? 'bg-[var(--color-accent-soft)] text-text-primary'\n : 'bg-bg-sunken text-text-secondary'\n )}\n >\n {item.count}\n </span>\n )}\n </button>\n );\n })}\n </div>\n );\n}\n"],"mappings":";;;AA0BA,SAAgB,EAAuB,EAAE,UAAO,UAAO,aAAU,gBAA2B;AAC1F,QACE,kBAAC,OAAD;EACE,MAAK;EACL,WAAW,EAAG,iEAAiE,EAAU;YAExF,EAAM,KAAK,MAAS;GACnB,IAAM,IAAS,EAAK,UAAU;AAC9B,UACE,kBAAC,UAAD;IAEE,MAAK;IACL,MAAK;IACL,iBAAe;IACf,eAAe,EAAS,EAAK,MAAM;IACnC,WAAW,EACT,+GACA,IACI,oCACA,iEACL;cAXH;KAaG,EAAK;KACL,EAAK;KACL,OAAO,EAAK,SAAU,YACrB,kBAAC,QAAD;MACE,WAAW,EACT,mDACA,IACI,oDACA,mCACL;gBAEA,EAAK;MACD,CAAA;KAEF;MA1BF,EAAK,MA0BH;IAEX;EACE,CAAA"}
|