@nextclaw/ui 0.14.1 → 0.14.3

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 (224) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/assets/{api-DPcvCLfa.js → api-BP_8IaxB.js} +7 -7
  3. package/dist/assets/channels-list-page-DEh5Jjqx.js +8 -0
  4. package/dist/assets/chat-page-BRB0N5WW.js +105 -0
  5. package/dist/assets/config-split-page-CRhlZ_pH.js +1 -0
  6. package/dist/assets/{confirm-dialog-CWh5FfK2.js → confirm-dialog-DsivFJ4r.js} +2 -2
  7. package/dist/assets/{desktop-update-config-DsRhBizi.js → desktop-update-config-B0w9p2ep.js} +1 -1
  8. package/dist/assets/{dist-DPrgcEC0.js → dist-CikkhOlq.js} +1 -1
  9. package/dist/assets/{dist-DsE4OsSS.js → dist-PVA_xL1D.js} +1 -1
  10. package/dist/assets/doc-browser-BoNJ7-tE.js +1 -0
  11. package/dist/assets/doc-browser-CdJ_d4ik.js +1 -0
  12. package/dist/assets/doc-browser-context-MTw4EvKd.js +1 -0
  13. package/dist/assets/{ellipsis-B81FStLm.js → ellipsis-y6vDtq4M.js} +1 -1
  14. package/dist/assets/{external-link-C_dmG-WJ.js → external-link-DVJIQf_G.js} +1 -1
  15. package/dist/assets/index-BiE-K8-q.css +1 -0
  16. package/dist/assets/index-D9BAE_L_.js +105 -0
  17. package/dist/assets/mcp-marketplace-page-B1SZNY5m.js +40 -0
  18. package/dist/assets/mcp-marketplace-page-Cq_Tw42T.js +1 -0
  19. package/dist/assets/middleware-CkFT3R4v.js +1 -0
  20. package/dist/assets/model-config-page-DotaTUHb.js +1 -0
  21. package/dist/assets/plus-BbZqquJe.js +1 -0
  22. package/dist/assets/provider-scoped-model-input-DsFRuWJH.js +1 -0
  23. package/dist/assets/providers-config-page-DW4tQSLN.js +1 -0
  24. package/dist/assets/{react-sZz5HSCm.js → react-VNqT5Zp7.js} +1 -1
  25. package/dist/assets/remote-B8TiUfwW.js +1 -0
  26. package/dist/assets/rotate-cw-DjHDTkCv.js +1 -0
  27. package/dist/assets/runtime-config-page-S6ErRHR8.js +1 -0
  28. package/dist/assets/{save-MzjoiHri.js → save-BOJ477H-.js} +1 -1
  29. package/dist/assets/{search-B29dE4GN.js → search-BEti5beA.js} +1 -1
  30. package/dist/assets/{search-config-page-Dl5sZzob.js → search-config-page-DGj5am03.js} +1 -1
  31. package/dist/assets/{secrets-config-page-C_l-IFCK.js → secrets-config-page--IzrIOid.js} +2 -2
  32. package/dist/assets/{select-BKmh05hd.js → select-hoj8dK4K.js} +2 -2
  33. package/dist/assets/{tag-chip-BS_7Ueom.js → tag-chip-T8-h-die.js} +1 -1
  34. package/dist/assets/use-config-CjRV2hwL.js +1 -0
  35. package/dist/assets/x-D9PXs_xP.js +1 -0
  36. package/dist/index.html +70 -19
  37. package/index.html +55 -5
  38. package/package.json +9 -9
  39. package/src/app/components/app-presenter-provider.tsx +11 -2
  40. package/src/app/components/layout/__tests__/app-layout.test.tsx +11 -2
  41. package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +110 -79
  42. package/src/app/components/layout/app-layout.tsx +18 -4
  43. package/src/app/components/layout/header.tsx +3 -3
  44. package/src/app/components/layout/page-layout.tsx +2 -2
  45. package/src/app/components/layout/runtime-status-entry.tsx +7 -7
  46. package/src/app/components/layout/sidebar-items.tsx +194 -71
  47. package/src/app/components/layout/sidebar-rail.styles.ts +15 -0
  48. package/src/app/components/layout/sidebar.tsx +340 -83
  49. package/src/app/managers/viewport-layout.manager.ts +10 -0
  50. package/src/app/stores/viewport-layout.store.ts +39 -3
  51. package/src/app/styles/design-system.css +388 -26
  52. package/src/app/test/vitest-setup.ts +31 -0
  53. package/src/features/agents/components/__tests__/agents-page.test.tsx +62 -39
  54. package/src/features/agents/components/agents-page.tsx +10 -6
  55. package/src/features/chat/components/chat-sidebar-list-mode-switch.tsx +3 -3
  56. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +145 -0
  57. package/src/features/chat/components/conversation/__tests__/chat-conversation-header.test.tsx +4 -2
  58. package/src/features/chat/components/conversation/__tests__/chat-conversation-panel.test.tsx +69 -762
  59. package/src/features/chat/components/conversation/__tests__/chat-conversation-workspace-section.test.tsx +132 -0
  60. package/src/features/chat/components/conversation/chat-conversation-content.tsx +22 -15
  61. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +15 -16
  62. package/src/features/chat/components/conversation/chat-conversation-header.tsx +7 -6
  63. package/src/features/chat/components/conversation/chat-conversation-panel.tsx +19 -19
  64. package/src/features/chat/components/conversation/chat-conversation-skeleton.tsx +13 -13
  65. package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +10 -4
  66. package/src/features/chat/components/layout/__tests__/chat-sidebar-read-state.test.tsx +52 -11
  67. package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +18 -16
  68. package/src/features/chat/components/layout/chat-page-shell.tsx +1 -1
  69. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +247 -0
  70. package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +93 -20
  71. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +93 -36
  72. package/src/features/chat/components/layout/chat-sidebar.tsx +60 -93
  73. package/src/features/chat/components/providers/chat-presenter.provider.tsx +0 -6
  74. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +225 -0
  75. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +199 -0
  76. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +308 -0
  77. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +439 -0
  78. package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +194 -0
  79. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-attachments.ts +66 -0
  80. package/src/features/chat/features/{input/hooks/use-chat-input-bar-query-state.ts → conversation/hooks/use-session-conversation-input-query.ts} +51 -20
  81. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-state.ts +281 -0
  82. package/src/features/chat/features/conversation/utils/session-conversation-input-toolbar.utils.ts +158 -0
  83. package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +56 -0
  84. package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +117 -0
  85. package/src/features/chat/features/input/input-surface-plugins/__tests__/panel-app-reference-plugin.test.ts +77 -0
  86. package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +108 -0
  87. package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +10 -0
  88. package/src/features/chat/features/input/input-surface-plugins/input-surface-search.utils.ts +108 -0
  89. package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +108 -0
  90. package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +161 -0
  91. package/src/features/chat/features/input/utils/__tests__/chat-composer-state.utils.test.ts +22 -1
  92. package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +26 -13
  93. package/src/features/chat/features/input/utils/__tests__/ncp-chat-input-availability.utils.test.ts +7 -28
  94. package/src/features/chat/features/input/utils/chat-composer-state.utils.ts +6 -0
  95. package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +26 -51
  96. package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +2 -0
  97. package/src/features/chat/features/input/utils/ncp-chat-input-availability.utils.ts +5 -6
  98. package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +47 -12
  99. package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +7 -7
  100. package/src/features/chat/features/message/components/chat-message-list.container.tsx +99 -18
  101. package/src/features/chat/features/message/utils/__tests__/chat-message.utils.test.ts +31 -12
  102. package/src/features/chat/features/message/utils/chat-message-markdown-part.utils.ts +38 -0
  103. package/src/features/chat/features/message/utils/chat-message-part.utils.ts +1 -1
  104. package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +7 -7
  105. package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +6 -6
  106. package/src/features/chat/features/session/components/session-context-icon.tsx +2 -2
  107. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-project-badge.test.tsx +2 -2
  108. package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +1 -1
  109. package/src/features/chat/features/session/components/session-header/chat-session-header-menu-item.tsx +1 -1
  110. package/src/features/chat/features/session/components/session-header/chat-session-metadata-dialog.tsx +1 -1
  111. package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +3 -3
  112. package/src/features/chat/features/session/components/session-run-badge.tsx +1 -1
  113. package/src/features/chat/features/session/hooks/__tests__/use-chat-session-project.test.tsx +3 -30
  114. package/src/features/chat/features/session/hooks/use-chat-session-project.ts +0 -5
  115. package/src/features/chat/features/session/utils/chat-run-metadata.utils.ts +8 -0
  116. package/src/features/chat/features/session-type/components/__tests__/chat-session-type-menu.test.tsx +2 -2
  117. package/src/features/chat/features/session-type/components/__tests__/chat-session-type-option-item.test.tsx +2 -0
  118. package/src/features/chat/features/session-type/components/chat-session-type-menu.tsx +2 -2
  119. package/src/features/chat/features/session-type/components/chat-session-type-option-item.tsx +2 -2
  120. package/src/features/chat/features/session-type/hooks/use-chat-session-type-state.ts +10 -2
  121. package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx +49 -31
  122. package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +2 -9
  123. package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +21 -10
  124. package/src/features/chat/features/welcome/components/chat-welcome-agent-picker.tsx +3 -3
  125. package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +4 -4
  126. package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +14 -19
  127. package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +5 -5
  128. package/src/features/chat/features/welcome/components/chat-welcome.tsx +6 -6
  129. package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-project-options.utils.test.ts +6 -8
  130. package/src/features/chat/features/welcome/utils/chat-welcome-project-options.utils.ts +1 -16
  131. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +54 -0
  132. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +44 -76
  133. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +5 -1
  134. package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +7 -1
  135. package/src/features/chat/features/workspace/hooks/use-chat-conversation-workspace-state.ts +40 -31
  136. package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +59 -1
  137. package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +109 -0
  138. package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +44 -4
  139. package/src/features/chat/index.ts +0 -1
  140. package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +51 -99
  141. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +135 -21
  142. package/src/features/chat/managers/chat-session-list.manager.ts +12 -43
  143. package/src/features/chat/managers/chat-thread.manager.ts +108 -34
  144. package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +48 -1
  145. package/src/features/chat/pages/__tests__/ncp-chat-page.test.tsx +72 -0
  146. package/src/features/chat/pages/ncp-chat-page.tsx +0 -122
  147. package/src/features/chat/presenters/chat.presenter.ts +2 -19
  148. package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +31 -0
  149. package/src/features/chat/stores/chat-thread.store.ts +42 -26
  150. package/src/features/panel-apps/components/panel-app-list-item.tsx +8 -8
  151. package/src/features/panel-apps/components/panel-app-toolbar.tsx +4 -4
  152. package/src/features/panel-apps/components/panel-apps-list.tsx +20 -20
  153. package/src/features/panel-apps/hooks/use-panel-apps.ts +2 -1
  154. package/src/features/pwa/managers/__tests__/pwa-shell-theme.manager.test.ts +70 -6
  155. package/src/features/pwa/managers/pwa-shell-theme.manager.ts +10 -5
  156. package/src/features/service-apps/components/service-action-authorization-dialog.tsx +8 -8
  157. package/src/features/service-apps/components/service-apps-panel.tsx +24 -24
  158. package/src/features/settings/components/config/provider-advanced-settings-section.tsx +5 -5
  159. package/src/features/settings/components/config/provider-auth-section.tsx +7 -7
  160. package/src/features/settings/components/config/provider-form.tsx +10 -10
  161. package/src/features/settings/components/config/provider-models-section.tsx +16 -16
  162. package/src/features/settings/components/config/provider-pill-selector.tsx +1 -1
  163. package/src/features/settings/pages/providers-config-page.tsx +17 -17
  164. package/src/platforms/desktop/components/__tests__/desktop-app-shell.test.tsx +50 -10
  165. package/src/platforms/desktop/components/desktop-app-shell.tsx +34 -10
  166. package/src/platforms/desktop/components/desktop-window-chrome.tsx +41 -16
  167. package/src/shared/components/config-split-page.tsx +11 -11
  168. package/src/shared/components/doc-browser/doc-browser-home-page.tsx +2 -2
  169. package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +4 -4
  170. package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +4 -4
  171. package/src/shared/components/doc-browser/doc-browser.tsx +3 -3
  172. package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +1 -1
  173. package/src/shared/components/ui/actions/icon-action-button.tsx +1 -1
  174. package/src/shared/components/ui/button.tsx +4 -4
  175. package/src/shared/components/ui/card.tsx +4 -4
  176. package/src/shared/components/ui/dialog.tsx +5 -5
  177. package/src/shared/components/ui/input.tsx +1 -1
  178. package/src/shared/components/ui/label.tsx +1 -1
  179. package/src/shared/components/ui/popover.tsx +1 -1
  180. package/src/shared/components/ui/select.tsx +3 -3
  181. package/src/shared/components/ui/switch.tsx +3 -3
  182. package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +24 -1
  183. package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +8 -4
  184. package/src/shared/components/ui/tabs.tsx +4 -4
  185. package/src/shared/components/ui/textarea.tsx +1 -1
  186. package/src/shared/lib/i18n/locales/en-US/chat.json +20 -2
  187. package/src/shared/lib/i18n/locales/en-US/core.json +9 -2
  188. package/src/shared/lib/i18n/locales/zh-CN/chat.json +20 -2
  189. package/src/shared/lib/i18n/locales/zh-CN/core.json +9 -2
  190. package/src/shared/lib/theme/index.ts +94 -52
  191. package/src/shared/lib/ui-document-title/__tests__/ui-document-title.test.ts +49 -0
  192. package/src/shared/lib/ui-document-title/index.ts +50 -21
  193. package/dist/assets/channels-list-page-D6jtziu4.js +0 -8
  194. package/dist/assets/chat-page-BBnNJoKG.js +0 -105
  195. package/dist/assets/config-split-page-BCJhqdK9.js +0 -1
  196. package/dist/assets/doc-browser-C8xOF_9F.js +0 -1
  197. package/dist/assets/doc-browser-DgQedeAn.js +0 -1
  198. package/dist/assets/doc-browser-context-B46tQGmO.js +0 -1
  199. package/dist/assets/index-7o1WdUbU.js +0 -103
  200. package/dist/assets/index-DQWmEWzk.css +0 -1
  201. package/dist/assets/mcp-marketplace-page-CWZN7ohK.js +0 -1
  202. package/dist/assets/mcp-marketplace-page-Dzull_4-.js +0 -40
  203. package/dist/assets/model-config-page-Cv0O3BIj.js +0 -1
  204. package/dist/assets/plus-Bnt56oqG.js +0 -1
  205. package/dist/assets/provider-scoped-model-input-Duuh7i3h.js +0 -1
  206. package/dist/assets/providers-config-page-CPDz0Lxc.js +0 -1
  207. package/dist/assets/remote-B0LMs8u7.js +0 -1
  208. package/dist/assets/rotate-cw-BfyG_KEF.js +0 -1
  209. package/dist/assets/runtime-config-page-q0fvv8ME.js +0 -1
  210. package/dist/assets/use-config-DZkBWIdL.js +0 -1
  211. package/dist/assets/x-CW2KO-Hz.js +0 -1
  212. package/src/features/chat/components/conversation/chat-conversation-alerts.tsx +0 -37
  213. package/src/features/chat/features/input/components/__tests__/chat-attachment-upload-limit.test.ts +0 -38
  214. package/src/features/chat/features/input/components/chat-input-bar.container.tsx +0 -496
  215. package/src/features/chat/features/message/utils/chat-message-inline-content.utils.ts +0 -95
  216. package/src/features/chat/managers/__tests__/chat-input.manager.test.ts +0 -448
  217. package/src/features/chat/managers/__tests__/chat-run-snapshot.manager.test.ts +0 -129
  218. package/src/features/chat/managers/__tests__/chat-run.manager.test.ts +0 -236
  219. package/src/features/chat/managers/__tests__/chat-session-preference-sync.manager.test.ts +0 -219
  220. package/src/features/chat/managers/chat-input.manager.ts +0 -430
  221. package/src/features/chat/managers/chat-run.manager.ts +0 -110
  222. package/src/features/chat/managers/chat-session-preference-sync.manager.ts +0 -142
  223. package/src/features/chat/stores/chat-input.store.ts +0 -104
  224. /package/dist/assets/{config-hints-CTqBnCDp.js → config-hints-BkYBc7z0.js} +0 -0
