@agent-native/core 0.178.1 → 0.178.2-nightly-20260910214741

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.
Files changed (50) hide show
  1. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +108 -121
  2. package/corpus/templates/assets/actions/create-generation-session.ts +2 -6
  3. package/corpus/templates/assets/actions/generate-image.ts +3 -12
  4. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +87 -220
  5. package/corpus/templates/assets/server/lib/library-access.ts +23 -0
  6. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +83 -221
  7. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +324 -460
  8. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +57 -247
  9. package/corpus/templates/clips/app/components/library/library-layout.tsx +15 -48
  10. package/corpus/templates/clips/changelog/2026-09-10-open-mcp-connections-in-new-tabs.md +1 -0
  11. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +84 -94
  12. package/corpus/templates/crm/app/components/layout/CrmLayout.tsx +1 -1
  13. package/corpus/templates/crm/app/components/layout/CrmSidebar.tsx +108 -296
  14. package/corpus/templates/design/app/components/layout/Sidebar.tsx +55 -212
  15. package/corpus/templates/factory/app/components/layout/Sidebar.tsx +72 -260
  16. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +143 -362
  17. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +64 -159
  18. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +152 -165
  19. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +82 -207
  20. package/corpus/templates/slides/app/components/layout/Sidebar.tsx +61 -234
  21. package/corpus/templates/tasks/app/components/layout/Sidebar.tsx +73 -225
  22. package/dist/client/EnvironmentBadge.d.ts +7 -4
  23. package/dist/client/EnvironmentBadge.js +26 -18
  24. package/dist/client/FeedbackButton.js +23 -8
  25. package/dist/client/app-providers.js +1 -1
  26. package/dist/client/auth/AuthPage.js +0 -38
  27. package/dist/client/components/icons/AgentNativeIcon.js +1 -1
  28. package/dist/client/index.d.ts +1 -0
  29. package/dist/client/index.js +1 -0
  30. package/dist/client/ui/AppSidebar.d.ts +15 -0
  31. package/dist/client/ui/AppSidebar.js +34 -0
  32. package/dist/client/ui/index.d.ts +1 -0
  33. package/dist/client/ui/index.js +1 -0
  34. package/dist/collab/routes.d.ts +1 -1
  35. package/dist/config.d.ts +4 -0
  36. package/dist/config.js +19 -3
  37. package/dist/file-upload/actions/upload-image.d.ts +3 -3
  38. package/dist/notifications/routes.d.ts +2 -2
  39. package/dist/observability/routes.d.ts +5 -5
  40. package/dist/provider-api/actions/custom-provider-registration.d.ts +9 -9
  41. package/dist/provider-api/actions/provider-api.d.ts +15 -15
  42. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  43. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  44. package/dist/server/beta-opt-out-html.js +2 -2
  45. package/dist/templates/chat/app/components/layout/Sidebar.tsx +57 -247
  46. package/dist/vite/client.js +4 -1
  47. package/docs/content/template-clips-anonymous-intake.mdx +7 -5
  48. package/docs/content/template-clips-developers.mdx +1 -1
  49. package/package.json +2 -2
  50. package/src/templates/chat/app/components/layout/Sidebar.tsx +57 -247
@@ -1,49 +1,43 @@
1
1
  import {
2
- useSendToAgentChat,
3
2
  focusAgentChat,
4
3
  navigateWithAgentChatViewTransition,
4
+ useSendToAgentChat,
5
5
  } from "@agent-native/core/client/agent-chat";
6
- import { DevDatabaseLink } from "@agent-native/core/client/db-admin";
7
6
  import { useT } from "@agent-native/core/client/i18n";
8
7
  import { openCommandMenu } from "@agent-native/core/client/navigation";
9
8
  import { OrgSwitcher } from "@agent-native/core/client/org";
