@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.
- package/CHANGELOG.md +12 -0
- package/dist/assets/{api-a14tR_z1.js → api-BP_8IaxB.js} +3 -3
- package/dist/assets/{channels-list-page-CFGkm4DT.js → channels-list-page-DEh5Jjqx.js} +1 -1
- package/dist/assets/chat-page-BRB0N5WW.js +105 -0
- package/dist/assets/config-split-page-CRhlZ_pH.js +1 -0
- package/dist/assets/{confirm-dialog-ClH_Ec7n.js → confirm-dialog-DsivFJ4r.js} +2 -2
- package/dist/assets/{desktop-update-config-DPNDnVpk.js → desktop-update-config-B0w9p2ep.js} +1 -1
- package/dist/assets/{dist-BtCS2Ucq.js → dist-CikkhOlq.js} +1 -1
- package/dist/assets/{dist-CLGGLm8b.js → dist-PVA_xL1D.js} +1 -1
- package/dist/assets/doc-browser-BoNJ7-tE.js +1 -0
- package/dist/assets/doc-browser-CdJ_d4ik.js +1 -0
- package/dist/assets/{doc-browser-context-D4JwMMxK.js → doc-browser-context-MTw4EvKd.js} +1 -1
- package/dist/assets/{ellipsis-CtJXjIol.js → ellipsis-y6vDtq4M.js} +1 -1
- package/dist/assets/{external-link-CdJiG13p.js → external-link-DVJIQf_G.js} +1 -1
- package/dist/assets/index-BiE-K8-q.css +1 -0
- package/dist/assets/index-D9BAE_L_.js +105 -0
- package/dist/assets/{mcp-marketplace-page-BU0kIjhR.js → mcp-marketplace-page-B1SZNY5m.js} +1 -1
- package/dist/assets/mcp-marketplace-page-Cq_Tw42T.js +1 -0
- package/dist/assets/{model-config-page-D6hYXROd.js → model-config-page-DotaTUHb.js} +1 -1
- package/dist/assets/plus-BbZqquJe.js +1 -0
- package/dist/assets/{provider-scoped-model-input-DAX2HgmR.js → provider-scoped-model-input-DsFRuWJH.js} +1 -1
- package/dist/assets/providers-config-page-DW4tQSLN.js +1 -0
- package/dist/assets/{react-C6cZgP44.js → react-VNqT5Zp7.js} +1 -1
- package/dist/assets/remote-B8TiUfwW.js +1 -0
- package/dist/assets/rotate-cw-DjHDTkCv.js +1 -0
- package/dist/assets/{runtime-config-page-C5xKaV3i.js → runtime-config-page-S6ErRHR8.js} +1 -1
- package/dist/assets/{save-DKA0x4zk.js → save-BOJ477H-.js} +1 -1
- package/dist/assets/{search-BNlcoj5c.js → search-BEti5beA.js} +1 -1
- package/dist/assets/{search-config-page-Cq9uJ-N8.js → search-config-page-DGj5am03.js} +1 -1
- package/dist/assets/{secrets-config-page-CxSbZLBo.js → secrets-config-page--IzrIOid.js} +1 -1
- package/dist/assets/{select-CvTlDZTQ.js → select-hoj8dK4K.js} +2 -2
- package/dist/assets/{tag-chip-8ffF2y2C.js → tag-chip-T8-h-die.js} +1 -1
- package/dist/assets/{use-config-DmT0o1gv.js → use-config-CjRV2hwL.js} +1 -1
- package/dist/assets/x-D9PXs_xP.js +1 -0
- package/dist/index.html +69 -19
- package/index.html +55 -5
- package/package.json +7 -7
- package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +3 -3
- package/src/app/components/layout/header.tsx +3 -3
- package/src/app/components/layout/page-layout.tsx +2 -2
- package/src/app/components/layout/runtime-status-entry.tsx +7 -7
- package/src/app/components/layout/sidebar-items.tsx +10 -10
- package/src/app/components/layout/sidebar-rail.styles.ts +2 -2
- package/src/app/components/layout/sidebar.tsx +6 -6
- package/src/app/styles/design-system.css +388 -26
- package/src/app/test/vitest-setup.ts +31 -0
- package/src/features/chat/components/chat-sidebar-list-mode-switch.tsx +3 -3
- package/src/features/chat/components/conversation/__tests__/chat-conversation-header.test.tsx +4 -2
- package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +1 -1
- package/src/features/chat/components/conversation/chat-conversation-header.tsx +7 -6
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +1 -1
- package/src/features/chat/components/conversation/chat-conversation-skeleton.tsx +13 -13
- package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +2 -2
- package/src/features/chat/components/layout/chat-page-shell.tsx +1 -1
- package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +3 -3
- package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +14 -14
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +15 -15
- package/src/features/chat/components/layout/chat-sidebar.tsx +3 -3
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +225 -0
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +199 -0
- package/src/features/chat/features/conversation/components/session-conversation-area.tsx +28 -9
- package/src/features/chat/features/conversation/components/session-conversation-input.tsx +18 -6
- package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +3 -13
- package/src/features/chat/features/conversation/hooks/use-session-conversation-input-query.ts +19 -3
- package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +7 -7
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +6 -6
- package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +5 -5
- package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +4 -4
- package/src/features/chat/features/session/components/session-context-icon.tsx +2 -2
- package/src/features/chat/features/session/components/session-header/__tests__/chat-session-project-badge.test.tsx +2 -2
- package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +1 -1
- package/src/features/chat/features/session/components/session-header/chat-session-header-menu-item.tsx +1 -1
- package/src/features/chat/features/session/components/session-header/chat-session-metadata-dialog.tsx +1 -1
- package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +3 -3
- package/src/features/chat/features/session/components/session-run-badge.tsx +1 -1
- package/src/features/chat/features/session-type/hooks/use-chat-session-type-state.ts +10 -2
- package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +0 -7
- package/src/features/chat/features/welcome/components/chat-welcome-agent-picker.tsx +3 -3
- package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +4 -4
- package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +14 -19
- package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +3 -3
- package/src/features/chat/features/welcome/components/chat-welcome.tsx +4 -4
- package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-project-options.utils.test.ts +6 -8
- package/src/features/chat/features/welcome/utils/chat-welcome-project-options.utils.ts +1 -16
- package/src/features/panel-apps/components/panel-app-list-item.tsx +8 -8
- package/src/features/panel-apps/components/panel-app-toolbar.tsx +4 -4
- package/src/features/panel-apps/components/panel-apps-list.tsx +20 -20
- package/src/features/pwa/managers/__tests__/pwa-shell-theme.manager.test.ts +70 -6
- package/src/features/pwa/managers/pwa-shell-theme.manager.ts +10 -5
- package/src/features/service-apps/components/service-action-authorization-dialog.tsx +8 -8
- package/src/features/service-apps/components/service-apps-panel.tsx +24 -24
- package/src/features/settings/components/config/provider-advanced-settings-section.tsx +5 -5
- package/src/features/settings/components/config/provider-auth-section.tsx +7 -7
- package/src/features/settings/components/config/provider-form.tsx +10 -10
- package/src/features/settings/components/config/provider-models-section.tsx +16 -16
- package/src/features/settings/components/config/provider-pill-selector.tsx +1 -1
- package/src/features/settings/pages/providers-config-page.tsx +17 -17
- package/src/shared/components/config-split-page.tsx +11 -11
- package/src/shared/components/doc-browser/doc-browser-home-page.tsx +2 -2
- package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +4 -4
- package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +4 -4
- package/src/shared/components/doc-browser/doc-browser.tsx +3 -3
- package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +1 -1
- package/src/shared/components/ui/actions/icon-action-button.tsx +1 -1
- package/src/shared/components/ui/button.tsx +4 -4
- package/src/shared/components/ui/card.tsx +4 -4
- package/src/shared/components/ui/dialog.tsx +5 -5
- package/src/shared/components/ui/input.tsx +1 -1
- package/src/shared/components/ui/label.tsx +1 -1
- package/src/shared/components/ui/popover.tsx +1 -1
- package/src/shared/components/ui/select.tsx +3 -3
- package/src/shared/components/ui/switch.tsx +3 -3
- package/src/shared/components/ui/tabs.tsx +4 -4
- package/src/shared/components/ui/textarea.tsx +1 -1
- package/src/shared/lib/i18n/locales/en-US/core.json +7 -2
- package/src/shared/lib/i18n/locales/zh-CN/core.json +7 -2
- package/src/shared/lib/theme/index.ts +94 -52
- package/dist/assets/chat-page-CEdavJXc.js +0 -105
- package/dist/assets/config-split-page-B5b9B0uE.js +0 -1
- package/dist/assets/doc-browser-5nMWYKBD.js +0 -1
- package/dist/assets/doc-browser-BJRLhztv.js +0 -1
- package/dist/assets/index-BDN1zzMQ.js +0 -105
- package/dist/assets/index-g3uSZXoc.css +0 -1
- package/dist/assets/mcp-marketplace-page-sMNFSq_T.js +0 -1
- package/dist/assets/plus-DPjb0piO.js +0 -1
- package/dist/assets/providers-config-page-BzAeaACA.js +0 -1
- package/dist/assets/remote-B0G80uPr.js +0 -1
- package/dist/assets/rotate-cw-DnEB1Rju.js +0 -1
- 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
|
|
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="
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
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-
|
|
123
|
-
inactiveTabClassName="
|
|
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-
|
|
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
|
|
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
|
|
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-
|
|
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-
|
|
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-
|
|
13
|
-
secondary: 'bg-
|
|
14
|
-
ghost: 'hover:bg-
|
|
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-
|
|
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-
|
|
12
|
-
hover && 'hover:
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
46
|
-
<X className="h-4 w-4 text-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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-
|
|
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-
|
|
20
|
-
checked ? 'bg-primary' : 'bg-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
70
|
-
: 'hover:bg-
|
|
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-
|
|
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
|
-
"
|
|
43
|
-
"
|
|
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
|
-
"
|
|
43
|
-
"
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
return;
|
|
22
|
+
if ((THEME_VALUES as readonly string[]).includes(value)) {
|
|
23
|
+
return value as UiTheme;
|
|
21
24
|
}
|
|
22
|
-
|
|
25
|
+
|
|
26
|
+
return value === 'leaf' ? 'warm' : null;
|
|
23
27
|
}
|
|
24
28
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
activeTheme = resolveInitialTheme();
|
|
45
|
-
applyThemeAttribute(activeTheme);
|
|
46
|
-
initialized = true;
|
|
47
|
-
}
|
|
48
|
-
return activeTheme;
|
|
49
|
-
}
|
|
49
|
+
return DEFAULT_THEME;
|
|
50
|
+
};
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
56
|
-
initializeTheme();
|
|
57
|
-
if (theme === activeTheme) {
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
61
|
+
getTheme = (): UiTheme => this.initialized ? this.activeTheme : this.initializeTheme();
|
|
60
62
|
|
|
61
|
-
|
|
62
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
77
|
-
return () => {
|
|
78
|
-
listeners.delete(listener);
|
|
79
|
-
};
|
|
121
|
+
return uiThemeOwner.subscribeThemeChange(listener);
|
|
80
122
|
}
|