@@ -1,10 +1,26 @@
1
- import type { ComponentType, ReactNode } from 'react';
2
- import { NavLink } from 'react-router-dom';
3
- import { Select, SelectContent, SelectTrigger } from '@/shared/components/ui/select';
4
- import { cn } from '@/shared/lib/utils';
1
+ import type { ComponentType, ReactNode } from "react";
2
+ import { Link, useLocation } from "react-router-dom";
3
+ import {
4
+ Select,
5
+ SelectContent,
6
+ SelectTrigger,
7
+ } from "@/shared/components/ui/select";
8
+ import {
9
+ Tooltip,
10
+ TooltipContent,
11
+ TooltipProvider,
12
+ TooltipTrigger,
13
+ } from "@/shared/components/ui/tooltip";
14
+ import { cn } from "@/shared/lib/utils";
15
+ import {
16
+ SIDEBAR_RAIL_ACTIVE_SURFACE_CLASS,
17
+ SIDEBAR_RAIL_CONTROL_CLASS,
18
+ SIDEBAR_RAIL_ICON_CLASS,
19
+ SIDEBAR_RAIL_SURFACE_CLASS,
20
+ } from "@/app/components/layout/sidebar-rail.styles";
5
21
 
6
22
  type SidebarIcon = ComponentType<{ className?: string }>;
