@burdenoff/microfe-vibecontrols 2026.519.2 → 2026.519.4
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/components/actions/ActionForm.js +138 -134
- package/dist/components/actions/ActionForm.js.map +1 -1
- package/dist/components/docs/DocsPageEditor.js +83 -69
- package/dist/components/docs/DocsPageEditor.js.map +1 -1
- package/dist/components/notes/NotesTabPanel.js +4 -5
- package/dist/components/notes/NotesTabPanel.js.map +1 -1
- package/dist/components/shared/EntityTagPicker.js +110 -81
- package/dist/components/shared/EntityTagPicker.js.map +1 -1
- package/dist/components/shared/TagFilter.js +4 -4
- package/dist/components/shared/index.js +1 -1
- package/dist/components/tunnels/TunnelCreateWizard.js +208 -187
- package/dist/components/tunnels/TunnelCreateWizard.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckForm.js +41 -37
- package/dist/components/vibedeck/VibeDeckForm.js.map +1 -1
- package/dist/pages/ActionsPage.js +225 -217
- package/dist/pages/ActionsPage.js.map +1 -1
- package/dist/pages/AgentDetailsPage.js +13 -13
- package/dist/pages/AgentDetailsPage.js.map +1 -1
- package/dist/pages/CatalogDetailPage.js +248 -234
- package/dist/pages/CatalogDetailPage.js.map +1 -1
- package/dist/pages/CatalogDocsPage.js +119 -105
- package/dist/pages/CatalogDocsPage.js.map +1 -1
- package/dist/pages/ComponentDetailPage.js +257 -243
- package/dist/pages/ComponentDetailPage.js.map +1 -1
- package/dist/pages/ComponentDocsPage.js +119 -105
- package/dist/pages/ComponentDocsPage.js.map +1 -1
- package/dist/pages/SessionDetailsPage.js +4 -4
- package/dist/pages/TargetDetailsPage.js +136 -119
- package/dist/pages/TargetDetailsPage.js.map +1 -1
- package/dist/pages/TemplateDetailPage.js +47 -33
- package/dist/pages/TemplateDetailPage.js.map +1 -1
- package/dist/pages/VibeDeckPage.js +161 -153
- package/dist/pages/VibeDeckPage.js.map +1 -1
- package/dist/pages/VibeDetailsPage.js +407 -407
- package/dist/pages/VibeDetailsPage.js.map +1 -1
- package/dist/pages/VibeDocsPage.js +119 -105
- package/dist/pages/VibeDocsPage.js.map +1 -1
- package/dist/pages/tunnels/TunnelDetailsPage.js +95 -77
- package/dist/pages/tunnels/TunnelDetailsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -5,83 +5,115 @@ import { Loader2 as d, Plus as f, Search as p, Tag as m, X as h } from "lucide-r
|
|
|
5
5
|
import { jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
6
6
|
//#region src/components/shared/EntityTagPicker.tsx
|
|
7
7
|
var v = 4e3, y = 250;
|
|
8
|
-
function b({ entityType: b, entityId: x,
|
|
9
|
-
let
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
function b({ entityType: b, entityId: x, onDraftChange: S, readOnly: C = !1 }) {
|
|
9
|
+
let w = e(), T = !x, [E, D] = u(!1), [O, k] = u(""), [A, j] = u(null), [M, N] = u([]), { data: P, loading: F, refetch: I } = n(b, x ?? ""), { data: L, loading: R } = t(50, { skip: !E }), [z, { data: B, loading: V }] = r(), [H, { loading: U }] = i(), [W, { loading: G }] = a(), K = c(() => P?.vibecontrolsEntityTags ?? [], [P]), q = c(() => T ? M.map((e) => ({
|
|
10
|
+
key: e.id,
|
|
11
|
+
tagId: e.id,
|
|
12
|
+
label: e.label
|
|
13
|
+
})) : K.map((e) => ({
|
|
14
|
+
key: e.id,
|
|
15
|
+
tagId: e.tagId,
|
|
16
|
+
label: e.tag?.label ?? w("vibecontrols.shared.entityTagPicker.untitledTag", "Untitled tag")
|
|
17
|
+
})), [
|
|
18
|
+
T,
|
|
19
|
+
M,
|
|
20
|
+
K,
|
|
21
|
+
w
|
|
22
|
+
]), J = c(() => new Set(q.map((e) => e.tagId)), [q]), Y = c(() => ((O ? B?.searchVibecontrolsTags : L?.availableVibecontrolsTags) ?? []).filter((e) => !J.has(e.id)), [
|
|
23
|
+
O,
|
|
24
|
+
B,
|
|
25
|
+
L,
|
|
26
|
+
J
|
|
14
27
|
]);
|
|
15
28
|
s(() => {
|
|
16
|
-
if (!
|
|
17
|
-
let e = setTimeout(() =>
|
|
29
|
+
if (!A) return;
|
|
30
|
+
let e = setTimeout(() => j(null), v);
|
|
18
31
|
return () => clearTimeout(e);
|
|
19
|
-
}, [
|
|
20
|
-
let
|
|
32
|
+
}, [A]);
|
|
33
|
+
let X = l(null);
|
|
21
34
|
s(() => () => {
|
|
22
|
-
|
|
35
|
+
X.current && clearTimeout(X.current);
|
|
23
36
|
}, []);
|
|
24
|
-
let
|
|
25
|
-
|
|
26
|
-
|
|
37
|
+
let Z = o((e) => {
|
|
38
|
+
k(e), X.current && clearTimeout(X.current), !(e.length < 2) && (X.current = setTimeout(() => {
|
|
39
|
+
z({ variables: {
|
|
27
40
|
query: e,
|
|
28
41
|
limit: 10
|
|
29
42
|
} });
|
|
30
43
|
}, y));
|
|
31
|
-
}, [
|
|
44
|
+
}, [z]), Q = o(async (e) => {
|
|
45
|
+
if (T) {
|
|
46
|
+
N((t) => {
|
|
47
|
+
if (t.some((t) => t.id === e.id)) return t;
|
|
48
|
+
let n = [...t, e];
|
|
49
|
+
return S?.(n.map((e) => e.id)), n;
|
|
50
|
+
}), k(""), j(null);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
32
53
|
try {
|
|
33
|
-
await
|
|
54
|
+
await H({ variables: { input: {
|
|
34
55
|
entityType: b,
|
|
35
|
-
entityId: x,
|
|
36
|
-
tagId: e
|
|
37
|
-
} } }), await
|
|
56
|
+
entityId: x ?? "",
|
|
57
|
+
tagId: e.id
|
|
58
|
+
} } }), await I(), k(""), j(null);
|
|
38
59
|
} catch (e) {
|
|
39
|
-
|
|
60
|
+
j(e instanceof Error ? e.message : w("vibecontrols.shared.entityTagPicker.failedToAddTag", "Failed to add tag"));
|
|
40
61
|
}
|
|
41
62
|
}, [
|
|
42
|
-
|
|
63
|
+
T,
|
|
64
|
+
S,
|
|
65
|
+
H,
|
|
43
66
|
b,
|
|
44
67
|
x,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
]),
|
|
68
|
+
I,
|
|
69
|
+
w
|
|
70
|
+
]), $ = o(async (e) => {
|
|
71
|
+
if (T) {
|
|
72
|
+
N((t) => {
|
|
73
|
+
let n = t.filter((t) => t.id !== e);
|
|
74
|
+
return S?.(n.map((e) => e.id)), n;
|
|
75
|
+
}), j(null);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
48
78
|
try {
|
|
49
|
-
await
|
|
79
|
+
await W({ variables: { input: {
|
|
50
80
|
entityType: b,
|
|
51
|
-
entityId: x,
|
|
81
|
+
entityId: x ?? "",
|
|
52
82
|
tagId: e
|
|
53
|
-
} } }), await
|
|
83
|
+
} } }), await I(), j(null);
|
|
54
84
|
} catch (e) {
|
|
55
|
-
|
|
85
|
+
j(e instanceof Error ? e.message : w("vibecontrols.shared.entityTagPicker.failedToRemoveTag", "Failed to remove tag"));
|
|
56
86
|
}
|
|
57
87
|
}, [
|
|
58
|
-
|
|
88
|
+
T,
|
|
89
|
+
S,
|
|
90
|
+
W,
|
|
59
91
|
b,
|
|
60
92
|
x,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
])
|
|
64
|
-
return
|
|
93
|
+
I,
|
|
94
|
+
w
|
|
95
|
+
]);
|
|
96
|
+
return F && !T ? /* @__PURE__ */ _("div", {
|
|
65
97
|
className: "flex items-center gap-2 text-text-secondary text-sm",
|
|
66
98
|
role: "status",
|
|
67
99
|
"aria-live": "polite",
|
|
68
100
|
children: [/* @__PURE__ */ g(d, {
|
|
69
101
|
className: "h-4 w-4 animate-spin",
|
|
70
102
|
"aria-hidden": "true"
|
|
71
|
-
}),
|
|
103
|
+
}), w("vibecontrols.shared.entityTagPicker.loadingTags", "Loading tags...")]
|
|
72
104
|
}) : /* @__PURE__ */ _("div", {
|
|
73
105
|
className: "space-y-3",
|
|
74
106
|
children: [
|
|
75
|
-
|
|
107
|
+
A && /* @__PURE__ */ _("div", {
|
|
76
108
|
role: "alert",
|
|
77
109
|
className: "flex items-start gap-2 rounded-md border border-red-300 bg-red-50 px-3 py-2 text-sm text-red-700",
|
|
78
110
|
children: [/* @__PURE__ */ g("span", {
|
|
79
111
|
className: "flex-1",
|
|
80
|
-
children:
|
|
112
|
+
children: A
|
|
81
113
|
}), /* @__PURE__ */ g("button", {
|
|
82
114
|
type: "button",
|
|
83
|
-
onClick: () =>
|
|
84
|
-
"aria-label":
|
|
115
|
+
onClick: () => j(null),
|
|
116
|
+
"aria-label": w("vibecontrols.shared.entityTagPicker.dismissError", "Dismiss error"),
|
|
85
117
|
className: "text-red-700 hover:text-red-900",
|
|
86
118
|
children: /* @__PURE__ */ g(h, {
|
|
87
119
|
className: "h-3.5 w-3.5",
|
|
@@ -91,49 +123,46 @@ function b({ entityType: b, entityId: x, readOnly: S = !1 }) {
|
|
|
91
123
|
}),
|
|
92
124
|
/* @__PURE__ */ _("div", {
|
|
93
125
|
className: "flex flex-wrap gap-2",
|
|
94
|
-
"aria-label":
|
|
126
|
+
"aria-label": w("vibecontrols.shared.entityTagPicker.assignedTags", "Assigned tags"),
|
|
95
127
|
children: [
|
|
96
|
-
|
|
128
|
+
q.length === 0 && !E && /* @__PURE__ */ g("p", {
|
|
97
129
|
className: "text-text-secondary text-sm",
|
|
98
|
-
children:
|
|
130
|
+
children: w("vibecontrols.shared.entityTagPicker.noTagsAssigned", "No tags assigned")
|
|
99
131
|
}),
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
132
|
+
q.map((e) => /* @__PURE__ */ _("span", {
|
|
133
|
+
className: "inline-flex items-center gap-1 px-3 py-1 bg-action-primary-bg/10 text-action-primary-bg text-sm rounded-full",
|
|
134
|
+
children: [
|
|
135
|
+
/* @__PURE__ */ g(m, {
|
|
136
|
+
className: "h-3 w-3",
|
|
137
|
+
"aria-hidden": "true"
|
|
138
|
+
}),
|
|
139
|
+
e.label,
|
|
140
|
+
!C && /* @__PURE__ */ g("button", {
|
|
141
|
+
type: "button",
|
|
142
|
+
onClick: () => $(e.tagId),
|
|
143
|
+
disabled: G,
|
|
144
|
+
"aria-label": `Remove tag ${e.label}`,
|
|
145
|
+
className: "ml-1 hover:text-red-500 transition-colors disabled:opacity-50",
|
|
146
|
+
children: /* @__PURE__ */ g(h, {
|
|
106
147
|
className: "h-3 w-3",
|
|
107
148
|
"aria-hidden": "true"
|
|
108
|
-
}),
|
|
109
|
-
t,
|
|
110
|
-
!S && /* @__PURE__ */ g("button", {
|
|
111
|
-
type: "button",
|
|
112
|
-
onClick: () => J(e.tagId),
|
|
113
|
-
disabled: V,
|
|
114
|
-
"aria-label": `Remove tag ${t}`,
|
|
115
|
-
className: "ml-1 hover:text-red-500 transition-colors disabled:opacity-50",
|
|
116
|
-
children: /* @__PURE__ */ g(h, {
|
|
117
|
-
className: "h-3 w-3",
|
|
118
|
-
"aria-hidden": "true"
|
|
119
|
-
})
|
|
120
149
|
})
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}),
|
|
124
|
-
!
|
|
150
|
+
})
|
|
151
|
+
]
|
|
152
|
+
}, e.key)),
|
|
153
|
+
!C && !E && /* @__PURE__ */ _("button", {
|
|
125
154
|
type: "button",
|
|
126
|
-
onClick: () =>
|
|
127
|
-
"aria-label":
|
|
155
|
+
onClick: () => D(!0),
|
|
156
|
+
"aria-label": w("vibecontrols.shared.entityTagPicker.addTag", "Add tag"),
|
|
128
157
|
className: "inline-flex items-center gap-1 px-3 py-1 border border-dashed border-border-default text-text-secondary text-sm rounded-full hover:border-action-primary-bg hover:text-action-primary-bg transition-colors",
|
|
129
158
|
children: [/* @__PURE__ */ g(f, {
|
|
130
159
|
className: "h-3 w-3",
|
|
131
160
|
"aria-hidden": "true"
|
|
132
|
-
}),
|
|
161
|
+
}), w("vibecontrols.shared.entityTagPicker.addTag", "Add Tag")]
|
|
133
162
|
})
|
|
134
163
|
]
|
|
135
164
|
}),
|
|
136
|
-
|
|
165
|
+
E && /* @__PURE__ */ _("div", {
|
|
137
166
|
className: "border border-border-default rounded-lg p-3 space-y-2 bg-bg-surface",
|
|
138
167
|
children: [
|
|
139
168
|
/* @__PURE__ */ _("div", {
|
|
@@ -143,30 +172,30 @@ function b({ entityType: b, entityId: x, readOnly: S = !1 }) {
|
|
|
143
172
|
"aria-hidden": "true"
|
|
144
173
|
}), /* @__PURE__ */ g("input", {
|
|
145
174
|
type: "text",
|
|
146
|
-
value:
|
|
147
|
-
onChange: (e) =>
|
|
148
|
-
placeholder:
|
|
175
|
+
value: O,
|
|
176
|
+
onChange: (e) => Z(e.target.value),
|
|
177
|
+
placeholder: w("vibecontrols.shared.entityTagPicker.searchPlaceholder", "Search tags..."),
|
|
149
178
|
autoFocus: !0,
|
|
150
|
-
"aria-label":
|
|
179
|
+
"aria-label": w("vibecontrols.shared.entityTagPicker.searchAvailableTags", "Search available tags"),
|
|
151
180
|
className: "w-full pl-9 pr-3 py-2 text-sm bg-bg-default border border-border-default rounded-md focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 text-text-primary placeholder:text-text-secondary"
|
|
152
181
|
})]
|
|
153
182
|
}),
|
|
154
183
|
/* @__PURE__ */ _("div", {
|
|
155
184
|
className: "flex flex-wrap gap-2 max-h-32 overflow-y-auto",
|
|
156
|
-
"aria-label":
|
|
185
|
+
"aria-label": w("vibecontrols.shared.entityTagPicker.tagSuggestions", "Tag suggestions"),
|
|
157
186
|
children: [
|
|
158
|
-
(
|
|
187
|
+
(V || R && !O) && /* @__PURE__ */ g("span", {
|
|
159
188
|
className: "text-text-secondary text-xs",
|
|
160
|
-
children:
|
|
189
|
+
children: w("vibecontrols.shared.entityTagPicker.loading", "Loading...")
|
|
161
190
|
}),
|
|
162
|
-
!
|
|
191
|
+
!V && !R && Y.length === 0 && /* @__PURE__ */ g("span", {
|
|
163
192
|
className: "text-text-secondary text-xs",
|
|
164
|
-
children:
|
|
193
|
+
children: O ? w("vibecontrols.shared.entityTagPicker.noMatchingTags", "No matching tags") : w("vibecontrols.shared.entityTagPicker.noAvailableTags", "No available tags")
|
|
165
194
|
}),
|
|
166
|
-
|
|
195
|
+
Y.map((e) => /* @__PURE__ */ _("button", {
|
|
167
196
|
type: "button",
|
|
168
|
-
onClick: () =>
|
|
169
|
-
disabled:
|
|
197
|
+
onClick: () => Q(e),
|
|
198
|
+
disabled: U,
|
|
170
199
|
"aria-label": `Add tag ${e.label}`,
|
|
171
200
|
className: "inline-flex items-center gap-1 px-3 py-1 bg-bg-sunken text-text-primary text-sm rounded-full hover:bg-action-primary-bg/10 hover:text-action-primary-bg transition-colors cursor-pointer disabled:opacity-50",
|
|
172
201
|
children: [/* @__PURE__ */ g(f, {
|
|
@@ -179,10 +208,10 @@ function b({ entityType: b, entityId: x, readOnly: S = !1 }) {
|
|
|
179
208
|
/* @__PURE__ */ g("button", {
|
|
180
209
|
type: "button",
|
|
181
210
|
onClick: () => {
|
|
182
|
-
|
|
211
|
+
D(!1), k("");
|
|
183
212
|
},
|
|
184
213
|
className: "text-xs text-text-secondary hover:text-text-primary",
|
|
185
|
-
children:
|
|
214
|
+
children: w("vibecontrols.shared.entityTagPicker.done", "Done")
|
|
186
215
|
})
|
|
187
216
|
]
|
|
188
217
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntityTagPicker.js","names":[],"sources":["../../../src/components/shared/EntityTagPicker.tsx"],"sourcesContent":["/**\n * EntityTagPicker — Reusable tag picker for any VibeControls entity\n *\n * Usage:\n * <EntityTagPicker entityType=\"VIBECONTROLS_VIBE\" entityId={vibe.id} />\n */\nimport { useState, useCallback, useMemo, useEffect, useRef } from 'react';\nimport { Tag, Plus, X, Search, Loader2 } from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n useGetEntityTags,\n useAvailableTags,\n useSearchTags,\n useTagEntity,\n useUntagEntity,\n} from '../../hooks/useEntityTags';\nimport type { AvailableTag, EntityTagAssignment } from '../../hooks/useEntityTags';\n\ninterface EntityTagPickerProps {\n entityType: string;\n entityId: string;\n readOnly?: boolean;\n}\n\nconst ERROR_DISMISS_MS = 4000;\nconst SEARCH_DEBOUNCE_MS = 250;\n\nexport function EntityTagPicker({ entityType, entityId, readOnly = false }: EntityTagPickerProps) {\n const tr = useTr();\n const [isAdding, setIsAdding] = useState(false);\n const [searchQuery, setSearchQuery] = useState('');\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\n\n const {\n data: tagsData,\n loading: tagsLoading,\n refetch: refetchTags,\n } = useGetEntityTags(entityType, entityId);\n const { data: availableData, loading: availableLoading } = useAvailableTags(50, {\n skip: !isAdding,\n });\n const [searchTags, { data: searchData, loading: searchLoading }] = useSearchTags();\n const [tagEntity, { loading: tagging }] = useTagEntity();\n const [untagEntity, { loading: untagging }] = useUntagEntity();\n\n // Apollo Client v4 returns DeepPartialObject from useQuery data, but our\n // hook types declare full shapes — we cast back to the declared types since\n // the resolver is guaranteed to return non-null fields per the schema.\n const assignedTags = (tagsData?.vibecontrolsEntityTags ?? []) as EntityTagAssignment[];\n const assignedTagIds = useMemo(() => new Set(assignedTags.map((t) => t.tagId)), [assignedTags]);\n\n const suggestions: AvailableTag[] = useMemo(() => {\n const source = searchQuery\n ? (searchData?.searchVibecontrolsTags as AvailableTag[] | undefined)\n : (availableData?.availableVibecontrolsTags as AvailableTag[] | undefined);\n return (source ?? []).filter((t) => !assignedTagIds.has(t.id));\n }, [searchQuery, searchData, availableData, assignedTagIds]);\n\n // Auto-dismiss error toast after a few seconds\n useEffect(() => {\n if (!errorMessage) return;\n const timer = setTimeout(() => setErrorMessage(null), ERROR_DISMISS_MS);\n return () => clearTimeout(timer);\n }, [errorMessage]);\n\n // Debounce the search query so we don't fire a request on every keystroke.\n const searchTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n useEffect(() => {\n return () => {\n if (searchTimerRef.current) clearTimeout(searchTimerRef.current);\n };\n }, []);\n\n const handleSearch = useCallback(\n (query: string) => {\n setSearchQuery(query);\n if (searchTimerRef.current) clearTimeout(searchTimerRef.current);\n if (query.length < 2) return;\n searchTimerRef.current = setTimeout(() => {\n searchTags({ variables: { query, limit: 10 } });\n }, SEARCH_DEBOUNCE_MS);\n },\n [searchTags]\n );\n\n const handleAddTag = useCallback(\n async (tagId: string) => {\n try {\n await tagEntity({ variables: { input: { entityType, entityId, tagId } } });\n await refetchTags();\n setSearchQuery('');\n setErrorMessage(null);\n } catch (err) {\n setErrorMessage(\n err instanceof Error\n ? err.message\n : tr('vibecontrols.shared.entityTagPicker.failedToAddTag', 'Failed to add tag')\n );\n }\n },\n [tagEntity, entityType, entityId, refetchTags, tr]\n );\n\n const handleRemoveTag = useCallback(\n async (tagId: string) => {\n try {\n await untagEntity({ variables: { input: { entityType, entityId, tagId } } });\n await refetchTags();\n setErrorMessage(null);\n } catch (err) {\n setErrorMessage(\n err instanceof Error\n ? err.message\n : tr('vibecontrols.shared.entityTagPicker.failedToRemoveTag', 'Failed to remove tag')\n );\n }\n },\n [untagEntity, entityType, entityId, refetchTags, tr]\n );\n\n // The assignment fragment always carries `tag.label` (see\n // EntityTagPickerAssignmentFields in useEntityTags.ts), so we read directly\n // from it. Falling back to `availableData` here is dead code in display\n // mode because availableData is only fetched when `isAdding=true`.\n const getTagLabel = useCallback(\n (assignment: EntityTagAssignment): string =>\n assignment.tag?.label ??\n tr('vibecontrols.shared.entityTagPicker.untitledTag', 'Untitled tag'),\n [tr]\n );\n\n if (tagsLoading) {\n return (\n <div\n className=\"flex items-center gap-2 text-text-secondary text-sm\"\n role=\"status\"\n aria-live=\"polite\"\n >\n <Loader2 className=\"h-4 w-4 animate-spin\" aria-hidden=\"true\" />\n {tr('vibecontrols.shared.entityTagPicker.loadingTags', 'Loading tags...')}\n </div>\n );\n }\n\n return (\n <div className=\"space-y-3\">\n {errorMessage && (\n <div\n role=\"alert\"\n className=\"flex items-start gap-2 rounded-md border border-red-300 bg-red-50 px-3 py-2 text-sm text-red-700\"\n >\n <span className=\"flex-1\">{errorMessage}</span>\n <button\n type=\"button\"\n onClick={() => setErrorMessage(null)}\n aria-label={tr('vibecontrols.shared.entityTagPicker.dismissError', 'Dismiss error')}\n className=\"text-red-700 hover:text-red-900\"\n >\n <X className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n </button>\n </div>\n )}\n\n <div\n className=\"flex flex-wrap gap-2\"\n aria-label={tr('vibecontrols.shared.entityTagPicker.assignedTags', 'Assigned tags')}\n >\n {assignedTags.length === 0 && !isAdding && (\n <p className=\"text-text-secondary text-sm\">\n {tr('vibecontrols.shared.entityTagPicker.noTagsAssigned', 'No tags assigned')}\n </p>\n )}\n {assignedTags.map((assignment) => {\n const label = getTagLabel(assignment);\n return (\n <span\n key={assignment.id}\n className=\"inline-flex items-center gap-1 px-3 py-1 bg-action-primary-bg/10 text-action-primary-bg text-sm rounded-full\"\n >\n <Tag className=\"h-3 w-3\" aria-hidden=\"true\" />\n {label}\n {!readOnly && (\n <button\n type=\"button\"\n onClick={() => handleRemoveTag(assignment.tagId)}\n disabled={untagging}\n aria-label={`Remove tag ${label}`}\n className=\"ml-1 hover:text-red-500 transition-colors disabled:opacity-50\"\n >\n <X className=\"h-3 w-3\" aria-hidden=\"true\" />\n </button>\n )}\n </span>\n );\n })}\n {!readOnly && !isAdding && (\n <button\n type=\"button\"\n onClick={() => setIsAdding(true)}\n aria-label={tr('vibecontrols.shared.entityTagPicker.addTag', 'Add tag')}\n className=\"inline-flex items-center gap-1 px-3 py-1 border border-dashed border-border-default text-text-secondary text-sm rounded-full hover:border-action-primary-bg hover:text-action-primary-bg transition-colors\"\n >\n <Plus className=\"h-3 w-3\" aria-hidden=\"true\" />\n {tr('vibecontrols.shared.entityTagPicker.addTag', 'Add Tag')}\n </button>\n )}\n </div>\n\n {isAdding && (\n <div className=\"border border-border-default rounded-lg p-3 space-y-2 bg-bg-surface\">\n <div className=\"relative\">\n <Search\n className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-text-secondary\"\n aria-hidden=\"true\"\n />\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => handleSearch(e.target.value)}\n placeholder={tr(\n 'vibecontrols.shared.entityTagPicker.searchPlaceholder',\n 'Search tags...'\n )}\n autoFocus\n aria-label={tr(\n 'vibecontrols.shared.entityTagPicker.searchAvailableTags',\n 'Search available tags'\n )}\n className=\"w-full pl-9 pr-3 py-2 text-sm bg-bg-default border border-border-default rounded-md focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 text-text-primary placeholder:text-text-secondary\"\n />\n </div>\n <div\n className=\"flex flex-wrap gap-2 max-h-32 overflow-y-auto\"\n aria-label={tr('vibecontrols.shared.entityTagPicker.tagSuggestions', 'Tag suggestions')}\n >\n {(searchLoading || (availableLoading && !searchQuery)) && (\n <span className=\"text-text-secondary text-xs\">\n {tr('vibecontrols.shared.entityTagPicker.loading', 'Loading...')}\n </span>\n )}\n {!searchLoading && !availableLoading && suggestions.length === 0 && (\n <span className=\"text-text-secondary text-xs\">\n {searchQuery\n ? tr('vibecontrols.shared.entityTagPicker.noMatchingTags', 'No matching tags')\n : tr('vibecontrols.shared.entityTagPicker.noAvailableTags', 'No available tags')}\n </span>\n )}\n {suggestions.map((tag) => (\n <button\n key={tag.id}\n type=\"button\"\n onClick={() => handleAddTag(tag.id)}\n disabled={tagging}\n aria-label={`Add tag ${tag.label}`}\n className=\"inline-flex items-center gap-1 px-3 py-1 bg-bg-sunken text-text-primary text-sm rounded-full hover:bg-action-primary-bg/10 hover:text-action-primary-bg transition-colors cursor-pointer disabled:opacity-50\"\n >\n <Plus className=\"h-3 w-3\" aria-hidden=\"true\" />\n {tag.label}\n </button>\n ))}\n </div>\n <button\n type=\"button\"\n onClick={() => {\n setIsAdding(false);\n setSearchQuery('');\n }}\n className=\"text-xs text-text-secondary hover:text-text-primary\"\n >\n {tr('vibecontrols.shared.entityTagPicker.done', 'Done')}\n </button>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;AAwBA,IAAM,IAAmB,KACnB,IAAqB;AAE3B,SAAgB,EAAgB,EAAE,eAAY,aAAU,cAAW,MAA+B;CAChG,IAAM,IAAK,GAAO,EACZ,CAAC,GAAU,KAAe,EAAS,GAAM,EACzC,CAAC,GAAa,KAAkB,EAAS,GAAG,EAC5C,CAAC,GAAc,KAAmB,EAAwB,KAAK,EAE/D,EACJ,MAAM,GACN,SAAS,GACT,SAAS,MACP,EAAiB,GAAY,EAAS,EACpC,EAAE,MAAM,GAAe,SAAS,MAAqB,EAAiB,IAAI,EAC9E,MAAM,CAAC,GACR,CAAC,EACI,CAAC,GAAY,EAAE,MAAM,GAAY,SAAS,OAAmB,GAAe,EAC5E,CAAC,GAAW,EAAE,SAAS,OAAa,GAAc,EAClD,CAAC,GAAa,EAAE,SAAS,OAAe,GAAgB,EAKxD,IAAgB,GAAU,0BAA0B,EAAE,EACtD,IAAiB,QAAc,IAAI,IAAI,EAAa,KAAK,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,EAAa,CAAC,EAEzF,IAA8B,UACnB,IACV,GAAY,yBACZ,GAAe,8BACF,EAAE,EAAE,QAAQ,MAAM,CAAC,EAAe,IAAI,EAAE,GAAG,CAAC,EAC7D;EAAC;EAAa;EAAY;EAAe;EAAe,CAAC;AAG5D,SAAgB;AACd,MAAI,CAAC,EAAc;EACnB,IAAM,IAAQ,iBAAiB,EAAgB,KAAK,EAAE,EAAiB;AACvE,eAAa,aAAa,EAAM;IAC/B,CAAC,EAAa,CAAC;CAGlB,IAAM,IAAiB,EAA6C,KAAK;AACzE,eACe;AACX,EAAI,EAAe,WAAS,aAAa,EAAe,QAAQ;IAEjE,EAAE,CAAC;CAEN,IAAM,IAAe,GAClB,MAAkB;AACjB,IAAe,EAAM,EACjB,EAAe,WAAS,aAAa,EAAe,QAAQ,EAC5D,IAAM,SAAS,OACnB,EAAe,UAAU,iBAAiB;AACxC,KAAW,EAAE,WAAW;IAAE;IAAO,OAAO;IAAI,EAAE,CAAC;KAC9C,EAAmB;IAExB,CAAC,EAAW,CACb,EAEK,IAAe,EACnB,OAAO,MAAkB;AACvB,MAAI;AAIF,GAHA,MAAM,EAAU,EAAE,WAAW,EAAE,OAAO;IAAE;IAAY;IAAU;IAAO,EAAE,EAAE,CAAC,EAC1E,MAAM,GAAa,EACnB,EAAe,GAAG,EAClB,EAAgB,KAAK;WACd,GAAK;AACZ,KACE,aAAe,QACX,EAAI,UACJ,EAAG,sDAAsD,oBAAoB,CAClF;;IAGL;EAAC;EAAW;EAAY;EAAU;EAAa;EAAG,CACnD,EAEK,IAAkB,EACtB,OAAO,MAAkB;AACvB,MAAI;AAGF,GAFA,MAAM,EAAY,EAAE,WAAW,EAAE,OAAO;IAAE;IAAY;IAAU;IAAO,EAAE,EAAE,CAAC,EAC5E,MAAM,GAAa,EACnB,EAAgB,KAAK;WACd,GAAK;AACZ,KACE,aAAe,QACX,EAAI,UACJ,EAAG,yDAAyD,uBAAuB,CACxF;;IAGL;EAAC;EAAa;EAAY;EAAU;EAAa;EAAG,CACrD,EAMK,IAAc,GACjB,MACC,EAAW,KAAK,SAChB,EAAG,mDAAmD,eAAe,EACvE,CAAC,EAAG,CACL;AAeD,QAbI,IAEA,kBAAC,OAAD;EACE,WAAU;EACV,MAAK;EACL,aAAU;YAHZ,CAKE,kBAAC,GAAD;GAAS,WAAU;GAAuB,eAAY;GAAS,CAAA,EAC9D,EAAG,mDAAmD,kBAAkB,CACrE;MAKR,kBAAC,OAAD;EAAK,WAAU;YAAf;GACG,KACC,kBAAC,OAAD;IACE,MAAK;IACL,WAAU;cAFZ,CAIE,kBAAC,QAAD;KAAM,WAAU;eAAU;KAAoB,CAAA,EAC9C,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAgB,KAAK;KACpC,cAAY,EAAG,oDAAoD,gBAAgB;KACnF,WAAU;eAEV,kBAAC,GAAD;MAAG,WAAU;MAAc,eAAY;MAAS,CAAA;KACzC,CAAA,CACL;;GAGR,kBAAC,OAAD;IACE,WAAU;IACV,cAAY,EAAG,oDAAoD,gBAAgB;cAFrF;KAIG,EAAa,WAAW,KAAK,CAAC,KAC7B,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAG,sDAAsD,mBAAmB;MAC3E,CAAA;KAEL,EAAa,KAAK,MAAe;MAChC,IAAM,IAAQ,EAAY,EAAW;AACrC,aACE,kBAAC,QAAD;OAEE,WAAU;iBAFZ;QAIE,kBAAC,GAAD;SAAK,WAAU;SAAU,eAAY;SAAS,CAAA;QAC7C;QACA,CAAC,KACA,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,EAAgB,EAAW,MAAM;SAChD,UAAU;SACV,cAAY,cAAc;SAC1B,WAAU;mBAEV,kBAAC,GAAD;UAAG,WAAU;UAAU,eAAY;UAAS,CAAA;SACrC,CAAA;QAEN;SAhBA,EAAW,GAgBX;OAET;KACD,CAAC,KAAY,CAAC,KACb,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAY,GAAK;MAChC,cAAY,EAAG,8CAA8C,UAAU;MACvE,WAAU;gBAJZ,CAME,kBAAC,GAAD;OAAM,WAAU;OAAU,eAAY;OAAS,CAAA,EAC9C,EAAG,8CAA8C,UAAU,CACrD;;KAEP;;GAEL,KACC,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OACE,WAAU;OACV,eAAY;OACZ,CAAA,EACF,kBAAC,SAAD;OACE,MAAK;OACL,OAAO;OACP,WAAW,MAAM,EAAa,EAAE,OAAO,MAAM;OAC7C,aAAa,EACX,yDACA,iBACD;OACD,WAAA;OACA,cAAY,EACV,2DACA,wBACD;OACD,WAAU;OACV,CAAA,CACE;;KACN,kBAAC,OAAD;MACE,WAAU;MACV,cAAY,EAAG,sDAAsD,kBAAkB;gBAFzF;QAII,KAAkB,KAAoB,CAAC,MACvC,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAG,+CAA+C,aAAa;QAC3D,CAAA;OAER,CAAC,KAAiB,CAAC,KAAoB,EAAY,WAAW,KAC7D,kBAAC,QAAD;QAAM,WAAU;kBACb,IACG,EAAG,sDAAsD,mBAAmB,GAC5E,EAAG,uDAAuD,oBAAoB;QAC7E,CAAA;OAER,EAAY,KAAK,MAChB,kBAAC,UAAD;QAEE,MAAK;QACL,eAAe,EAAa,EAAI,GAAG;QACnC,UAAU;QACV,cAAY,WAAW,EAAI;QAC3B,WAAU;kBANZ,CAQE,kBAAC,GAAD;SAAM,WAAU;SAAU,eAAY;SAAS,CAAA,EAC9C,EAAI,MACE;UATF,EAAI,GASF,CACT;OACE;;KACN,kBAAC,UAAD;MACE,MAAK;MACL,eAAe;AAEb,OADA,EAAY,GAAM,EAClB,EAAe,GAAG;;MAEpB,WAAU;gBAET,EAAG,4CAA4C,OAAO;MAChD,CAAA;KACL;;GAEJ"}
|
|
1
|
+
{"version":3,"file":"EntityTagPicker.js","names":[],"sources":["../../../src/components/shared/EntityTagPicker.tsx"],"sourcesContent":["/**\n * EntityTagPicker — Reusable tag picker for any VibeControls entity\n *\n * Two modes:\n * - Persisted (entityId provided): self-fetches assignments and writes\n * immediately via tag/untag mutations. Used on detail/edit surfaces.\n * - Draft (entityId omitted/empty): no queries or mutations — selections\n * are held locally and surfaced via `onDraftChange(tagIds)`. The parent\n * create form persists them after the entity is created (e.g. via\n * replaceVibecontrolsEntityTags with the new id). Used in create\n * forms/wizards where no entity id exists yet.\n *\n * Usage:\n * <EntityTagPicker entityType=\"VIBECONTROLS_VIBE\" entityId={vibe.id} />\n * <EntityTagPicker entityType=\"VIBECONTROLS_VIBE\" onDraftChange={setTagIds} />\n */\nimport { useState, useCallback, useMemo, useEffect, useRef } from 'react';\nimport { Tag, Plus, X, Search, Loader2 } from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n useGetEntityTags,\n useAvailableTags,\n useSearchTags,\n useTagEntity,\n useUntagEntity,\n} from '../../hooks/useEntityTags';\nimport type { AvailableTag, EntityTagAssignment } from '../../hooks/useEntityTags';\n\ninterface EntityTagPickerProps {\n entityType: string;\n /**\n * Existing entity id. Omit (or pass empty) to run in draft mode for\n * create forms — selections are emitted via `onDraftChange` instead of\n * being persisted immediately.\n */\n entityId?: string;\n /**\n * Draft-mode callback. Receives the current selected tag ids whenever\n * the draft selection changes. Only invoked when `entityId` is absent.\n */\n onDraftChange?: (tagIds: string[]) => void;\n readOnly?: boolean;\n}\n\nconst ERROR_DISMISS_MS = 4000;\nconst SEARCH_DEBOUNCE_MS = 250;\n\nexport function EntityTagPicker({\n entityType,\n entityId,\n onDraftChange,\n readOnly = false,\n}: EntityTagPickerProps) {\n const tr = useTr();\n const draftMode = !entityId;\n const [isAdding, setIsAdding] = useState(false);\n const [searchQuery, setSearchQuery] = useState('');\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\n // Draft-mode local selection (no persistence until the parent flushes it).\n const [draftTags, setDraftTags] = useState<AvailableTag[]>([]);\n\n const {\n data: tagsData,\n loading: tagsLoading,\n refetch: refetchTags,\n } = useGetEntityTags(entityType, entityId ?? '');\n const { data: availableData, loading: availableLoading } = useAvailableTags(50, {\n skip: !isAdding,\n });\n const [searchTags, { data: searchData, loading: searchLoading }] = useSearchTags();\n const [tagEntity, { loading: tagging }] = useTagEntity();\n const [untagEntity, { loading: untagging }] = useUntagEntity();\n\n // Apollo Client v4 returns DeepPartialObject from useQuery data, but our\n // hook types declare full shapes — we cast back to the declared types since\n // the resolver is guaranteed to return non-null fields per the schema.\n const assignedTags = useMemo(\n () => (tagsData?.vibecontrolsEntityTags ?? []) as EntityTagAssignment[],\n [tagsData]\n );\n\n // Unified chip list — draft selections in draft mode, persisted\n // assignments otherwise. `tagId` drives removal; `key` is render-stable.\n const chipItems = useMemo(\n () =>\n draftMode\n ? draftTags.map((t) => ({ key: t.id, tagId: t.id, label: t.label }))\n : assignedTags.map((a) => ({\n key: a.id,\n tagId: a.tagId,\n label:\n a.tag?.label ?? tr('vibecontrols.shared.entityTagPicker.untitledTag', 'Untitled tag'),\n })),\n [draftMode, draftTags, assignedTags, tr]\n );\n\n const assignedTagIds = useMemo(() => new Set(chipItems.map((c) => c.tagId)), [chipItems]);\n\n const suggestions: AvailableTag[] = useMemo(() => {\n const source = searchQuery\n ? (searchData?.searchVibecontrolsTags as AvailableTag[] | undefined)\n : (availableData?.availableVibecontrolsTags as AvailableTag[] | undefined);\n return (source ?? []).filter((t) => !assignedTagIds.has(t.id));\n }, [searchQuery, searchData, availableData, assignedTagIds]);\n\n // Auto-dismiss error toast after a few seconds\n useEffect(() => {\n if (!errorMessage) return;\n const timer = setTimeout(() => setErrorMessage(null), ERROR_DISMISS_MS);\n return () => clearTimeout(timer);\n }, [errorMessage]);\n\n // Debounce the search query so we don't fire a request on every keystroke.\n const searchTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n useEffect(() => {\n return () => {\n if (searchTimerRef.current) clearTimeout(searchTimerRef.current);\n };\n }, []);\n\n const handleSearch = useCallback(\n (query: string) => {\n setSearchQuery(query);\n if (searchTimerRef.current) clearTimeout(searchTimerRef.current);\n if (query.length < 2) return;\n searchTimerRef.current = setTimeout(() => {\n searchTags({ variables: { query, limit: 10 } });\n }, SEARCH_DEBOUNCE_MS);\n },\n [searchTags]\n );\n\n const handleAddTag = useCallback(\n async (tag: AvailableTag) => {\n if (draftMode) {\n setDraftTags((prev) => {\n if (prev.some((t) => t.id === tag.id)) return prev;\n const next = [...prev, tag];\n onDraftChange?.(next.map((t) => t.id));\n return next;\n });\n setSearchQuery('');\n setErrorMessage(null);\n return;\n }\n try {\n await tagEntity({\n variables: { input: { entityType, entityId: entityId ?? '', tagId: tag.id } },\n });\n await refetchTags();\n setSearchQuery('');\n setErrorMessage(null);\n } catch (err) {\n setErrorMessage(\n err instanceof Error\n ? err.message\n : tr('vibecontrols.shared.entityTagPicker.failedToAddTag', 'Failed to add tag')\n );\n }\n },\n [draftMode, onDraftChange, tagEntity, entityType, entityId, refetchTags, tr]\n );\n\n const handleRemoveTag = useCallback(\n async (tagId: string) => {\n if (draftMode) {\n setDraftTags((prev) => {\n const next = prev.filter((t) => t.id !== tagId);\n onDraftChange?.(next.map((t) => t.id));\n return next;\n });\n setErrorMessage(null);\n return;\n }\n try {\n await untagEntity({\n variables: { input: { entityType, entityId: entityId ?? '', tagId } },\n });\n await refetchTags();\n setErrorMessage(null);\n } catch (err) {\n setErrorMessage(\n err instanceof Error\n ? err.message\n : tr('vibecontrols.shared.entityTagPicker.failedToRemoveTag', 'Failed to remove tag')\n );\n }\n },\n [draftMode, onDraftChange, untagEntity, entityType, entityId, refetchTags, tr]\n );\n\n if (tagsLoading && !draftMode) {\n return (\n <div\n className=\"flex items-center gap-2 text-text-secondary text-sm\"\n role=\"status\"\n aria-live=\"polite\"\n >\n <Loader2 className=\"h-4 w-4 animate-spin\" aria-hidden=\"true\" />\n {tr('vibecontrols.shared.entityTagPicker.loadingTags', 'Loading tags...')}\n </div>\n );\n }\n\n return (\n <div className=\"space-y-3\">\n {errorMessage && (\n <div\n role=\"alert\"\n className=\"flex items-start gap-2 rounded-md border border-red-300 bg-red-50 px-3 py-2 text-sm text-red-700\"\n >\n <span className=\"flex-1\">{errorMessage}</span>\n <button\n type=\"button\"\n onClick={() => setErrorMessage(null)}\n aria-label={tr('vibecontrols.shared.entityTagPicker.dismissError', 'Dismiss error')}\n className=\"text-red-700 hover:text-red-900\"\n >\n <X className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n </button>\n </div>\n )}\n\n <div\n className=\"flex flex-wrap gap-2\"\n aria-label={tr('vibecontrols.shared.entityTagPicker.assignedTags', 'Assigned tags')}\n >\n {chipItems.length === 0 && !isAdding && (\n <p className=\"text-text-secondary text-sm\">\n {tr('vibecontrols.shared.entityTagPicker.noTagsAssigned', 'No tags assigned')}\n </p>\n )}\n {chipItems.map((chip) => (\n <span\n key={chip.key}\n className=\"inline-flex items-center gap-1 px-3 py-1 bg-action-primary-bg/10 text-action-primary-bg text-sm rounded-full\"\n >\n <Tag className=\"h-3 w-3\" aria-hidden=\"true\" />\n {chip.label}\n {!readOnly && (\n <button\n type=\"button\"\n onClick={() => handleRemoveTag(chip.tagId)}\n disabled={untagging}\n aria-label={`Remove tag ${chip.label}`}\n className=\"ml-1 hover:text-red-500 transition-colors disabled:opacity-50\"\n >\n <X className=\"h-3 w-3\" aria-hidden=\"true\" />\n </button>\n )}\n </span>\n ))}\n {!readOnly && !isAdding && (\n <button\n type=\"button\"\n onClick={() => setIsAdding(true)}\n aria-label={tr('vibecontrols.shared.entityTagPicker.addTag', 'Add tag')}\n className=\"inline-flex items-center gap-1 px-3 py-1 border border-dashed border-border-default text-text-secondary text-sm rounded-full hover:border-action-primary-bg hover:text-action-primary-bg transition-colors\"\n >\n <Plus className=\"h-3 w-3\" aria-hidden=\"true\" />\n {tr('vibecontrols.shared.entityTagPicker.addTag', 'Add Tag')}\n </button>\n )}\n </div>\n\n {isAdding && (\n <div className=\"border border-border-default rounded-lg p-3 space-y-2 bg-bg-surface\">\n <div className=\"relative\">\n <Search\n className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-text-secondary\"\n aria-hidden=\"true\"\n />\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => handleSearch(e.target.value)}\n placeholder={tr(\n 'vibecontrols.shared.entityTagPicker.searchPlaceholder',\n 'Search tags...'\n )}\n autoFocus\n aria-label={tr(\n 'vibecontrols.shared.entityTagPicker.searchAvailableTags',\n 'Search available tags'\n )}\n className=\"w-full pl-9 pr-3 py-2 text-sm bg-bg-default border border-border-default rounded-md focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 text-text-primary placeholder:text-text-secondary\"\n />\n </div>\n <div\n className=\"flex flex-wrap gap-2 max-h-32 overflow-y-auto\"\n aria-label={tr('vibecontrols.shared.entityTagPicker.tagSuggestions', 'Tag suggestions')}\n >\n {(searchLoading || (availableLoading && !searchQuery)) && (\n <span className=\"text-text-secondary text-xs\">\n {tr('vibecontrols.shared.entityTagPicker.loading', 'Loading...')}\n </span>\n )}\n {!searchLoading && !availableLoading && suggestions.length === 0 && (\n <span className=\"text-text-secondary text-xs\">\n {searchQuery\n ? tr('vibecontrols.shared.entityTagPicker.noMatchingTags', 'No matching tags')\n : tr('vibecontrols.shared.entityTagPicker.noAvailableTags', 'No available tags')}\n </span>\n )}\n {suggestions.map((tag) => (\n <button\n key={tag.id}\n type=\"button\"\n onClick={() => handleAddTag(tag)}\n disabled={tagging}\n aria-label={`Add tag ${tag.label}`}\n className=\"inline-flex items-center gap-1 px-3 py-1 bg-bg-sunken text-text-primary text-sm rounded-full hover:bg-action-primary-bg/10 hover:text-action-primary-bg transition-colors cursor-pointer disabled:opacity-50\"\n >\n <Plus className=\"h-3 w-3\" aria-hidden=\"true\" />\n {tag.label}\n </button>\n ))}\n </div>\n <button\n type=\"button\"\n onClick={() => {\n setIsAdding(false);\n setSearchQuery('');\n }}\n className=\"text-xs text-text-secondary hover:text-text-primary\"\n >\n {tr('vibecontrols.shared.entityTagPicker.done', 'Done')}\n </button>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { useTr as e } from "../../shared/hooks/useTr.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { useAvailableTags as t } from "../../hooks/useEntityTags.js";
|
|
3
|
+
import { FilterDropdown as n } from "./FilterDropdown.js";
|
|
4
4
|
import { useMemo as r } from "react";
|
|
5
5
|
import { jsx as i } from "react/jsx-runtime";
|
|
6
6
|
//#region src/components/shared/TagFilter.tsx
|
|
7
7
|
function a({ value: a, onChange: o, label: s }) {
|
|
8
|
-
let c = e(), l = s ?? c("vibecontrols.shared.tagFilter.label", "Tag"), { data: u } =
|
|
8
|
+
let c = e(), l = s ?? c("vibecontrols.shared.tagFilter.label", "Tag"), { data: u } = t(50), d = r(() => {
|
|
9
9
|
let e = u?.availableVibecontrolsTags ?? [];
|
|
10
10
|
return [{
|
|
11
11
|
value: "all",
|
|
@@ -15,7 +15,7 @@ function a({ value: a, onChange: o, label: s }) {
|
|
|
15
15
|
label: e.label
|
|
16
16
|
}))];
|
|
17
17
|
}, [u, c]);
|
|
18
|
-
return d.length <= 1 ? null : /* @__PURE__ */ i(
|
|
18
|
+
return d.length <= 1 ? null : /* @__PURE__ */ i(n, {
|
|
19
19
|
label: l,
|
|
20
20
|
options: d,
|
|
21
21
|
value: a,
|
|
@@ -5,6 +5,7 @@ import "./LoadingState.js";
|
|
|
5
5
|
import "./ErrorState.js";
|
|
6
6
|
import "./ConfirmDialog.js";
|
|
7
7
|
import "./QuotaExhaustedDialog.js";
|
|
8
|
+
import "./EntityTagPicker.js";
|
|
8
9
|
import "./EmptyState.js";
|
|
9
10
|
import "./SearchInput.js";
|
|
10
11
|
import "./FilterDropdown.js";
|
|
@@ -17,7 +18,6 @@ import "./GetHelpButton.js";
|
|
|
17
18
|
import "./SearchableSelect.js";
|
|
18
19
|
import "./SessionNotificationBanner.js";
|
|
19
20
|
import "./SelectionCheckbox.js";
|
|
20
|
-
import "./EntityTagPicker.js";
|
|
21
21
|
import "./TagFilter.js";
|
|
22
22
|
import "./PluginGate.js";
|
|
23
23
|
import "./RequireAgentReachable.js";
|