@nextclaw/ui 0.14.2 → 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 (129) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/assets/{api-a14tR_z1.js → api-BP_8IaxB.js} +3 -3
  3. package/dist/assets/{channels-list-page-CFGkm4DT.js → channels-list-page-DEh5Jjqx.js} +1 -1
  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-ClH_Ec7n.js → confirm-dialog-DsivFJ4r.js} +2 -2
  7. package/dist/assets/{desktop-update-config-DPNDnVpk.js → desktop-update-config-B0w9p2ep.js} +1 -1
  8. package/dist/assets/{dist-BtCS2Ucq.js → dist-CikkhOlq.js} +1 -1
  9. package/dist/assets/{dist-CLGGLm8b.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-D4JwMMxK.js → doc-browser-context-MTw4EvKd.js} +1 -1
  13. package/dist/assets/{ellipsis-CtJXjIol.js → ellipsis-y6vDtq4M.js} +1 -1
  14. package/dist/assets/{external-link-CdJiG13p.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-BU0kIjhR.js → mcp-marketplace-page-B1SZNY5m.js} +1 -1
  18. package/dist/assets/mcp-marketplace-page-Cq_Tw42T.js +1 -0
  19. package/dist/assets/{model-config-page-D6hYXROd.js → model-config-page-DotaTUHb.js} +1 -1
  20. package/dist/assets/plus-BbZqquJe.js +1 -0
  21. package/dist/assets/{provider-scoped-model-input-DAX2HgmR.js → provider-scoped-model-input-DsFRuWJH.js} +1 -1
  22. package/dist/assets/providers-config-page-DW4tQSLN.js +1 -0
  23. package/dist/assets/{react-C6cZgP44.js → react-VNqT5Zp7.js} +1 -1
  24. package/dist/assets/remote-B8TiUfwW.js +1 -0
  25. package/dist/assets/rotate-cw-DjHDTkCv.js +1 -0
  26. package/dist/assets/{runtime-config-page-C5xKaV3i.js → runtime-config-page-S6ErRHR8.js} +1 -1
  27. package/dist/assets/{save-DKA0x4zk.js → save-BOJ477H-.js} +1 -1
  28. package/dist/assets/{search-BNlcoj5c.js → search-BEti5beA.js} +1 -1
  29. package/dist/assets/{search-config-page-Cq9uJ-N8.js → search-config-page-DGj5am03.js} +1 -1
  30. package/dist/assets/{secrets-config-page-CxSbZLBo.js → secrets-config-page--IzrIOid.js} +1 -1
  31. package/dist/assets/{select-CvTlDZTQ.js → select-hoj8dK4K.js} +2 -2
  32. package/dist/assets/{tag-chip-8ffF2y2C.js → tag-chip-T8-h-die.js} +1 -1
  33. package/dist/assets/{use-config-DmT0o1gv.js → use-config-CjRV2hwL.js} +1 -1
  34. package/dist/assets/x-D9PXs_xP.js +1 -0
  35. package/dist/index.html +69 -19
  36. package/index.html +55 -5
  37. package/package.json +7 -7
  38. package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +3 -3
  39. package/src/app/components/layout/header.tsx +3 -3
  40. package/src/app/components/layout/page-layout.tsx +2 -2
  41. package/src/app/components/layout/runtime-status-entry.tsx +7 -7
  42. package/src/app/components/layout/sidebar-items.tsx +10 -10
  43. package/src/app/components/layout/sidebar-rail.styles.ts +2 -2
  44. package/src/app/components/layout/sidebar.tsx +6 -6
  45. package/src/app/styles/design-system.css +388 -26
  46. package/src/app/test/vitest-setup.ts +31 -0
  47. package/src/features/chat/components/chat-sidebar-list-mode-switch.tsx +3 -3
  48. package/src/features/chat/components/conversation/__tests__/chat-conversation-header.test.tsx +4 -2
  49. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +1 -1
  50. package/src/features/chat/components/conversation/chat-conversation-header.tsx +7 -6
  51. package/src/features/chat/components/conversation/chat-conversation-panel.tsx +1 -1
  52. package/src/features/chat/components/conversation/chat-conversation-skeleton.tsx +13 -13
  53. package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +2 -2
  54. package/src/features/chat/components/layout/chat-page-shell.tsx +1 -1
  55. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +3 -3
  56. package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +14 -14
  57. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +15 -15
  58. package/src/features/chat/components/layout/chat-sidebar.tsx +3 -3
  59. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +225 -0
  60. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +199 -0
  61. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +28 -9
  62. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +18 -6
  63. package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +3 -13
  64. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-query.ts +19 -3
  65. package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +7 -7
  66. package/src/features/chat/features/message/components/chat-message-list.container.tsx +6 -6
  67. package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +5 -5
  68. package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +4 -4
  69. package/src/features/chat/features/session/components/session-context-icon.tsx +2 -2
  70. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-project-badge.test.tsx +2 -2
  71. package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +1 -1
  72. package/src/features/chat/features/session/components/session-header/chat-session-header-menu-item.tsx +1 -1
  73. package/src/features/chat/features/session/components/session-header/chat-session-metadata-dialog.tsx +1 -1
  74. package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +3 -3
  75. package/src/features/chat/features/session/components/session-run-badge.tsx +1 -1
  76. package/src/features/chat/features/session-type/hooks/use-chat-session-type-state.ts +10 -2
  77. package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +0 -7
  78. package/src/features/chat/features/welcome/components/chat-welcome-agent-picker.tsx +3 -3
  79. package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +4 -4
  80. package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +14 -19
  81. package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +3 -3
  82. package/src/features/chat/features/welcome/components/chat-welcome.tsx +4 -4
  83. package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-project-options.utils.test.ts +6 -8
  84. package/src/features/chat/features/welcome/utils/chat-welcome-project-options.utils.ts +1 -16
  85. package/src/features/panel-apps/components/panel-app-list-item.tsx +8 -8
  86. package/src/features/panel-apps/components/panel-app-toolbar.tsx +4 -4
  87. package/src/features/panel-apps/components/panel-apps-list.tsx +20 -20
  88. package/src/features/pwa/managers/__tests__/pwa-shell-theme.manager.test.ts +70 -6
  89. package/src/features/pwa/managers/pwa-shell-theme.manager.ts +10 -5
  90. package/src/features/service-apps/components/service-action-authorization-dialog.tsx +8 -8
  91. package/src/features/service-apps/components/service-apps-panel.tsx +24 -24
  92. package/src/features/settings/components/config/provider-advanced-settings-section.tsx +5 -5
  93. package/src/features/settings/components/config/provider-auth-section.tsx +7 -7
  94. package/src/features/settings/components/config/provider-form.tsx +10 -10
  95. package/src/features/settings/components/config/provider-models-section.tsx +16 -16
  96. package/src/features/settings/components/config/provider-pill-selector.tsx +1 -1
  97. package/src/features/settings/pages/providers-config-page.tsx +17 -17
  98. package/src/shared/components/config-split-page.tsx +11 -11
  99. package/src/shared/components/doc-browser/doc-browser-home-page.tsx +2 -2
  100. package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +4 -4
  101. package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +4 -4
  102. package/src/shared/components/doc-browser/doc-browser.tsx +3 -3
  103. package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +1 -1
  104. package/src/shared/components/ui/actions/icon-action-button.tsx +1 -1
  105. package/src/shared/components/ui/button.tsx +4 -4
  106. package/src/shared/components/ui/card.tsx +4 -4
  107. package/src/shared/components/ui/dialog.tsx +5 -5
  108. package/src/shared/components/ui/input.tsx +1 -1
  109. package/src/shared/components/ui/label.tsx +1 -1
  110. package/src/shared/components/ui/popover.tsx +1 -1
  111. package/src/shared/components/ui/select.tsx +3 -3
  112. package/src/shared/components/ui/switch.tsx +3 -3
  113. package/src/shared/components/ui/tabs.tsx +4 -4
  114. package/src/shared/components/ui/textarea.tsx +1 -1
  115. package/src/shared/lib/i18n/locales/en-US/core.json +7 -2
  116. package/src/shared/lib/i18n/locales/zh-CN/core.json +7 -2
  117. package/src/shared/lib/theme/index.ts +94 -52
  118. package/dist/assets/chat-page-CEdavJXc.js +0 -105
  119. package/dist/assets/config-split-page-B5b9B0uE.js +0 -1
  120. package/dist/assets/doc-browser-5nMWYKBD.js +0 -1
  121. package/dist/assets/doc-browser-BJRLhztv.js +0 -1
  122. package/dist/assets/index-BDN1zzMQ.js +0 -105
  123. package/dist/assets/index-g3uSZXoc.css +0 -1
  124. package/dist/assets/mcp-marketplace-page-sMNFSq_T.js +0 -1
  125. package/dist/assets/plus-DPjb0piO.js +0 -1
  126. package/dist/assets/providers-config-page-BzAeaACA.js +0 -1
  127. package/dist/assets/remote-B0G80uPr.js +0 -1
  128. package/dist/assets/rotate-cw-DnEB1Rju.js +0 -1
  129. package/dist/assets/x-BiSSpgqB.js +0 -1