7
- type SidebarItemDensity = 'default' | 'compact';
23
+ type SidebarItemDensity = "default" | "compact";
8
24
 
9
25
  type SidebarItemTone = {
10
26
  row: string;
@@ -15,72 +31,117 @@ type SidebarItemTone = {
15
31
 
16
32
  const SIDEBAR_ITEM_TONES: Record<SidebarItemDensity, SidebarItemTone> = {
17
33
  default: {
18
- row: 'gap-3 px-3 py-2.5 text-[14px]',
19
- icon: 'h-[17px] w-[17px]',
20
- value: 'text-xs',
21
- gap: 'gap-3'
34
+ row: "gap-3 px-3 py-2.5 text-[14px]",
35
+ icon: "h-[17px] w-[17px]",
36
+ value: "text-xs",
37
+ gap: "gap-3",
22
38
  },
23
39
  compact: {
24
- row: 'gap-2.5 px-3 py-2 text-[13px]',
25
- icon: 'h-4 w-4',
26
- value: 'text-[11px]',
27
- gap: 'gap-2.5'
28
- }
40
+ row: "gap-2.5 px-3 py-2 text-[13px]",
41
+ icon: "h-4 w-4",
42
+ value: "text-[11px]",
43
+ gap: "gap-2.5",
44
+ },
29
45
  };
30
46
 
31
47
  function getSidebarItemTone(density: SidebarItemDensity): SidebarItemTone {
32
48
  return SIDEBAR_ITEM_TONES[density];
33
49
  }
34
50
 
51
+ function isSidebarRouteActive(pathname: string, target: string): boolean {
52
+ const normalizedPathname = pathname.toLowerCase();
53
+ const normalizedTarget = target.toLowerCase();
54
+ return (
55
+ normalizedPathname === normalizedTarget ||
56
+ normalizedPathname.startsWith(`${normalizedTarget}/`)
57
+ );
58
+ }
59
+
60
+ function SidebarItemTooltip({
61
+ children,
62
+ label,
63
+ side = "right",
64
+ }: {
65
+ children: ReactNode;
66
+ label: ReactNode;
67
+ side?: "right" | "bottom";
68
+ }) {
69
+ return (
70
+ <TooltipProvider delayDuration={250}>
71
+ <Tooltip>
72
+ <TooltipTrigger asChild>{children}</TooltipTrigger>
73
+ <TooltipContent side={side} className="text-xs">
74
+ {label}
75
+ </TooltipContent>
76
+ </Tooltip>
77
+ </TooltipProvider>
78
+ );
79
+ }
80
+
35
81
  type SidebarNavLinkItemProps = {
36
82
  to: string;
37
- label: ReactNode;
83
+ label: string;
38
84
  icon: SidebarIcon;
39
85
  density?: SidebarItemDensity;
40
86
  className?: string;
87
+ collapsed?: boolean;
41
88
  };
42
89
 
43
90
  export function SidebarNavLinkItem({
44
91
  to,
45
92
  label,
46
93
  icon: Icon,
47
- density = 'default',
48
- className
94
+ density = "default",
95
+ className,
96
+ collapsed = false,
49
97
  }: SidebarNavLinkItemProps) {
50
98
  const tone = getSidebarItemTone(density);
51
-
52
- return (
53
- <NavLink
99
+ const { pathname } = useLocation();
100
+ const isActive = isSidebarRouteActive(pathname, to);
101
+ const link = (
102
+ <Link
54
103
  to={to}
55
- className={({ isActive }) =>
56
- cn(
57
- 'group flex w-full items-center rounded-xl font-medium transition-colors duration-base',
58
- tone.row,
59
- isActive
60
- ? 'bg-gray-200 text-gray-900 shadow-sm'
61
- : 'text-gray-600 hover:bg-gray-200/60 hover:text-gray-900',
62
- className
63
- )
64
- }
65
- >
66
- {({ isActive }) => (
67
- <>
68
- <Icon
69
- className={cn(
70
- tone.icon,
71
- 'transition-colors',
72
- isActive ? 'text-gray-900' : 'text-gray-500 group-hover:text-gray-800'
73
- )}
74
- />
75
- <span className="min-w-0 flex-1 text-left">{label}</span>
76
- </>
104
+ aria-label={label}
105
+ aria-current={isActive ? "page" : undefined}
106
+ className={cn(
107
+ "group flex w-full items-center rounded-xl font-medium transition-colors duration-base focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35",
108
+ collapsed
109
+ ? cn(SIDEBAR_RAIL_CONTROL_CLASS, "justify-center px-0 py-0")
110
+ : tone.row,
111
+ isActive
112
+ ? collapsed
113
+ ? SIDEBAR_RAIL_ACTIVE_SURFACE_CLASS
114
+ : "bg-accent text-accent-foreground shadow-sm"
115
+ : collapsed
116
+ ? SIDEBAR_RAIL_SURFACE_CLASS
117
+ : "text-muted-foreground hover:bg-accent hover:text-accent-foreground",
118
+ className,
77
119
  )}
78
- </NavLink>
120
+ >
121
+ <Icon
122
+ className={cn(
123
+ collapsed ? SIDEBAR_RAIL_ICON_CLASS : tone.icon,
124
+ "shrink-0 transition-colors",
125
+ isActive
126
+ ? "text-accent-foreground"
127
+ : "text-muted-foreground/75 group-hover:text-accent-foreground",
128
+ )}
129
+ />
130
+ <span className={collapsed ? "sr-only" : "min-w-0 flex-1 text-left"}>
131
+ {label}
132
+ </span>
133
+ </Link>
134
+ );
135
+
136
+ return collapsed ? (
137
+ <SidebarItemTooltip label={label}>{link}</SidebarItemTooltip>
138
+ ) : (
139
+ link
79
140
  );
80
141
  }
81
142
 
82
143
  type SidebarActionItemProps = {
83
- label: ReactNode;
144
+ label: string;
84
145
  icon: SidebarIcon;
85
146
  onClick: () => void;
86
147
  density?: SidebarItemDensity;
@@ -90,38 +151,62 @@ type SidebarActionItemProps = {
90
151
  trailingClassName?: string;
91
152
  testId?: string;
92
153
  trailingTestId?: string;
154
+ collapsed?: boolean;
93
155
  };
94
156
 
95
157
  export function SidebarActionItem({
96
158
  label,
97
159
  icon: Icon,
98
160
  onClick,
99
- density = 'default',
161
+ density = "default",
100
162
  className,
101
163
  labelClassName,
102
164
  trailing,
103
165
  trailingClassName,
104
166
  testId,
105
- trailingTestId
167
+ trailingTestId,
168
+ collapsed = false,
106
169
  }: SidebarActionItemProps) {
107
170
  const tone = getSidebarItemTone(density);
108
-
109
- return (
171
+ const button = (
110
172
  <button
111
173
  type="button"
112
174
  onClick={onClick}
175
+ aria-label={label}
113
176
  className={cn(
114
- 'flex w-full items-center rounded-xl font-medium text-gray-600 transition-all duration-base hover:bg-gray-200/60 hover:text-gray-800',
115
- tone.row,
116
- className
177
+ "group flex w-full items-center rounded-xl font-medium text-muted-foreground transition-all duration-base hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35",
178
+ collapsed
179
+ ? cn(
180
+ SIDEBAR_RAIL_CONTROL_CLASS,
181
+ "justify-center px-0 py-0",
182
+ SIDEBAR_RAIL_SURFACE_CLASS,
183
+ )
184
+ : tone.row,
185
+ className,
117
186
  )}
118
187
  data-testid={testId}
119
188
  >
120
- <Icon className={cn(tone.icon, 'shrink-0 text-gray-400')} />
121
- <span className={cn('min-w-0 flex-1 text-left', labelClassName)}>{label}</span>
122
- {trailing ? (
189
+ <Icon
190
+ className={cn(
191
+ collapsed ? SIDEBAR_RAIL_ICON_CLASS : tone.icon,
192
+ "shrink-0 text-muted-foreground/75 transition-colors group-hover:text-accent-foreground",
193
+ )}
194
+ />
195
+ <span
196
+ className={cn(
197
+ collapsed ? "sr-only" : "min-w-0 flex-1 text-left",
198
+ labelClassName,
199
+ )}
200
+ >
201
+ {label}
202
+ </span>
203
+ {!collapsed && trailing ? (
123
204
  <span
124
- className={cn('shrink-0 text-gray-500', tone.value, trailingClassName)}
205
+ className={cn(
206
+ "shrink-0 text-muted-foreground",
207
+ tone.value,
208
+ trailingClassName,
209
+ )}
125
210
  data-testid={trailingTestId}
126
211
  >
127
212
  {trailing}
@@ -129,16 +214,23 @@ export function SidebarActionItem({
129
214
  ) : null}
130
215
  </button>
131
216
  );
217
+
218
+ return collapsed ? (
219
+ <SidebarItemTooltip label={label}>{button}</SidebarItemTooltip>
220
+ ) : (
221
+ button
222
+ );
132
223
  }
133
224
 
134
225
  type SidebarSelectItemProps = {
135
- label: ReactNode;
226
+ label: string;
136
227
  icon: SidebarIcon;
137
228
  value: string;
138
- valueLabel: ReactNode;
229
+ valueLabel: string;
139
230
  onValueChange: (value: string) => void;
140
231
  density?: SidebarItemDensity;
141
232
  children: ReactNode;
233
+ collapsed?: boolean;
142
234
  };
143
235
 
144
236
  export function SidebarSelectItem({
@@ -147,25 +239,56 @@ export function SidebarSelectItem({
147
239
  value,
148
240
  valueLabel,
149
241
  onValueChange,
150
- density = 'default',
151
- children
242
+ density = "default",
243
+ children,
244
+ collapsed = false,
152
245
  }: SidebarSelectItemProps) {
153
246
  const tone = getSidebarItemTone(density);
154
-
155
- return (
156
- <Select value={value} onValueChange={onValueChange}>
157
- <SelectTrigger
247
+ const trigger = (
248
+ <SelectTrigger
249
+ aria-label={label}
250
+ className={cn(
251
+ "group h-auto w-full rounded-xl border-0 bg-transparent font-medium text-muted-foreground shadow-none hover:bg-accent hover:text-accent-foreground focus:ring-0 focus-visible:ring-2 focus-visible:ring-primary/35",
252
+ collapsed
253
+ ? cn(
254
+ SIDEBAR_RAIL_CONTROL_CLASS,
255
+ "justify-center px-0 py-0",
256
+ SIDEBAR_RAIL_SURFACE_CLASS,
257
+ )
258
+ : tone.row,
259
+ )}
260
+ >
261
+ <div
158
262
  className={cn(
159
- 'h-auto w-full rounded-xl border-0 bg-transparent font-medium text-gray-600 shadow-none hover:bg-gray-200/60 focus:ring-0',
160
- tone.row
263
+ "flex min-w-0 items-center",
264
+ collapsed ? "justify-center" : tone.gap,
161
265
  )}
162
266
  >
163
- <div className={cn('flex min-w-0 items-center', tone.gap)}>
164
- <Icon className={cn(tone.icon, 'text-gray-400')} />
165
- <span className="text-left">{label}</span>
166
- </div>
167
- <span className={cn('ml-auto text-gray-500', tone.value)}>{valueLabel}</span>
168
- </SelectTrigger>
267
+ <Icon
268
+ className={cn(
269
+ collapsed ? SIDEBAR_RAIL_ICON_CLASS : tone.icon,
270
+ "shrink-0 text-muted-foreground/75 transition-colors group-hover:text-accent-foreground",
271
+ )}
272
+ />
273
+ <span className={collapsed ? "sr-only" : "text-left"}>{label}</span>
274
+ </div>
275
+ {!collapsed ? (
276
+ <span className={cn("ml-auto text-muted-foreground", tone.value)}>
277
+ {valueLabel}
278
+ </span>
279
+ ) : null}
280
+ </SelectTrigger>
281
+ );
282
+
283
+ return (
284
+ <Select value={value} onValueChange={onValueChange}>
285
+ {collapsed ? (
286
+ <SidebarItemTooltip label={`${label}: ${valueLabel}`}>
287
+ {trigger}
288
+ </SidebarItemTooltip>
289
+ ) : (
290
+ trigger
291
+ )}
169
292
  <SelectContent>{children}</SelectContent>
170
293
  </Select>
171
294
  );
@@ -0,0 +1,15 @@
1
+ export const SIDEBAR_RAIL_WIDTH_PX = 56;
2
+ export const SIDEBAR_RAIL_WIDTH_CLASS = "w-[56px]";
3
+ export const SIDEBAR_RAIL_PADDING_X_CLASS = "px-2";
4
+ export const SIDEBAR_RAIL_CONTROL_CLASS = "h-9 w-9 rounded-xl";
5
+ export const SIDEBAR_RAIL_ICON_CLASS = "h-4 w-4";
6
+ export const SIDEBAR_RAIL_STACK_CLASS = "space-y-1";
7
+ export const SIDEBAR_RAIL_ITEM_GAP_CLASS = "gap-1";
8
+
9
+ export const SIDEBAR_RAIL_SURFACE_CLASS =
10
+ "text-muted-foreground transition-colors duration-base hover:bg-accent hover:text-accent-foreground active:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35";
11
+
12
+ export const SIDEBAR_RAIL_ACTIVE_SURFACE_CLASS = "bg-accent text-accent-foreground";
13
+
14
+ export const SIDEBAR_RAIL_PRIMARY_SURFACE_CLASS =
15
+ "bg-primary/10 text-primary transition-colors duration-base hover:bg-primary/15 hover:text-primary-700 active:bg-primary/20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35";