10
- import { AgentNativeIcon, FeedbackButton } from "@agent-native/core/client/ui";
11
- import { SidebarFooterActions } from "@agent-native/toolkit/app-shell";
9
+ import {
10
+ AppSidebar,
11
+ FeedbackButton,
12
+ type AppSidebarItemDefinition,
13
+ } from "@agent-native/core/client/ui";
12
14
  import {
13
15
  IconArrowUp,
14
- IconPlus,
15
- IconLoader2,
16
+ IconForms,
16
17
  IconMenu2,
17
- IconX,
18
18
  IconMessageCircle,
19
- IconSettings,
20
- IconForms,
21
- IconLayoutSidebarLeftCollapse,
22
- IconLayoutSidebarLeftExpand,
19
+ IconPlus,
23
20
  IconSearch,
21
+ IconSettings,
24
22
  } from "@tabler/icons-react";
25
- import { useState, useRef, useEffect, type MouseEvent } from "react";
26
- import { Link, useLocation, useNavigate } from "react-router";
23
+ import { useEffect, useRef, useState, type MouseEvent } from "react";
24
+ import { useLocation, useNavigate } from "react-router";
27
25
 
28
- import { ThemeToggle } from "@/components/ThemeToggle";
29
26
  import { Button } from "@/components/ui/button";
30
27
  import {
31
28
  Popover,
32
- PopoverTrigger,
33
29
  PopoverContent,
30
+ PopoverTrigger,
34
31
  } from "@/components/ui/popover";
35
- import { ScrollArea } from "@/components/ui/scroll-area";
36
32
  import { Textarea } from "@/components/ui/textarea";
37
33
  import {
38
34
  Tooltip,
39
35
  TooltipContent,
40
- TooltipProvider,
41
36
  TooltipTrigger,
42
37
  } from "@/components/ui/tooltip";
43
38
  import { useAgentPromptRun } from "@/hooks/use-agent-prompt-run";
44
39
  import { useCreateForm } from "@/hooks/use-forms";
45
40
  import { useIsMobile } from "@/hooks/use-mobile";
46
- import { cn } from "@/lib/utils";
47
41
 
48
42
  const SIDEBAR_COLLAPSE_KEY = "forms.sidebar.collapsed";
49
43
 
@@ -69,57 +63,6 @@ export function Sidebar() {
69
63
  return false;
70
64
  }
71
65
  });
72
- const effectiveCollapsed = collapsed && !isMobile;
73
-
74
- const collapseButton = (
75
- <Tooltip>
76
- <TooltipTrigger asChild>
77
- <button
78
- type="button"
79
- onClick={() => setCollapsed((value) => !value)}
80
- className="flex size-8 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground"
81
- aria-label={
82
- effectiveCollapsed
83
- ? t("sidebar.expandSidebar")
84
- : t("sidebar.collapseSidebar")
85
- }
86
- >
87
- {effectiveCollapsed ? (
88
- <IconLayoutSidebarLeftExpand className="h-4 w-4 rtl:-scale-x-100" />
89
- ) : (
90
- <IconLayoutSidebarLeftCollapse className="h-4 w-4 rtl:-scale-x-100" />
91
- )}
92
- </button>
93
- </TooltipTrigger>
94
- <TooltipContent side="right">
95
- {effectiveCollapsed
96
- ? t("sidebar.expandSidebar")
97
- : t("sidebar.collapseSidebar")}
98
- </TooltipContent>
99
- </Tooltip>
100
- );
101
- const searchButton = (
102
- <Tooltip>
103
- <TooltipTrigger asChild>
104
- <button
105
- type="button"
106
- onClick={openCommandMenu}
107
- className="flex size-8 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground"
108
- aria-label={t("root.searchForms")}
109
- >
110
- <IconSearch className="h-4 w-4" />
111
- </button>
112
- </TooltipTrigger>
113
- <TooltipContent side="right">{t("root.searchForms")}</TooltipContent>
114
- </Tooltip>
115
- );
116
- const feedbackButton = (
117
- <FeedbackButton
118
- variant={effectiveCollapsed ? "icon" : "sidebar"}
119
- side="right"
120
- className={effectiveCollapsed ? "size-8" : "min-w-0"}
121
- />
122
- );
123
66
 