@@ -38,15 +38,15 @@ export function DocBrowserDocsToolbar({
38
38
  }
39
39
 
40
40
  return (
41
- <div className="flex items-center gap-2 px-3.5 py-2 bg-white border-b border-gray-100 shrink-0">
41
+ <div className="flex items-center gap-2 border-b border-border/70 bg-card px-3.5 py-2 shrink-0">
42
42
  <form onSubmit={onSubmit} className="flex-1 relative">
43
- <Search className="w-3.5 h-3.5 absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" />
43
+ <Search className="absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground/70" />
44
44
  <input
45
45
  type="text"
46
46
  value={urlInput}
47
47
  onChange={(e) => onUrlInputChange(e.target.value)}
48
48
  placeholder={t('docBrowserSearchPlaceholder')}
49
- className="w-full h-8 pl-8 pr-3 rounded-lg bg-gray-50 border border-gray-200 text-xs text-gray-700 focus:outline-none focus:ring-1 focus:ring-primary/30 focus:border-primary/40 transition-colors placeholder:text-gray-400"
49
+ className="h-8 w-full rounded-lg border border-border bg-background pl-8 pr-3 text-xs text-foreground transition-colors placeholder:text-muted-foreground/55 focus:border-primary/40 focus:outline-none focus:ring-1 focus:ring-primary/30"
50
50
  />
51
51
  </form>
52
52
  </div>
@@ -91,7 +91,7 @@ export function DocBrowserExternalLink({ currentUrl, isDocsTab }: { currentUrl:
91
91
  }
92
92
 
93
93
  return (
94
- <div className="flex items-center justify-between px-4 py-2 bg-gray-50 border-t border-gray-200 shrink-0">
94
+ <div className="flex items-center justify-between border-t border-border/70 bg-muted/55 px-4 py-2 shrink-0">
95
95
  <a
96
96
  href={currentUrl}
97
97
  target="_blank"
@@ -111,16 +111,16 @@ export function DocBrowserTabStrip({
111
111
  tabs={compactTabs}
112
112
  actions={actions}
113
113
  className={cn(
114
- "h-11 gap-2 px-2.5 bg-background border-[#f1e7d4] shrink-0 select-none",
114
+ "h-11 gap-2 border-border/70 bg-card px-2.5 shrink-0 select-none",
115
115
  isFullscreen && "h-[calc(env(safe-area-inset-top,0px)+2.75rem)] pt-[env(safe-area-inset-top,0px)]",
116
116
  )}
117
117
  scrollClassName="doc-browser-tab-scrollbar flex h-full items-center gap-1.5"
118
118
  tabsClassName="items-center gap-1.5"
119
119
  actionsClassName="h-full items-center gap-1"
120
- actionButtonClassName="rounded-md p-1.5 hover:text-gray-700 disabled:opacity-60"
120
+ actionButtonClassName="rounded-md p-1.5 hover:text-foreground disabled:opacity-60"
121
121
  tabBaseClassName="group inline-flex min-w-0 items-center gap-1 h-7 px-1.5 rounded-lg text-xs border max-w-[220px] shrink-0 transition-colors"
122
- activeTabClassName="bg-amber-50/80 border-amber-200 text-amber-900 shadow-[0_1px_2px_rgba(30,20,10,0.04)]"
123
- inactiveTabClassName="bg-[#f9f8f5] border-[#eee3d1] text-[#78644d] hover:bg-[#fff7ea] hover:text-[#2f2212]"
122
+ activeTabClassName="border-primary/30 bg-primary-50/70 text-foreground shadow-[0_1px_2px_rgba(30,20,10,0.04)]"
123
+ inactiveTabClassName="border-border bg-muted/60 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
124
124
  labelClassName="px-1 text-xs font-normal"
125
125
  onPointerDown={!isDocked && !isFullscreen ? onDragStart : undefined}
126
126
  onScrollPointerDown={(event) => event.stopPropagation()}
@@ -359,10 +359,10 @@ export function DocBrowser({ customTabRenderers = {}, displayMode = 'desktop', d
359
359
  <div
360
360
  data-testid="doc-browser-panel"
361
361
  className={cn(
362
- 'flex flex-col bg-white overflow-hidden relative',
362
+ 'relative flex flex-col overflow-hidden bg-card text-card-foreground',
363
363
  isFullscreen
364
364
  ? 'fixed inset-0 z-[9999] h-[100dvh] w-screen rounded-none border-0 shadow-2xl'
365
- : 'rounded-2xl shadow-2xl border border-gray-200',
365
+ : 'rounded-2xl border border-border shadow-2xl',
366
366
  )}
367
367
  style={
368
368
  isFullscreen
@@ -398,7 +398,7 @@ export function DocBrowser({ customTabRenderers = {}, displayMode = 'desktop', d
398
398
  onPointerDown={startFloatResize('bottom')}
399
399
  />
400
400
  <div
401
- className="absolute bottom-0 right-0 w-4 h-4 cursor-se-resize z-30 flex items-center justify-center text-gray-300 hover:text-gray-500 transition-colors"
401
+ className="absolute bottom-0 right-0 z-30 flex h-4 w-4 cursor-se-resize items-center justify-center text-muted-foreground/45 transition-colors hover:text-muted-foreground"
402
402
  data-testid="doc-browser-resize-bottom-right"
403
403
  onPointerDown={startFloatResize('bottom-right')}
404
404
  >
@@ -36,7 +36,7 @@ export function ResizableRightPanel({ children, className, style, defaultWidth =
36
36
  return (
37
37
  <aside
38
38
  {...props}
39
- className={cn("relative flex h-full min-h-0 shrink-0 overflow-hidden bg-white", overlay ? "fixed inset-0 z-40" : "border-l border-gray-200", className)}
39
+ className={cn("relative flex h-full min-h-0 shrink-0 overflow-hidden bg-card text-card-foreground", overlay ? "fixed inset-0 z-40" : "border-l border-border", className)}
40
40
  style={overlay ? style : { ...style, width }}
41
41
  >
42
42
  {!overlay ? (
@@ -27,7 +27,7 @@ const IconActionButton = React.forwardRef<HTMLButtonElement, IconActionButtonPro
27
27
  aria-label={label}
28
28
  className={cn(
29
29
  'inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 disabled:cursor-not-allowed',
30
- 'text-gray-500 hover:bg-gray-100 hover:text-gray-900 disabled:text-gray-300 disabled:hover:bg-transparent disabled:hover:text-gray-300',
30
+ 'text-muted-foreground hover:bg-accent hover:text-accent-foreground disabled:text-muted-foreground/45 disabled:hover:bg-transparent disabled:hover:text-muted-foreground/45',
31
31
  className
32
32
  )}
33
33
  >
@@ -9,12 +9,12 @@ const buttonVariants = cva(
9
9
  variant: {
10
10
  default: 'bg-primary text-primary-foreground hover:bg-primary-600 active:bg-primary-700 shadow-sm',
11
11
  destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
12
- outline: 'border border-gray-200 bg-white hover:bg-gray-50 hover:text-gray-800 text-gray-600',
13
- secondary: 'bg-gray-100 text-gray-700 hover:bg-gray-200/80',
14
- ghost: 'hover:bg-gray-100/80 hover:text-gray-800',
12
+ outline: 'border border-border bg-card text-muted-foreground hover:bg-accent hover:text-accent-foreground',
13
+ secondary: 'bg-muted text-foreground hover:bg-accent hover:text-accent-foreground',
14
+ ghost: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground',
15
15
  link: 'text-primary underline-offset-4 hover:underline',
16
16
  primary: 'bg-primary text-primary-foreground hover:bg-primary-600 active:bg-primary-700 shadow-sm',
17
- subtle: 'bg-gray-100 text-gray-600 hover:bg-gray-200/80',
17
+ subtle: 'bg-muted text-muted-foreground hover:bg-accent hover:text-accent-foreground',
18
18
  'primary-outline': 'border border-primary/30 text-primary hover:bg-primary hover:text-primary-foreground'
19
19
  },
20
20
  size: {
@@ -8,8 +8,8 @@ const Card = React.forwardRef<
8
8
  <div
9
9
  ref={ref}
10
10
  className={cn(
11
- 'rounded-2xl border border-gray-200/50 bg-white text-card-foreground shadow-card transition-all duration-base',
12
- hover && 'hover:shadow-card-hover hover:border-gray-200',
11
+ 'rounded-2xl border border-border bg-card text-card-foreground shadow-card transition-all duration-base',
12
+ hover && 'hover:border-primary/25 hover:shadow-card-hover',
13
13
  className
14
14
  )}
15
15
  {...props}
@@ -36,7 +36,7 @@ const CardTitle = React.forwardRef<
36
36
  <h3
37
37
  ref={ref}
38
38
  className={cn(
39
- 'text-[15px] font-semibold leading-tight tracking-tight text-gray-900',
39
+ 'text-[15px] font-semibold leading-tight tracking-tight text-foreground',
40
40
  className
41
41
  )}
42
42
  {...props}
@@ -50,7 +50,7 @@ const CardDescription = React.forwardRef<
50
50
  >(({ className, ...props }, ref) => (
51
51
  <p
52
52
  ref={ref}
53
- className={cn('text-[13px] text-gray-400 leading-relaxed', className)}
53
+ className={cn('text-[13px] text-muted-foreground leading-relaxed', className)}
54
54
  {...props}
55
55
  />
56
56
  ));
@@ -36,14 +36,14 @@ const DialogContent = React.forwardRef<
36
36
  <DialogPrimitive.Content
37
37
  ref={ref}
38
38
  className={cn(
39
- "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-gray-200/50 bg-white p-6 shadow-xl duration-base data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] rounded-2xl",
39
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 rounded-2xl border border-border bg-popover p-6 text-popover-foreground shadow-xl duration-base data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]",
40
40
  className
41
41
  )}
42
42
  {...props}
43
43
  >
44
44
  {children}
45
- <DialogPrimitive.Close className="absolute right-4 top-4 rounded-lg p-1 opacity-70 ring-offset-white transition-all duration-fast hover:opacity-100 hover:bg-gray-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 disabled:pointer-events-none">
46
- <X className="h-4 w-4 text-gray-500" />
45
+ <DialogPrimitive.Close className="absolute right-4 top-4 rounded-lg p-1 opacity-70 ring-offset-background transition-all duration-fast hover:bg-accent hover:text-accent-foreground hover:opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 disabled:pointer-events-none">
46
+ <X className="h-4 w-4 text-muted-foreground" />
47
47
  <span className="sr-only">Close</span>
48
48
  </DialogPrimitive.Close>
49
49
  </DialogPrimitive.Content>
@@ -86,7 +86,7 @@ const DialogTitle = React.forwardRef<
86
86
  <DialogPrimitive.Title
87
87
  ref={ref}
88
88
  className={cn(
89
- "text-lg font-semibold leading-tight tracking-tight text-gray-900",
89
+ "text-lg font-semibold leading-tight tracking-tight text-foreground",
90
90
  className
91
91
  )}
92
92
  {...props}
@@ -100,7 +100,7 @@ const DialogDescription = React.forwardRef<
100
100
  >(({ className, ...props }, ref) => (
101
101
  <DialogPrimitive.Description
102
102
  ref={ref}
103
- className={cn("text-sm text-gray-500 leading-relaxed", className)}
103
+ className={cn("text-sm text-muted-foreground leading-relaxed", className)}
104
104
  {...props}
105
105
  />
106
106
  ))
@@ -9,7 +9,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
9
9
  <input
10
10
  type={type}
11
11
  className={cn(
12
- 'flex h-9 w-full rounded-xl border border-gray-200/80 bg-white px-3.5 py-2 text-sm text-gray-900 file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-gray-300 placeholder:font-normal focus:outline-none focus:ring-1 focus:ring-primary/40 focus:border-primary/40 transition-colors disabled:cursor-not-allowed disabled:opacity-50',
12
+ 'flex h-9 w-full rounded-xl border border-border bg-card px-3.5 py-2 text-sm text-foreground file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground/55 placeholder:font-normal focus:outline-none focus:ring-1 focus:ring-primary/40 focus:border-primary/50 transition-colors disabled:cursor-not-allowed disabled:opacity-50',
13
13
  className
14
14
  )}
15
15
  ref={ref}
@@ -8,7 +8,7 @@ const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
8
8
  <label
9
9
  ref={ref}
10
10
  className={cn(
11
- 'text-sm font-medium leading-none text-gray-700 peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
11
+ 'text-sm font-medium leading-none text-foreground peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
12
12
  className
13
13
  )}
14
14
  {...props}
@@ -28,7 +28,7 @@ const PopoverContent = React.forwardRef<
28
28
  align={align}
29
29
  collisionPadding={collisionPadding}
30
30
  className={cn(
31
- 'z-[var(--z-popover,50)] w-72 overflow-x-hidden overflow-y-auto rounded-2xl border border-gray-200/50 bg-white p-4 shadow-lg animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
31
+ 'z-[var(--z-popover,50)] w-72 overflow-x-hidden overflow-y-auto rounded-2xl border border-border bg-popover p-4 text-popover-foreground shadow-lg animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
32
32
  className
33
33
  )}
34
34
  style={{ maxHeight: POPOVER_CONTENT_MAX_HEIGHT, ...style }}
@@ -25,7 +25,7 @@ const SelectTrigger = React.forwardRef<
25
25
  <SelectPrimitive.Trigger
26
26
  ref={ref}
27
27
  className={cn(
28
- "flex h-9 w-full items-center justify-between whitespace-nowrap rounded-xl border border-gray-200/80 bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary/40 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 bg-white",
28
+ "flex h-9 w-full items-center justify-between whitespace-nowrap rounded-xl border border-border bg-card px-3 py-2 text-sm text-foreground shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary/40 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
29
29
  className
30
30
  )}
31
31
  {...props}
@@ -78,7 +78,7 @@ const SelectContent = React.forwardRef<
78
78
  <SelectPrimitive.Content
79
79
  ref={ref}
80
80
  className={cn(
81
- "relative z-[var(--z-popover,50)] flex max-h-96 min-w-[8rem] flex-col overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 bg-white",
81
+ "relative z-[var(--z-popover,50)] flex max-h-96 min-w-[8rem] flex-col overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
82
82
  position === "popper" &&
83
83
  "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
84
84
  className
@@ -119,7 +119,7 @@ const SelectItem = React.forwardRef<
119
119
  <SelectPrimitive.Item
120
120
  ref={ref}
121
121
  className={cn(
122
- "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-gray-100 focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 hover:bg-gray-100",
122
+ "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 hover:bg-accent",
123
123
  className
124
124
  )}
125
125
  {...props}
@@ -16,8 +16,8 @@ const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>(
16
16
  aria-checked={checked}
17
17
  ref={ref}
18
18
  className={cn(
19
- 'peer inline-flex h-[22px] w-10 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors duration-fast focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-white disabled:cursor-not-allowed disabled:opacity-50',
20
- checked ? 'bg-primary' : 'bg-gray-200/80 hover:bg-gray-300/80',
19
+ 'peer inline-flex h-[22px] w-10 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors duration-fast focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50',
20
+ checked ? 'bg-primary' : 'bg-muted hover:bg-accent',
21
21
  className
22
22
  )}
23
23
  onClick={() => onCheckedChange?.(!checked)}
@@ -26,7 +26,7 @@ const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>(
26
26
  <span
27
27
  data-state={checked ? 'checked' : 'unchecked'}
28
28
  className={cn(
29
- 'pointer-events-none block h-5 w-5 rounded-full bg-white shadow-md ring-0 transition-transform duration-fast',
29
+ 'pointer-events-none block h-5 w-5 rounded-full bg-card shadow-md ring-0 transition-transform duration-fast',
30
30
  checked ? 'translate-x-5' : 'translate-x-0',
31
31
  thumbClassName
32
32
  )}
@@ -42,7 +42,7 @@ export function TabsList({ children, className }: TabsListProps) {
42
42
  return (
43
43
  <div
44
44
  className={cn(
45
- 'inline-flex h-9 items-center justify-center rounded-xl bg-gray-100/80 p-1 text-gray-500',
45
+ 'inline-flex h-9 items-center justify-center rounded-xl bg-muted p-1 text-muted-foreground',
46
46
  className
47
47
  )}
48
48
  >
@@ -64,10 +64,10 @@ export function TabsTrigger({ value, children, className }: TabsTriggerProps) {
64
64
  aria-pressed={isActive}
65
65
  data-state={isActive ? 'active' : 'inactive'}
66
66
  className={cn(
67
- 'inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-[13px] font-medium ring-offset-white transition-all duration-fast focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
67
+ 'inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-[13px] font-medium ring-offset-background transition-all duration-fast focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
68
68
  isActive
69
- ? 'bg-white text-gray-900 shadow-sm'
70
- : 'hover:bg-white/50 hover:text-gray-800 text-gray-600',
69
+ ? 'bg-card text-foreground shadow-sm'
70
+ : 'text-muted-foreground hover:bg-accent hover:text-accent-foreground',
71
71
  className
72
72
  )}
73
73
  >
@@ -7,7 +7,7 @@ export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
7
7
  ({ className, ...props }, ref) => (
8
8
  <textarea
9
9
  className={cn(
10
- "flex min-h-28 w-full rounded-xl border border-gray-200/80 bg-white px-3.5 py-2.5 text-sm text-gray-900 placeholder:text-gray-300 placeholder:font-normal focus:outline-none focus:ring-1 focus:ring-primary/40 focus:border-primary/40 transition-colors disabled:cursor-not-allowed disabled:opacity-50",
10
+ "flex min-h-28 w-full rounded-xl border border-border bg-card px-3.5 py-2.5 text-sm text-foreground placeholder:text-muted-foreground/55 placeholder:font-normal focus:outline-none focus:ring-1 focus:ring-primary/40 focus:border-primary/50 transition-colors disabled:cursor-not-allowed disabled:opacity-50",
11
11
  className,
12
12
  )}
13
13
  ref={ref}
@@ -39,8 +39,13 @@
39
39
  "noneOption": "None",
40
40
  "language": "Language",
41
41
  "theme": "Theme",
42
- "themeWarm": "Warm",
43
- "themeCool": "Cool",
42
+ "themeNatural": "Natural",
43
+ "themeMinimal": "Minimal",
44
+ "themeWarm": "Paper",
45
+ "themeCool": "Mist",
46
+ "themeDawn": "Dawn",
47
+ "themeGraphite": "Graphite",
48
+ "themeProbe": "Probe",
44
49
  "isRequired": "is required",
45
50
  "duplicate": "duplicate",
46
51
  "notFound": "not found",
@@ -39,8 +39,13 @@
39
39
  "noneOption": "无",
40
40
  "language": "语言",
41
41
  "theme": "主题",
42
- "themeWarm": "暖色",
43
- "themeCool": "冷色",
42
+ "themeNatural": "自然",
43
+ "themeMinimal": "简白",
44
+ "themeWarm": "纸暖",
45
+ "themeCool": "雾蓝",
46
+ "themeDawn": "晨砂",
47
+ "themeGraphite": "石墨",
48
+ "themeProbe": "探针",
44
49
  "isRequired": "必填",
45
50
  "duplicate": "重复",
46
51
  "notFound": "未找到",
@@ -1,80 +1,122 @@
1
- export type UiTheme = 'warm' | 'cool';
1
+ export type UiTheme = 'natural' | 'minimal' | 'warm' | 'cool' | 'dawn' | 'graphite' | 'probe';
2
2
 
3
3
  const THEME_STORAGE_KEY = 'nextclaw.ui.theme';
4
+ const DEFAULT_THEME: UiTheme = 'natural';
5
+ const THEME_VALUES: readonly UiTheme[] = ['natural', 'minimal', 'warm', 'cool', 'dawn', 'graphite', 'probe'];
4
6
 
5
7
  export const THEME_OPTIONS: Array<{ value: UiTheme; labelKey: string }> = [
8
+ { value: 'natural', labelKey: 'themeNatural' },
9
+ { value: 'minimal', labelKey: 'themeMinimal' },
6
10
  { value: 'warm', labelKey: 'themeWarm' },
7
- { value: 'cool', labelKey: 'themeCool' }
11
+ { value: 'cool', labelKey: 'themeCool' },
12
+ { value: 'dawn', labelKey: 'themeDawn' },
13
+ { value: 'graphite', labelKey: 'themeGraphite' },
14
+ { value: 'probe', labelKey: 'themeProbe' }
8
15
  ];
9
16
 
10
- let activeTheme: UiTheme = 'warm';
11
- let initialized = false;
12
- const listeners = new Set<(theme: UiTheme) => void>();
13
-
14
- function isTheme(value: unknown): value is UiTheme {
15
- return value === 'warm' || value === 'cool';
16
- }
17
+ export function normalizeTheme(value: unknown): UiTheme | null {
18
+ if (typeof value !== 'string') {
19
+ return null;
20
+ }
17
21
 
18
- function applyThemeAttribute(theme: UiTheme): void {
19
- if (typeof document === 'undefined') {
20
- return;
22
+ if ((THEME_VALUES as readonly string[]).includes(value)) {
23
+ return value as UiTheme;
21
24
  }
22
- document.documentElement.setAttribute('data-theme', theme);
25
+
26
+ return value === 'leaf' ? 'warm' : null;
23
27
  }
24
28
 
25
- export function resolveInitialTheme(): UiTheme {
26
- if (typeof window === 'undefined') {
27
- return 'warm';
28
- }
29
+ class UiThemeOwner {
30
+ private activeTheme: UiTheme = DEFAULT_THEME;
31
+ private initialized = false;
32
+ private readonly listeners = new Set<(theme: UiTheme) => void>();
29
33
 
30
- try {
31
- const saved = window.localStorage.getItem(THEME_STORAGE_KEY);
32
- if (isTheme(saved)) {
33
- return saved;
34
+ resolveInitialTheme = (): UiTheme => {
35
+ if (typeof window === 'undefined') {
36
+ return DEFAULT_THEME;
34
37
  }
35
- } catch {
36
- // ignore storage failures
37
- }
38
38
 
39
- return 'warm';
40
- }
39
+ try {
40
+ const saved = window.localStorage.getItem(THEME_STORAGE_KEY);
41
+ const theme = normalizeTheme(saved);
42
+ if (theme) {
43
+ return theme;
44
+ }
45
+ } catch {
46
+ // ignore storage failures
47
+ }
41
48
 
42
- export function initializeTheme(): UiTheme {
43
- if (!initialized) {
44
- activeTheme = resolveInitialTheme();
45
- applyThemeAttribute(activeTheme);
46
- initialized = true;
47
- }
48
- return activeTheme;
49
- }
49
+ return DEFAULT_THEME;
50
+ };
50
51
 
51
- export function getTheme(): UiTheme {
52
- return initialized ? activeTheme : initializeTheme();
53
- }
52
+ initializeTheme = (): UiTheme => {
53
+ if (!this.initialized) {
54
+ this.activeTheme = this.resolveInitialTheme();
55
+ this.applyThemeAttribute(this.activeTheme);
56
+ this.initialized = true;
57
+ }
58
+ return this.activeTheme;
59
+ };
54
60
 
55
- export function setTheme(theme: UiTheme): void {
56
- initializeTheme();
57
- if (theme === activeTheme) {
58
- return;
59
- }
61
+ getTheme = (): UiTheme => this.initialized ? this.activeTheme : this.initializeTheme();
60
62
 
61
- activeTheme = theme;
62
- applyThemeAttribute(activeTheme);
63
+ setTheme = (theme: UiTheme): void => {
64
+ this.initializeTheme();
65
+ if (theme === this.activeTheme) {
66
+ return;
67
+ }
68
+
69
+ this.activeTheme = theme;
70
+ this.applyThemeAttribute(this.activeTheme);
71
+ this.saveTheme(this.activeTheme);
72
+ this.listeners.forEach((listener) => listener(this.activeTheme));
73
+ };
74
+
75
+ subscribeThemeChange = (listener: (theme: UiTheme) => void): (() => void) => {
76
+ this.listeners.add(listener);
77
+ return () => {
78
+ this.listeners.delete(listener);
79
+ };
80
+ };
81
+
82
+ private applyThemeAttribute = (theme: UiTheme): void => {
83
+ if (typeof document === 'undefined') {
84
+ return;
85
+ }
86
+ document.documentElement.setAttribute('data-theme', theme);
87
+ };
88
+
89
+ private saveTheme = (theme: UiTheme): void => {
90
+ if (typeof window === 'undefined') {
91
+ return;
92
+ }
63
93
 
64
- if (typeof window !== 'undefined') {
65
94
  try {
66
- window.localStorage.setItem(THEME_STORAGE_KEY, activeTheme);
95
+ window.localStorage.setItem(THEME_STORAGE_KEY, theme);
67
96
  } catch {
68
97
  // ignore storage failures
69
98
  }
70
- }
99
+ };
100
+ }
101
+
102
+ const uiThemeOwner = new UiThemeOwner();
71
103
 
72
- listeners.forEach((listener) => listener(activeTheme));
104
+ export function resolveInitialTheme(): UiTheme {
105
+ return uiThemeOwner.resolveInitialTheme();
106
+ }
107
+
108
+ export function initializeTheme(): UiTheme {
109
+ return uiThemeOwner.initializeTheme();
110
+ }
111
+
112
+ export function getTheme(): UiTheme {
113
+ return uiThemeOwner.getTheme();
114
+ }
115
+
116
+ export function setTheme(theme: UiTheme): void {
117
+ uiThemeOwner.setTheme(theme);
73
118
  }
74
119
 
75
120
  export function subscribeThemeChange(listener: (theme: UiTheme) => void): () => void {
76
- listeners.add(listener);
77
- return () => {
78
- listeners.delete(listener);
79
- };
121
+ return uiThemeOwner.subscribeThemeChange(listener);
80
122
  }