@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
|
@@ -39,7 +39,7 @@ export function ChatWelcomeSessionTypePicker({
|
|
|
39
39
|
<PopoverTrigger asChild>
|
|
40
40
|
<button
|
|
41
41
|
type="button"
|
|
42
|
-
className="inline-flex min-w-0 max-w-full items-center gap-1.5 rounded-lg px-2 py-1.5 text-left font-medium text-
|
|
42
|
+
className="inline-flex min-w-0 max-w-full items-center gap-1.5 rounded-lg px-2 py-1.5 text-left font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/25"
|
|
43
43
|
aria-label={t('chatWelcomeSessionTypePickerLabel')}
|
|
44
44
|
>
|
|
45
45
|
{selectedOption.icon?.src ? (
|
|
@@ -56,12 +56,12 @@ export function ChatWelcomeSessionTypePicker({
|
|
|
56
56
|
<Bot className="h-4 w-4 shrink-0" />
|
|
57
57
|
)}
|
|
58
58
|
<span className="truncate">{selectedOption.label}</span>
|
|
59
|
-
<ChevronDown className="h-3.5 w-3.5 shrink-0 text-
|
|
59
|
+
<ChevronDown className="h-3.5 w-3.5 shrink-0 text-muted-foreground/70" />
|
|
60
60
|
</button>
|
|
61
61
|
</PopoverTrigger>
|
|
62
62
|
<PopoverContent
|
|
63
63
|
align="start"
|
|
64
|
-
className="flex w-[min(16rem,calc(100vw-1rem))] flex-col overflow-hidden rounded-2xl border border-
|
|
64
|
+
className="flex w-[min(16rem,calc(100vw-1rem))] flex-col overflow-hidden rounded-2xl border border-border bg-popover p-1.5 text-popover-foreground shadow-[0_24px_60px_-28px_rgba(15,23,42,0.38)]"
|
|
65
65
|
style={{ maxHeight: SESSION_TYPE_PICKER_MAX_HEIGHT }}
|
|
66
66
|
>
|
|
67
67
|
<div className="min-h-0 flex-1 overflow-y-auto">
|
|
@@ -64,22 +64,22 @@ export function ChatWelcome({
|
|
|
64
64
|
return (
|
|
65
65
|
<div className="flex min-h-full items-center justify-center px-4 py-8 sm:p-8">
|
|
66
66
|
<div className="min-w-0 w-full max-w-[min(760px,100%)]">
|
|
67
|
-
<div className="mx-auto mb-5 flex h-14 w-14 items-center justify-center rounded-2xl bg-
|
|
67
|
+
<div className="mx-auto mb-5 flex h-14 w-14 items-center justify-center rounded-2xl border border-border bg-accent shadow-card">
|
|
68
68
|
<Bot className="h-8 w-8 text-primary" />
|
|
69
69
|
</div>
|
|
70
70
|
|
|
71
71
|
<div className="text-center">
|
|
72
|
-
<h2 className="text-2xl font-semibold text-
|
|
72
|
+
<h2 className="text-2xl font-semibold text-foreground sm:text-3xl">
|
|
73
73
|
{t('chatWelcomeTitle')}
|
|
74
74
|
</h2>
|
|
75
|
-
<p className="mt-3 text-sm font-medium text-
|
|
75
|
+
<p className="mt-3 text-sm font-medium text-muted-foreground sm:text-base">
|
|
76
76
|
{t('chatWelcomeSubtitle')}
|
|
77
77
|
</p>
|
|
78
78
|
</div>
|
|
79
79
|
|
|
80
80
|
{inputSlot ? <div className="mt-8">{inputSlot}</div> : null}
|
|
81
81
|
|
|
82
|
-
<div className="mt-3 flex flex-wrap items-center gap-2 px-1 text-sm text-
|
|
82
|
+
<div className="mt-3 flex flex-wrap items-center gap-2 px-1 text-sm text-muted-foreground">
|
|
83
83
|
{onSelectProjectRoot ? (
|
|
84
84
|
<ChatWelcomeProjectPicker
|
|
85
85
|
defaultProjectRoot={defaultProjectRoot}
|
|
@@ -18,10 +18,15 @@ function createSummary(
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
describe("buildChatWelcomeProjectOptions", () => {
|
|
21
|
-
it("builds recent project options
|
|
21
|
+
it("builds recent project options without treating the default workspace as a project", () => {
|
|
22
22
|
const options = buildChatWelcomeProjectOptions({
|
|
23
23
|
defaultProjectRoot: "/Users/demo/.nextclaw/workspace",
|
|
24
24
|
sessionSummaries: [
|
|
25
|
+
createSummary({
|
|
26
|
+
sessionId: "session-default",
|
|
27
|
+
lastMessageAt: "2026-06-12T10:00:00.000Z",
|
|
28
|
+
metadata: { project_root: "/Users/demo/.nextclaw/workspace" },
|
|
29
|
+
}),
|
|
25
30
|
createSummary({
|
|
26
31
|
sessionId: "session-1",
|
|
27
32
|
lastMessageAt: "2026-06-10T10:00:00.000Z",
|
|
@@ -36,17 +41,10 @@ describe("buildChatWelcomeProjectOptions", () => {
|
|
|
36
41
|
});
|
|
37
42
|
|
|
38
43
|
expect(options).toEqual([
|
|
39
|
-
{
|
|
40
|
-
projectRoot: "/Users/demo/.nextclaw/workspace",
|
|
41
|
-
projectName: "workspace",
|
|
42
|
-
sessionCount: 0,
|
|
43
|
-
isDefault: true,
|
|
44
|
-
},
|
|
45
44
|
{
|
|
46
45
|
projectRoot: "/tmp/project-alpha",
|
|
47
46
|
projectName: "project-alpha",
|
|
48
47
|
sessionCount: 2,
|
|
49
|
-
isDefault: false,
|
|
50
48
|
},
|
|
51
49
|
]);
|
|
52
50
|
});
|
|
@@ -6,7 +6,6 @@ export type ChatWelcomeProjectOption = {
|
|
|
6
6
|
projectRoot: string;
|
|
7
7
|
projectName: string;
|
|
8
8
|
sessionCount: number;
|
|
9
|
-
isDefault: boolean;
|
|
10
9
|
};
|
|
11
10
|
|
|
12
11
|
function readSessionActivityAt(session: {
|
|
@@ -28,7 +27,7 @@ export function buildChatWelcomeProjectOptions(params: {
|
|
|
28
27
|
|
|
29
28
|
for (const session of adaptNcpSessionSummaries([...sessionSummaries])) {
|
|
30
29
|
const projectRoot = session.projectRoot?.trim();
|
|
31
|
-
if (!projectRoot) {
|
|
30
|
+
if (!projectRoot || projectRoot === defaultProjectRoot) {
|
|
32
31
|
continue;
|
|
33
32
|
}
|
|
34
33
|
const existing = groups.get(projectRoot);
|
|
@@ -45,26 +44,12 @@ export function buildChatWelcomeProjectOptions(params: {
|
|
|
45
44
|
getSessionProjectName(projectRoot) ||
|
|
46
45
|
projectRoot,
|
|
47
46
|
sessionCount: 1,
|
|
48
|
-
isDefault: projectRoot === defaultProjectRoot,
|
|
49
47
|
latestUpdatedAt,
|
|
50
48
|
});
|
|
51
49
|
}
|
|
52
50
|
|
|
53
|
-
if (defaultProjectRoot && !groups.has(defaultProjectRoot)) {
|
|
54
|
-
groups.set(defaultProjectRoot, {
|
|
55
|
-
projectRoot: defaultProjectRoot,
|
|
56
|
-
projectName: getSessionProjectName(defaultProjectRoot) ?? defaultProjectRoot,
|
|
57
|
-
sessionCount: 0,
|
|
58
|
-
isDefault: true,
|
|
59
|
-
latestUpdatedAt: Number.POSITIVE_INFINITY,
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
51
|
return [...groups.values()]
|
|
64
52
|
.sort((left, right) => {
|
|
65
|
-
if (left.isDefault !== right.isDefault) {
|
|
66
|
-
return left.isDefault ? -1 : 1;
|
|
67
|
-
}
|
|
68
53
|
return right.latestUpdatedAt - left.latestUpdatedAt;
|
|
69
54
|
})
|
|
70
55
|
.map(({ latestUpdatedAt: _latestUpdatedAt, ...option }) => option);
|
|
@@ -38,26 +38,26 @@ export function PanelAppListItem({
|
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
return (
|
|
41
|
-
<div className="group w-full min-w-0 rounded-lg border border-
|
|
41
|
+
<div className="group w-full min-w-0 rounded-lg border border-border bg-card px-2.5 py-2.5 transition-colors hover:border-primary/35 hover:bg-accent/60">
|
|
42
42
|
<div className="flex min-w-0 items-start gap-2">
|
|
43
43
|
<button type="button" onClick={onOpen} className="min-w-0 flex-1 text-left">
|
|
44
44
|
<span className="flex min-w-0 items-center gap-2">
|
|
45
|
-
<span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-
|
|
45
|
+
<span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-accent text-base text-accent-foreground">
|
|
46
46
|
<PanelAppIcon icon={entry.icon} title={entry.title} />
|
|
47
47
|
</span>
|
|
48
|
-
<span className="block min-w-0 flex-1 truncate text-sm font-medium text-
|
|
48
|
+
<span className="block min-w-0 flex-1 truncate text-sm font-medium text-foreground">{entry.title}</span>
|
|
49
49
|
</span>
|
|
50
50
|
{entry.description ? (
|
|
51
|
-
<span className="mt-1.5 block truncate text-xs leading-5 text-
|
|
51
|
+
<span className="mt-1.5 block truncate text-xs leading-5 text-muted-foreground">{entry.description}</span>
|
|
52
52
|
) : null}
|
|
53
|
-
<span className="mt-0.5 block truncate text-[11px] leading-4 text-
|
|
53
|
+
<span className="mt-0.5 block truncate text-[11px] leading-4 text-muted-foreground/70">{secondary}</span>
|
|
54
54
|
</button>
|
|
55
55
|
<div className="flex shrink-0 items-center gap-1">
|
|
56
56
|
<button
|
|
57
57
|
type="button"
|
|
58
58
|
onClick={handleFavorite}
|
|
59
59
|
disabled={favoritePending}
|
|
60
|
-
className="rounded-md p-1.5 text-
|
|
60
|
+
className="rounded-md p-1.5 text-muted-foreground/70 transition-colors hover:bg-background hover:text-amber-500 disabled:opacity-50"
|
|
61
61
|
title={favoriteLabel}
|
|
62
62
|
aria-label={favoriteLabel}
|
|
63
63
|
>
|
|
@@ -67,7 +67,7 @@ export function PanelAppListItem({
|
|
|
67
67
|
<PopoverTrigger asChild>
|
|
68
68
|
<button
|
|
69
69
|
type="button"
|
|
70
|
-
className="rounded-md p-1.5 text-
|
|
70
|
+
className="rounded-md p-1.5 text-muted-foreground/70 transition-colors hover:bg-background hover:text-foreground"
|
|
71
71
|
aria-label={t('panelAppsMoreActions')}
|
|
72
72
|
disabled={deletePending}
|
|
73
73
|
>
|
|
@@ -118,7 +118,7 @@ function PanelAppMenuItem({
|
|
|
118
118
|
type="button"
|
|
119
119
|
className={cn(
|
|
120
120
|
'flex w-full items-center gap-2 rounded-lg px-2.5 py-2 text-left text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-50',
|
|
121
|
-
destructive ? 'text-destructive hover:bg-destructive/10' : 'text-
|
|
121
|
+
destructive ? 'text-destructive hover:bg-destructive/10' : 'text-muted-foreground hover:bg-accent hover:text-accent-foreground',
|
|
122
122
|
)}
|
|
123
123
|
disabled={disabled}
|
|
124
124
|
onClick={onClick}
|
|
@@ -13,18 +13,18 @@ export function PanelAppToolbar({
|
|
|
13
13
|
onRefresh,
|
|
14
14
|
}: PanelAppToolbarProps) {
|
|
15
15
|
return (
|
|
16
|
-
<div className="flex items-center justify-between gap-2
|
|
16
|
+
<div className="flex items-center justify-between gap-2 border-b border-border/70 bg-card px-3.5 py-2 shrink-0">
|
|
17
17
|
<button
|
|
18
18
|
type="button"
|
|
19
19
|
onClick={onOpenApps}
|
|
20
|
-
className="inline-flex shrink-0 items-center rounded-md p-1.5 text-
|
|
20
|
+
className="inline-flex shrink-0 items-center rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground"
|
|
21
21
|
title={t('appsTitle')}
|
|
22
22
|
aria-label={t('appsTitle')}
|
|
23
23
|
>
|
|
24
24
|
<ArrowLeft className="h-3.5 w-3.5" />
|
|
25
25
|
</button>
|
|
26
26
|
<span
|
|
27
|
-
className="min-w-0 flex-1 truncate text-xs font-medium text-
|
|
27
|
+
className="min-w-0 flex-1 truncate text-xs font-medium text-muted-foreground"
|
|
28
28
|
title={appTitle}
|
|
29
29
|
>
|
|
30
30
|
{appTitle}
|
|
@@ -32,7 +32,7 @@ export function PanelAppToolbar({
|
|
|
32
32
|
<button
|
|
33
33
|
type="button"
|
|
34
34
|
onClick={onRefresh}
|
|
35
|
-
className="rounded-md p-1.5 text-
|
|
35
|
+
className="rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground"
|
|
36
36
|
title={t('panelAppsRefreshCurrent')}
|
|
37
37
|
aria-label={t('panelAppsRefreshCurrent')}
|
|
38
38
|
>
|
|
@@ -83,7 +83,7 @@ export function PanelAppsList({
|
|
|
83
83
|
|
|
84
84
|
if (panelApps.isLoading) {
|
|
85
85
|
return (
|
|
86
|
-
<div className="flex h-full items-center justify-center text-sm text-
|
|
86
|
+
<div className="flex h-full items-center justify-center text-sm text-muted-foreground">{t('panelAppsLoading')}</div>
|
|
87
87
|
);
|
|
88
88
|
}
|
|
89
89
|
|
|
@@ -94,17 +94,17 @@ export function PanelAppsList({
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
return (
|
|
97
|
-
<div className="flex h-full min-h-0 flex-col bg-
|
|
98
|
-
<div className="flex items-center justify-between gap-2 border-b border-
|
|
97
|
+
<div className="flex h-full min-h-0 flex-col bg-card text-card-foreground">
|
|
98
|
+
<div className="flex items-center justify-between gap-2 border-b border-border/70 px-4 py-3">
|
|
99
99
|
<div className="flex min-w-0 items-center gap-1.5">
|
|
100
100
|
{headerContent ?? (
|
|
101
|
-
<div className="truncate text-sm font-semibold text-
|
|
101
|
+
<div className="truncate text-sm font-semibold text-foreground">{t('panelAppsTitle')}</div>
|
|
102
102
|
)}
|
|
103
103
|
{panelApps.data?.panelsPath ? (
|
|
104
104
|
<TooltipProvider delayDuration={250}>
|
|
105
105
|
<Tooltip>
|
|
106
106
|
<TooltipTrigger asChild>
|
|
107
|
-
<button type="button" className="rounded p-0.5 text-
|
|
107
|
+
<button type="button" className="rounded p-0.5 text-muted-foreground/70 transition-colors hover:bg-accent hover:text-accent-foreground" aria-label={t('panelAppsTitle')}><HelpCircle className="h-3.5 w-3.5" /></button>
|
|
108
108
|
</TooltipTrigger>
|
|
109
109
|
<TooltipContent side="bottom" className="max-w-[320px] break-all font-mono text-xs">{panelApps.data.panelsPath}</TooltipContent>
|
|
110
110
|
</Tooltip>
|
|
@@ -114,16 +114,16 @@ export function PanelAppsList({
|
|
|
114
114
|
<button
|
|
115
115
|
type="button"
|
|
116
116
|
onClick={() => void panelApps.refetch()}
|
|
117
|
-
className="rounded-md p-1.5 text-
|
|
117
|
+
className="rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground"
|
|
118
118
|
title={t('panelAppsRefresh')}
|
|
119
119
|
aria-label={t('panelAppsRefresh')}
|
|
120
120
|
>
|
|
121
121
|
<RefreshCw className="h-3.5 w-3.5" />
|
|
122
122
|
</button>
|
|
123
123
|
</div>
|
|
124
|
-
<div className="border-b border-
|
|
124
|
+
<div className="border-b border-border/70 px-3 py-2">
|
|
125
125
|
<Tabs value={viewMode} onValueChange={(value) => setViewMode(value as PanelAppViewMode)}>
|
|
126
|
-
<TabsList className="grid h-auto w-full grid-cols-5 rounded-lg bg-
|
|
126
|
+
<TabsList className="grid h-auto w-full grid-cols-5 rounded-lg bg-muted/70 p-0.5">
|
|
127
127
|
<TabsTrigger value="smart" className="px-2 py-1 text-xs">{t('panelAppsSortSmart')}</TabsTrigger>
|
|
128
128
|
<TabsTrigger value="favorites" className="px-2 py-1 text-xs">{t('panelAppsFavorites')}</TabsTrigger>
|
|
129
129
|
<TabsTrigger value="recent-open" className="px-2 py-1 text-xs">{t('panelAppsSortRecentOpen')}</TabsTrigger>
|
|
@@ -139,7 +139,7 @@ export function PanelAppsList({
|
|
|
139
139
|
onRefresh={() => void panelApps.refetch()}
|
|
140
140
|
/>
|
|
141
141
|
) : entries.length === 0 ? (
|
|
142
|
-
<div className="flex flex-1 items-center justify-center px-6 text-center text-sm text-
|
|
142
|
+
<div className="flex flex-1 items-center justify-center px-6 text-center text-sm text-muted-foreground">{t('panelAppsEmptyFiltered')}</div>
|
|
143
143
|
) : (
|
|
144
144
|
<div className="custom-scrollbar min-h-0 flex-1 overflow-y-auto p-3">
|
|
145
145
|
<div className="space-y-1.5">
|
|
@@ -176,8 +176,8 @@ function PanelAppsEmptyGuide({
|
|
|
176
176
|
<div className="mx-auto flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
|
177
177
|
<AppWindow className="h-5 w-5" />
|
|
178
178
|
</div>
|
|
179
|
-
<h2 className="mt-3 text-sm font-semibold text-
|
|
180
|
-
<p className="mt-1 text-xs leading-5 text-
|
|
179
|
+
<h2 className="mt-3 text-sm font-semibold text-foreground">{t('panelAppsEmptyTitle')}</h2>
|
|
180
|
+
<p className="mt-1 text-xs leading-5 text-muted-foreground">{t('panelAppsEmptyDescription')}</p>
|
|
181
181
|
|
|
182
182
|
<div className="mt-4 space-y-2 text-left">
|
|
183
183
|
<PanelAppsEmptyGuideStep
|
|
@@ -195,16 +195,16 @@ function PanelAppsEmptyGuide({
|
|
|
195
195
|
</div>
|
|
196
196
|
|
|
197
197
|
{panelsPath ? (
|
|
198
|
-
<div className="mt-4 rounded-md bg-
|
|
199
|
-
<div className="text-[11px] font-medium uppercase text-
|
|
200
|
-
<code className="mt-1 block break-all text-xs text-
|
|
198
|
+
<div className="mt-4 rounded-md bg-muted/60 px-3 py-2 text-left">
|
|
199
|
+
<div className="text-[11px] font-medium uppercase text-muted-foreground/70">{t('panelAppsPanelsPath')}</div>
|
|
200
|
+
<code className="mt-1 block break-all text-xs text-muted-foreground">{panelsPath}</code>
|
|
201
201
|
</div>
|
|
202
202
|
) : null}
|
|
203
203
|
|
|
204
204
|
<button
|
|
205
205
|
type="button"
|
|
206
206
|
onClick={onRefresh}
|
|
207
|
-
className="mt-4 inline-flex items-center justify-center gap-1.5 rounded-md border border-
|
|
207
|
+
className="mt-4 inline-flex items-center justify-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40"
|
|
208
208
|
>
|
|
209
209
|
<RefreshCw className="h-3.5 w-3.5" />
|
|
210
210
|
{t('panelAppsRefresh')}
|
|
@@ -229,10 +229,10 @@ function PanelAppsEmptyGuideStep({
|
|
|
229
229
|
}) {
|
|
230
230
|
const content = (
|
|
231
231
|
<>
|
|
232
|
-
<div className="mt-0.5 text-
|
|
232
|
+
<div className="mt-0.5 text-muted-foreground">{icon}</div>
|
|
233
233
|
<div className="min-w-0">
|
|
234
|
-
<div className="text-xs font-medium text-
|
|
235
|
-
<div className="mt-0.5 text-xs leading-5 text-
|
|
234
|
+
<div className="text-xs font-medium text-foreground">{title}</div>
|
|
235
|
+
<div className="mt-0.5 text-xs leading-5 text-muted-foreground">{description}</div>
|
|
236
236
|
{actionLabel ? (
|
|
237
237
|
<div className="mt-2 text-xs font-medium text-primary">{actionLabel}</div>
|
|
238
238
|
) : null}
|
|
@@ -245,7 +245,7 @@ function PanelAppsEmptyGuideStep({
|
|
|
245
245
|
<button
|
|
246
246
|
type="button"
|
|
247
247
|
onClick={onAction}
|
|
248
|
-
className="flex w-full gap-2 rounded-md bg-
|
|
248
|
+
className="flex w-full gap-2 rounded-md bg-muted/60 px-3 py-2.5 text-left transition-colors hover:bg-accent/70 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40"
|
|
249
249
|
>
|
|
250
250
|
{content}
|
|
251
251
|
</button>
|
|
@@ -253,7 +253,7 @@ function PanelAppsEmptyGuideStep({
|
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
return (
|
|
256
|
-
<div className="flex gap-2 rounded-md bg-
|
|
256
|
+
<div className="flex gap-2 rounded-md bg-muted/60 px-3 py-2.5">
|
|
257
257
|
{content}
|
|
258
258
|
</div>
|
|
259
259
|
);
|
|
@@ -1,21 +1,45 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
-
import { pwaShellThemeManager } from '@/features/pwa';
|
|
2
|
+
import { pwaShellThemeManager } from '@/features/pwa/managers/pwa-shell-theme.manager';
|
|
3
3
|
|
|
4
4
|
describe('PwaShellThemeManager', () => {
|
|
5
5
|
beforeEach(() => {
|
|
6
|
-
document.head.innerHTML = '<meta name="theme-color" content="#
|
|
6
|
+
document.head.innerHTML = '<meta name="theme-color" content="#FFFFFF" />';
|
|
7
7
|
document.body.innerHTML = '<div id="root"></div>';
|
|
8
8
|
document.documentElement.removeAttribute('data-theme');
|
|
9
9
|
document.documentElement.style.backgroundColor = '';
|
|
10
10
|
document.body.style.backgroundColor = '';
|
|
11
11
|
});
|
|
12
12
|
|
|
13
|
+
it('applies natural shell colors by default', () => {
|
|
14
|
+
pwaShellThemeManager.syncCurrentTheme();
|
|
15
|
+
|
|
16
|
+
const meta = document.querySelector('meta[name="theme-color"]');
|
|
17
|
+
expect(meta?.getAttribute('content')).toBe('#FAF9F7');
|
|
18
|
+
expect(document.body.style.backgroundColor).toBe('rgb(250, 249, 247)');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('applies natural shell colors explicitly', () => {
|
|
22
|
+
pwaShellThemeManager.syncTheme('natural');
|
|
23
|
+
|
|
24
|
+
const meta = document.querySelector('meta[name="theme-color"]');
|
|
25
|
+
expect(meta?.getAttribute('content')).toBe('#FAF9F7');
|
|
26
|
+
expect(document.body.style.backgroundColor).toBe('rgb(250, 249, 247)');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('applies minimal shell colors explicitly', () => {
|
|
30
|
+
pwaShellThemeManager.syncTheme('minimal');
|
|
31
|
+
|
|
32
|
+
const meta = document.querySelector('meta[name="theme-color"]');
|
|
33
|
+
expect(meta?.getAttribute('content')).toBe('#FFFFFF');
|
|
34
|
+
expect(document.body.style.backgroundColor).toBe('rgb(255, 255, 255)');
|
|
35
|
+
});
|
|
36
|
+
|
|
13
37
|
it('applies warm shell colors', () => {
|
|
14
38
|
pwaShellThemeManager.syncTheme('warm');
|
|
15
39
|
|
|
16
40
|
const meta = document.querySelector('meta[name="theme-color"]');
|
|
17
|
-
expect(meta?.getAttribute('content')).toBe('#
|
|
18
|
-
expect(document.body.style.backgroundColor).toBe('rgb(
|
|
41
|
+
expect(meta?.getAttribute('content')).toBe('#FAF8F4');
|
|
42
|
+
expect(document.body.style.backgroundColor).toBe('rgb(250, 248, 244)');
|
|
19
43
|
});
|
|
20
44
|
|
|
21
45
|
it('applies cool shell colors from current theme attribute', () => {
|
|
@@ -24,7 +48,47 @@ describe('PwaShellThemeManager', () => {
|
|
|
24
48
|
pwaShellThemeManager.syncCurrentTheme();
|
|
25
49
|
|
|
26
50
|
const meta = document.querySelector('meta[name="theme-color"]');
|
|
27
|
-
expect(meta?.getAttribute('content')).toBe('#
|
|
28
|
-
expect(document.body.style.backgroundColor).toBe('rgb(248, 250,
|
|
51
|
+
expect(meta?.getAttribute('content')).toBe('#F8FAFC');
|
|
52
|
+
expect(document.body.style.backgroundColor).toBe('rgb(248, 250, 252)');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('applies dawn shell colors from current theme attribute', () => {
|
|
56
|
+
document.documentElement.setAttribute('data-theme', 'dawn');
|
|
57
|
+
|
|
58
|
+
pwaShellThemeManager.syncCurrentTheme();
|
|
59
|
+
|
|
60
|
+
const meta = document.querySelector('meta[name="theme-color"]');
|
|
61
|
+
expect(meta?.getAttribute('content')).toBe('#FAF7F4');
|
|
62
|
+
expect(document.body.style.backgroundColor).toBe('rgb(250, 247, 244)');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('applies graphite shell colors from current theme attribute', () => {
|
|
66
|
+
document.documentElement.setAttribute('data-theme', 'graphite');
|
|
67
|
+
|
|
68
|
+
pwaShellThemeManager.syncCurrentTheme();
|
|
69
|
+
|
|
70
|
+
const meta = document.querySelector('meta[name="theme-color"]');
|
|
71
|
+
expect(meta?.getAttribute('content')).toBe('#F8F8F7');
|
|
72
|
+
expect(document.body.style.backgroundColor).toBe('rgb(248, 248, 247)');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('maps the legacy leaf theme to warm shell colors', () => {
|
|
76
|
+
document.documentElement.setAttribute('data-theme', 'leaf');
|
|
77
|
+
|
|
78
|
+
pwaShellThemeManager.syncCurrentTheme();
|
|
79
|
+
|
|
80
|
+
const meta = document.querySelector('meta[name="theme-color"]');
|
|
81
|
+
expect(meta?.getAttribute('content')).toBe('#FAF8F4');
|
|
82
|
+
expect(document.body.style.backgroundColor).toBe('rgb(250, 248, 244)');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('applies probe shell colors from current theme attribute', () => {
|
|
86
|
+
document.documentElement.setAttribute('data-theme', 'probe');
|
|
87
|
+
|
|
88
|
+
pwaShellThemeManager.syncCurrentTheme();
|
|
89
|
+
|
|
90
|
+
const meta = document.querySelector('meta[name="theme-color"]');
|
|
91
|
+
expect(meta?.getAttribute('content')).toBe('#FEFCF6');
|
|
92
|
+
expect(document.body.style.backgroundColor).toBe('rgb(254, 252, 246)');
|
|
29
93
|
});
|
|
30
94
|
});
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { normalizeTheme, type UiTheme } from '@/shared/lib/theme';
|
|
2
2
|
|
|
3
3
|
const PWA_SHELL_THEME_COLORS: Record<UiTheme, string> = {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
natural: '#FAF9F7',
|
|
5
|
+
minimal: '#FFFFFF',
|
|
6
|
+
warm: '#FAF8F4',
|
|
7
|
+
cool: '#F8FAFC',
|
|
8
|
+
dawn: '#FAF7F4',
|
|
9
|
+
graphite: '#F8F8F7',
|
|
10
|
+
probe: '#FEFCF6'
|
|
6
11
|
};
|
|
7
12
|
|
|
8
13
|
export class PwaShellThemeManager {
|
|
@@ -22,8 +27,8 @@ export class PwaShellThemeManager {
|
|
|
22
27
|
return;
|
|
23
28
|
}
|
|
24
29
|
|
|
25
|
-
const currentTheme = document.documentElement.getAttribute('data-theme')
|
|
26
|
-
this.syncTheme(currentTheme);
|
|
30
|
+
const currentTheme = document.documentElement.getAttribute('data-theme');
|
|
31
|
+
this.syncTheme(normalizeTheme(currentTheme) ?? 'natural');
|
|
27
32
|
};
|
|
28
33
|
|
|
29
34
|
private updateThemeMeta = (themeColor: string) => {
|
|
@@ -37,10 +37,10 @@ export function ServiceActionAuthorizationDialog() {
|
|
|
37
37
|
</DialogDescription>
|
|
38
38
|
</DialogHeader>
|
|
39
39
|
{pending ? (
|
|
40
|
-
<div className="space-y-3 rounded-md border border-
|
|
40
|
+
<div className="space-y-3 rounded-md border border-border bg-muted/60 p-3 text-sm">
|
|
41
41
|
<AuthorizationField label={t('serviceActionAuthorizationSource')} value={pending.panelAppId} />
|
|
42
42
|
<div className="space-y-2">
|
|
43
|
-
<div className="text-xs font-medium text-
|
|
43
|
+
<div className="text-xs font-medium text-muted-foreground">
|
|
44
44
|
{t('serviceActionAuthorizationActions')}
|
|
45
45
|
</div>
|
|
46
46
|
<div className="max-h-64 space-y-2 overflow-auto">
|
|
@@ -78,18 +78,18 @@ function AuthorizationActionItem({
|
|
|
78
78
|
};
|
|
79
79
|
}) {
|
|
80
80
|
return (
|
|
81
|
-
<div className="space-y-1 rounded border border-
|
|
81
|
+
<div className="space-y-1 rounded border border-border bg-card px-3 py-2">
|
|
82
82
|
<div className="flex items-start justify-between gap-3">
|
|
83
|
-
<div className="min-w-0 break-words text-xs font-medium text-
|
|
83
|
+
<div className="min-w-0 break-words text-xs font-medium text-foreground">
|
|
84
84
|
{action.actionTitle ?? action.actionId}
|
|
85
85
|
</div>
|
|
86
86
|
<div className="shrink-0 rounded bg-amber-50 px-1.5 py-0.5 text-[11px] font-medium text-amber-700">
|
|
87
87
|
{action.risk ?? 'dangerous'}
|
|
88
88
|
</div>
|
|
89
89
|
</div>
|
|
90
|
-
<div className="break-words text-[11px] text-
|
|
90
|
+
<div className="break-words text-[11px] text-muted-foreground">{action.actionId}</div>
|
|
91
91
|
{action.actionDescription ? (
|
|
92
|
-
<div className="break-words text-xs text-
|
|
92
|
+
<div className="break-words text-xs text-muted-foreground">{action.actionDescription}</div>
|
|
93
93
|
) : null}
|
|
94
94
|
</div>
|
|
95
95
|
);
|
|
@@ -104,8 +104,8 @@ function AuthorizationField({
|
|
|
104
104
|
}) {
|
|
105
105
|
return (
|
|
106
106
|
<div className="grid grid-cols-[5rem_minmax(0,1fr)] gap-3">
|
|
107
|
-
<div className="text-xs font-medium text-
|
|
108
|
-
<div className="min-w-0 break-words text-xs text-
|
|
107
|
+
<div className="text-xs font-medium text-muted-foreground">{label}</div>
|
|
108
|
+
<div className="min-w-0 break-words text-xs text-foreground">{value}</div>
|
|
109
109
|
</div>
|
|
110
110
|
);
|
|
111
111
|
}
|