124
67
  useEffect(() => {
125
68
  if (popoverOpen) {
@@ -133,18 +76,10 @@ export function Sidebar() {
133
76
  try {
134
77
  window.localStorage.setItem(SIDEBAR_COLLAPSE_KEY, collapsed ? "1" : "0");
135
78
  } catch {
136
- // Ignore storage failures; the in-memory preference still works.
79
+ // coercion-ok: storage failures are tolerated; fallback to in-memory preference
137
80
  }
138
81
  }, [collapsed]);
139
82
 
140
- function handleSkip() {
141
- setPopoverOpen(false);
142
- createForm.mutate(
143
- { title: t("sidebar.untitledForm") },
144
- { onSuccess: (form) => navigate(`/forms/${form.id}`) },
145
- );
146
- }
147
-
148
83
  function handleSubmitPrompt() {
149
84
  const trimmed = prompt.trim();
150
85
  if (!trimmed || promptRun.isActivePrompt(trimmed)) return;
@@ -157,7 +92,15 @@ export function Sidebar() {
157
92
  promptRun.trackRun(trimmed, tabId);
158
93
  }
159
94
 
160
- function navigateHomeChat(event: MouseEvent<HTMLAnchorElement>) {
95
+ function handleSkip() {
96
+ setPopoverOpen(false);
97
+ createForm.mutate(
98
+ { title: t("sidebar.untitledForm") },
99
+ { onSuccess: (form) => navigate(`/forms/${form.id}`) },
100
+ );
101
+ }
102
+
103
+ function navigateHomeChat(event: MouseEvent) {
161
104
  if (
162
105
  event.metaKey ||
163
106
  event.ctrlKey ||
@@ -173,30 +116,34 @@ export function Sidebar() {
173
116
  navigateWithAgentChatViewTransition(navigate, "/ask");
174
117
  }
175
118
 
176
- function toggleLogoView() {
177
- if (isMobile) setMobileOpen(false);
178
- focusAgentChat();
179
- navigateWithAgentChatViewTransition(navigate, "/ask");
180
- }
119
+ const items: AppSidebarItemDefinition[] = [
120
+ {
121
+ to: "/ask",
122
+ label: t("navigation.askForms"),
123
+ icon: IconMessageCircle,
124
+ active: location.pathname === "/ask" || location.pathname === "/home",
125
+ onClick: navigateHomeChat,
126
+ },
127
+ {
128
+ to: "/forms",
129
+ label: t("navigation.allForms"),
130
+ icon: IconForms,
131
+ active: location.pathname.startsWith("/forms"),
132
+ onClick: () => isMobile && setMobileOpen(false),
133
+ },
134
+ ];
181
135
 
182
- function handleBrandClick() {
183
- if (isMobile) {
184
- toggleLogoView();
185
- return;
186
- }
187
- setCollapsed((value) => !value);
188
- }
136
+ const secondaryItems: AppSidebarItemDefinition[] = [
137
+ {
138
+ to: "/settings",
139
+ label: t("navigation.settings"),
140
+ icon: IconSettings,
141
+ active: location.pathname === "/settings",
142
+ onClick: () => isMobile && setMobileOpen(false),
143
+ },
144
+ ];
189
145
 
190
- const newFormButton = (
191
- <PopoverTrigger asChild>
192
- <button className="forms-sidebar-nav-item flex min-h-[44px] w-full cursor-pointer items-center gap-2.5 rounded-lg px-3 py-2 text-sm text-muted-foreground active:scale-[0.96] transition-[background-color,box-shadow,color,transform] hover:bg-accent/50 hover:text-foreground">
193
- <IconPlus className="h-4 w-4 shrink-0" />
194
- <span>{t("sidebar.newForm")}</span>
195
- </button>
196
- </PopoverTrigger>
197
- );
198
-
199
- const newFormPopover = (
146
+ const newFormPopoverContent = (
200
147
  <PopoverContent
201
148
  side="right"
202
149
  align="start"
@@ -217,26 +164,22 @@ export function Sidebar() {
217
164
  }}
218
165
  placeholder={t("sidebar.describeFormPlaceholder")}
219
166
  className="mt-2 w-full resize-none bg-transparent text-sm placeholder:text-muted-foreground/50 border-none shadow-none"
220
- rows={4}
167
+ rows={3}
221
168
  />
222
- </div>
223
- <div className="flex items-center justify-between border-t border-border px-4 py-2.5">
224
- <div />
225
- <div className="flex items-center gap-3">
169
+ <div className="mt-3 flex items-center justify-between">
226
170
  <Button
227
- variant="link"
171
+ variant="ghost"
228
172
  size="sm"
229
- className="min-h-10 px-2 text-xs text-muted-foreground active:scale-[0.96] transition-[background-color,color,transform]"
173
+ className="text-xs text-muted-foreground hover:text-foreground"
230
174
  onClick={handleSkip}
231
- disabled={createForm.isPending}
232
175
  >
233
- {createForm.isPending && (
234
- <IconLoader2 className="h-3 w-3 animate-spin" />
235
- )}
236
176
  {t("sidebar.skipPrompt")}
237
177
  </Button>
238
- <span className="text-[11px] text-muted-foreground/70">
239
- {/Mac|iPhone|iPad/.test(navigator.userAgent) ? "⌘" : "Ctrl"}
178
+ <span className="text-[11px] text-muted-foreground/50">
179
+ {typeof navigator !== "undefined" &&
180
+ /Mac|iPod|iPhone|iPad/.test(navigator.platform)
181
+ ? "⌘"
182
+ : "Ctrl"}{" "}
240
183
  {t("sidebar.submitShortcutSuffix")}
241
184
  </span>
242
185
  <Button
@@ -254,275 +197,113 @@ export function Sidebar() {
254
197
  </PopoverContent>
255
198
  );
256
199
 
257
- const sidebarContent = effectiveCollapsed ? (
258
- <div className="agent-layout-left-drawer flex h-screen w-12 min-w-0 shrink-0 flex-col items-center overflow-hidden border-e border-border bg-sidebar py-2 transition-[width] duration-200 ease-out">
259
- <TooltipProvider delayDuration={0}>
260
- <Tooltip>
261
- <TooltipTrigger asChild>
200
+ const effectiveCollapsed = collapsed && !isMobile;
201
+
202
+ const newFormItem = effectiveCollapsed ? (
203
+ <Popover open={popoverOpen} onOpenChange={setPopoverOpen}>
204
+ <Tooltip>
205
+ <TooltipTrigger asChild>
206
+ <PopoverTrigger asChild>
262
207
  <button
263
208
  type="button"
264
- onClick={handleBrandClick}
265
- aria-label={t("sidebar.expandSidebar")}
266
- className="flex size-8 shrink-0 items-center justify-center rounded-lg outline-none transition-[background-color,box-shadow,transform] hover:bg-accent/50 focus-visible:ring-2 focus-visible:ring-ring"
267
- data-sidebar-brand-toggle
209
+ aria-label={t("sidebar.newForm")}
210
+ className="flex size-9 items-center justify-center rounded-md text-primary hover:bg-accent/60 hover:text-primary"
268
211
  >
269
- <AgentNativeIcon
270
- aria-hidden="true"
271
- className="h-3.5 w-6 shrink-0 text-foreground"
272
- />
212
+ <IconPlus className="size-4 shrink-0 text-primary" />
273
213
  </button>
274
- </TooltipTrigger>
275
- <TooltipContent side="right">
276
- {t("sidebar.expandSidebar")}
277
- </TooltipContent>
278
- </Tooltip>
279
- <nav className="mt-1 flex min-h-0 flex-1 flex-col items-center gap-1 overflow-y-auto">
280
- <Tooltip>
281
- <TooltipTrigger asChild>
282
- <Link
283
- to="/ask"
284
- onClick={navigateHomeChat}
285
- aria-label={t("navigation.askForms")}
286
- className={cn(
287
- "forms-sidebar-nav-item flex size-10 items-center justify-center rounded-lg active:scale-[0.96] transition-[background-color,box-shadow,color,transform]",
288
- location.pathname === "/ask" || location.pathname === "/home"
289
- ? "bg-accent text-accent-foreground"
290
- : "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
291
- )}
292
- >
293
- <IconMessageCircle className="h-4 w-4" />
294
- </Link>
295
- </TooltipTrigger>
296
- <TooltipContent side="right">
297
- {t("navigation.askForms")}
298
- </TooltipContent>
299
- </Tooltip>
300
-
301
- <Tooltip>
302
- <TooltipTrigger asChild>
303
- <Link
304
- to="/forms"
305
- aria-label={t("navigation.allForms")}
306
- className={cn(
307
- "forms-sidebar-nav-item flex size-10 items-center justify-center rounded-lg active:scale-[0.96] transition-[background-color,box-shadow,color,transform]",
308
- location.pathname.startsWith("/forms")
309
- ? "bg-accent text-accent-foreground"
310
- : "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
311
- )}
312
- >
313
- <IconForms className="h-4 w-4" />
314
- </Link>
315
- </TooltipTrigger>
316
- <TooltipContent side="right">
317
- {t("navigation.allForms")}
318
- </TooltipContent>
319
- </Tooltip>
320
-
321
- <Popover open={popoverOpen} onOpenChange={setPopoverOpen}>
322
- <Tooltip>
323
- <TooltipTrigger asChild>
324
- <PopoverTrigger asChild>
325
- <button
326
- type="button"
327
- aria-label={t("sidebar.newForm")}
328
- className="forms-sidebar-nav-item flex size-10 items-center justify-center rounded-lg text-muted-foreground transition-[background-color,box-shadow,color,transform] duration-150 ease-out hover:bg-accent/50 hover:text-foreground active:scale-[0.96] motion-reduce:active:scale-100"
329
- >
330
- <IconPlus className="h-4 w-4" />
331
- </button>
332
- </PopoverTrigger>
333
- </TooltipTrigger>
334
- <TooltipContent side="right">
335
- {t("sidebar.newForm")}
336
- </TooltipContent>
337
- </Tooltip>
338
- {newFormPopover}
339
- </Popover>
340
-
341
- <div className="mt-auto flex flex-col items-center gap-1">
342
- <Tooltip>
343
- <TooltipTrigger asChild>
344
- <Link
345
- to="/settings"
346
- aria-label={t("navigation.settings")}
347
- className={cn(
348
- "forms-sidebar-nav-item flex size-10 items-center justify-center rounded-lg active:scale-[0.96] transition-[background-color,box-shadow,color,transform]",
349
- location.pathname === "/settings"
350
- ? "bg-accent text-accent-foreground"
351
- : "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
352
- )}
353
- >
354
- <IconSettings className="h-4 w-4" />
355
- </Link>
356
- </TooltipTrigger>
357
- <TooltipContent side="right">
358
- {t("navigation.settings")}
359
- </TooltipContent>
360
- </Tooltip>
361
- </div>
362
- </nav>
363
- <SidebarFooterActions
364
- collapsed
365
- feedback={feedbackButton}
366
- search={searchButton}
367
- collapse={collapseButton}
368
- />
369
- </TooltipProvider>
370
- </div>
214
+ </PopoverTrigger>
215
+ </TooltipTrigger>
216
+ <TooltipContent side="right">{t("sidebar.newForm")}</TooltipContent>
217
+ </Tooltip>
218
+ {newFormPopoverContent}
219
+ </Popover>
371
220
  ) : (
372
- <div
373
- className={cn(
374
- "agent-layout-left-drawer flex h-screen w-60 min-w-0 shrink-0 flex-col overflow-hidden border-e border-border bg-sidebar transition-[width] duration-200 ease-out",
375
- isMobile && "w-full",
376
- )}
377
- >
378
- {/* Header */}
379
- <div className="flex h-12 shrink-0 items-center justify-between border-b border-border px-4">
380
- <TooltipProvider delayDuration={700}>
381
- <Tooltip>
382
- <TooltipTrigger asChild>
383
- <button
384
- type="button"
385
- aria-label={
386
- isMobile
387
- ? t("sidebar.openAskFullScreen")
388
- : t("sidebar.collapseSidebar")
389
- }
390
- className="flex min-h-10 min-w-0 items-center gap-2 rounded-lg px-2 text-base font-semibold tracking-tight text-foreground active:scale-[0.96] transition-[color,transform] hover:text-foreground/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
391
- onClick={handleBrandClick}
392
- data-sidebar-brand-toggle
393
- >
394
- <AgentNativeIcon
395
- aria-hidden="true"
396
- className="h-3.5 w-6 shrink-0 text-foreground"
397
- />
398
- <span className="truncate">{t("navigation.brand")}</span>
399
- </button>
400
- </TooltipTrigger>
401
- <TooltipContent side="right">
402
- {isMobile
403
- ? t("sidebar.openAskFullScreen")
404
- : t("sidebar.collapseSidebar")}
405
- </TooltipContent>
406
- </Tooltip>
407
- </TooltipProvider>
408
- {isMobile && (
409
- <Button
410
- variant="ghost"
411
- size="icon"
412
- className="size-10 transition-[background-color,box-shadow,transform] active:scale-[0.96] motion-reduce:active:scale-100"
413
- onClick={() => setMobileOpen(false)}
221
+ <Popover open={popoverOpen} onOpenChange={setPopoverOpen}>
222
+ <PopoverTrigger asChild>
223
+ <div className="group flex items-center rounded text-primary hover:bg-accent/60 cursor-pointer">
224
+ <button
225
+ type="button"
226
+ className="flex min-w-0 flex-1 items-center gap-2 px-2 py-1.5 text-xs text-primary"
414
227
  >
415
- <IconX size={18} />
416
- </Button>
417
- )}
418
- </div>
419
-
420
- <ScrollArea className="min-h-0 min-w-0 flex-1">
421
- <div
422
- className={cn(
423
- "grid min-w-0 max-w-full gap-1 overflow-hidden p-2",
424
- isMobile ? "w-full" : "w-60",
425
- )}
426
- >
427
- <Link
428
- to="/ask"
429
- onClick={navigateHomeChat}
430
- className={cn(
431
- "forms-sidebar-nav-item flex min-h-[44px] w-full min-w-0 max-w-full items-center gap-2.5 overflow-hidden rounded-lg px-3 py-2 text-sm active:scale-[0.96] transition-[background-color,box-shadow,color,transform] hover:text-primary",
432
- location.pathname === "/ask" || location.pathname === "/home"
433
- ? "bg-accent text-accent-foreground"
434
- : "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
435
- )}
436
- >
437
- <IconMessageCircle className="h-4 w-4 shrink-0" />
438
- <span className="min-w-0 flex-1 basis-0 truncate">
439
- {t("navigation.askForms")}
228
+ <IconPlus className="size-4 shrink-0 text-primary" />
229
+ <span className="flex-1 truncate text-start text-primary">
230
+ {t("sidebar.newForm")}
440
231
  </span>
441
- </Link>
232
+ </button>
233
+ </div>
234
+ </PopoverTrigger>
235
+ {newFormPopoverContent}
236
+ </Popover>
237
+ );
442
238
 
443
- <Link
444
- to="/forms"
445
- onClick={() => isMobile && setMobileOpen(false)}
446
- className={cn(
447
- "forms-sidebar-nav-item flex min-h-[44px] w-full min-w-0 max-w-full items-center gap-2.5 overflow-hidden rounded-lg px-3 py-2 text-sm active:scale-[0.96] transition-[background-color,box-shadow,color,transform] hover:text-primary",
448
- location.pathname.startsWith("/forms")
449
- ? "bg-accent text-accent-foreground"
450
- : "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
451
- )}
452
- >
453
- <IconForms className="h-4 w-4 shrink-0" />
454
- <span className="min-w-0 flex-1 basis-0 truncate">
455
- {t("navigation.allForms")}
456
- </span>
457
- </Link>
239
+ const feedbackButton = (
240
+ <FeedbackButton
241
+ variant={effectiveCollapsed ? "icon" : "sidebar"}
242
+ side="right"
243
+ />
244
+ );
458
245
 
459
- <Popover open={popoverOpen} onOpenChange={setPopoverOpen}>
460
- {newFormButton}
461
- {newFormPopover}
462
- </Popover>
463
- </div>
464
- </ScrollArea>
246
+ const orgSwitcher = <OrgSwitcher compact={effectiveCollapsed} />;
465
247
 
466
- {/* Pinned nav + footer */}
467
- <div className="shrink-0 px-3 py-1.5">
468
- <Link
469
- to="/settings"
470
- onClick={() => isMobile && setMobileOpen(false)}
471
- className={cn(
472
- "forms-sidebar-nav-item flex min-h-[44px] w-full items-center gap-2.5 rounded-lg px-3 py-2 text-sm active:scale-[0.96] transition-[background-color,box-shadow,color,transform]",
473
- location.pathname === "/settings"
474
- ? "bg-accent text-accent-foreground"
475
- : "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
476
- )}
248
+ const searchButton = (
249
+ <Tooltip>
250
+ <TooltipTrigger asChild>
251
+ <Button
252
+ type="button"
253
+ variant="ghost"
254
+ size="icon"
255
+ className="size-9 shrink-0 text-primary hover:bg-accent/60 hover:text-primary"
256
+ onClick={openCommandMenu}
257
+ aria-label={t("root.searchForms")}
477
258
  >
478
- <IconSettings size={14} className="shrink-0" />
479
- <span>{t("navigation.settings")}</span>
480
- </Link>
481
- </div>
259
+ <IconSearch className="size-4" />
260
+ </Button>
261
+ </TooltipTrigger>
262
+ <TooltipContent side="right">{t("root.searchForms")}</TooltipContent>
263
+ </Tooltip>
264
+ );
482
265
 
483
- {/* Footer */}
484
- <div className="shrink-0 space-y-2 px-3 py-2">
485
- <OrgSwitcher />
486
- <DevDatabaseLink />
487
- <div className="flex justify-end">
488
- <ThemeToggle className="h-9 w-9 shrink-0" />
489
- </div>
490
- <SidebarFooterActions
491
- feedback={feedbackButton}
492
- search={searchButton}
493
- collapse={collapseButton}
494
- className="px-0 py-0"
495
- />
496
- </div>
497
- </div>
266
+ const sidebarElement = (
267
+ <AppSidebar
268
+ collapsed={effectiveCollapsed}
269
+ onCollapsedChange={setCollapsed}
270
+ brandName={t("navigation.brand")}
271
+ brandHref="/forms"
272
+ items={items}
273
+ secondaryItems={secondaryItems}
274
+ feedback={feedbackButton}
275
+ orgSwitcher={orgSwitcher}
276
+ footerExtras={searchButton}
277
+ isMobile={isMobile}
278
+ mobileOpen={mobileOpen}
279
+ >
280
+ {newFormItem}
281
+ </AppSidebar>
498
282
  );
499
283
 
500
- if (isMobile) {
501
- return (
502
- <>
284
+ return (
285
+ <>
286
+ <div className="fixed top-2.5 start-2.5 z-40 md:hidden">
503
287
  <Button
504
288
  variant="ghost"
505
289
  size="icon"
506
- className="fixed top-2 start-2 z-40 size-10 active:scale-[0.96] transition-[background-color,box-shadow,transform] md:hidden"
290
+ className="size-10 rounded-lg active:scale-[0.96] transition-[background-color,box-shadow,transform]"
507
291
  onClick={() => setMobileOpen(true)}
508
292
  aria-label={t("sidebar.openSidebar")}
509
293
  >
510
294
  <IconMenu2 size={20} />
511
295
  </Button>
512
- {mobileOpen && (
513
- <>
514
- <div
515
- className="fixed inset-0 z-40 bg-black/40"
516
- onClick={() => setMobileOpen(false)}
517
- />
518
- <div className="fixed inset-y-0 start-0 z-50 w-72 max-w-[85vw]">
519
- {sidebarContent}
520
- </div>
521
- </>
522
- )}
523
- </>
524
- );
525
- }
296
+ </div>
526
297
 
527
- return sidebarContent;
298
+ {mobileOpen && (
299
+ <div
300
+ // guard:allow-raw-color — backdrop overlay
301
+ className="fixed inset-0 z-40 bg-black/50 md:hidden"
302
+ onClick={() => setMobileOpen(false)}
303
+ />
304
+ )}
305
+
306
+ {sidebarElement}
307
+ </>
308
+ );
528
309
  }