feat(v3): redesign project workspace layout + emoji icons

- ProjectBox: CSS grid layout (header|session|terminal zones)
- AgentPane: bottom-anchored prompt, full-width form
- Icons: emoji replacing Nerd Font codepoints (cross-platform)
- SettingsTab: emoji picker grid (24 icons, 8-column popup)
- CSS: px to rem conversions across ProjectGrid, TerminalTabs, ProjectBox
This commit is contained in:
Hibryda 2026-03-08 02:15:34 +01:00
parent b8001dc56c
commit 5c657d0daa
6 changed files with 128 additions and 52 deletions

View file

@ -553,18 +553,17 @@
.prompt-area { .prompt-area {
display: flex; display: flex;
align-items: center; flex-direction: column;
justify-content: center; justify-content: flex-end;
height: 100%; height: 100%;
padding: 24px; padding: 1rem;
} }
.prompt-form { .prompt-form {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px; gap: 0.5rem;
width: 100%; width: 100%;
max-width: 600px;
} }
.prompt-input { .prompt-input {
@ -942,8 +941,7 @@
/* Session toolbar */ /* Session toolbar */
.session-toolbar { .session-toolbar {
width: 100%; width: 100%;
max-width: 600px; margin-bottom: 0.5rem;
margin-bottom: 8px;
} }
.toolbar-row { .toolbar-row {

View file

@ -45,13 +45,13 @@
<style> <style>
.project-box { .project-box {
display: flex; display: grid;
flex-direction: column; grid-template-rows: auto 1fr auto;
min-width: 480px; min-width: 30rem;
scroll-snap-align: start; scroll-snap-align: start;
background: var(--ctp-base); background: var(--ctp-base);
border: 1px solid var(--ctp-surface0); border: 1px solid var(--ctp-surface0);
border-radius: 6px; border-radius: 0.375rem;
overflow: hidden; overflow: hidden;
transition: border-color 0.15s; transition: border-color 0.15s;
} }
@ -61,17 +61,15 @@
} }
.project-session-area { .project-session-area {
flex: 1; display: flex;
min-height: 200px;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
display: flex; min-height: 0;
} }
.project-terminal-area { .project-terminal-area {
flex-shrink: 0; height: 16rem;
min-height: 120px; min-height: 8rem;
border-top: 1px solid var(--ctp-surface0); border-top: 1px solid var(--ctp-surface0);
} }
</style> </style>

View file

@ -57,16 +57,16 @@
<style> <style>
.project-grid { .project-grid {
display: flex; display: flex;
gap: 4px; gap: 0.25rem;
height: 100%; height: 100%;
overflow-x: auto; overflow-x: auto;
scroll-snap-type: x mandatory; scroll-snap-type: x mandatory;
padding: 4px; padding: 0.25rem;
} }
.project-slot { .project-slot {
flex: 0 0 calc((100% - (var(--visible-count) - 1) * 4px) / var(--visible-count)); flex: 0 0 calc((100% - (var(--visible-count) - 1) * 0.25rem) / var(--visible-count));
min-width: 480px; min-width: 30rem;
display: flex; display: flex;
} }

View file

@ -20,7 +20,7 @@
style="--accent: var({accentVar})" style="--accent: var({accentVar})"
{onclick} {onclick}
> >
<span class="project-icon">{project.icon || '\uf120'}</span> <span class="project-icon">{project.icon || '📁'}</span>
<span class="project-name">{project.name}</span> <span class="project-name">{project.name}</span>
<span class="project-id">({project.identifier})</span> <span class="project-id">({project.identifier})</span>
</button> </button>
@ -29,9 +29,8 @@
.project-header { .project-header {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 6px; gap: 0.375rem;
padding: 4px 10px; padding: 0.375rem 0.625rem;
height: 28px;
background: var(--ctp-mantle); background: var(--ctp-mantle);
border: none; border: none;
border-bottom: 2px solid transparent; border-bottom: 2px solid transparent;
@ -54,9 +53,9 @@
} }
.project-icon { .project-icon {
font-family: 'NerdFontsSymbols Nerd Font', 'Symbols Nerd Font Mono', monospace; font-size: 0.85rem;
font-size: 0.9rem; line-height: 1;
color: var(--accent); flex-shrink: 0;
} }
.project-name { .project-name {

View file

@ -18,6 +18,12 @@
import { THEME_LIST, getPalette, type ThemeId } from '../../styles/themes'; import { THEME_LIST, getPalette, type ThemeId } from '../../styles/themes';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '@tauri-apps/api/core';
const PROJECT_ICONS = [
'📁', '🚀', '🤖', '🌐', '🔧', '🎮', '📱', '💻',
'🔬', '📊', '🎨', '🔒', '💬', '📦', '⚡', '🧪',
'🏗️', '📝', '🎯', '💡', '🔥', '🛠️', '🧩', '🗄️',
];
let activeGroupId = $derived(getActiveGroupId()); let activeGroupId = $derived(getActiveGroupId());
let activeGroup = $derived(getActiveGroup()); let activeGroup = $derived(getActiveGroup());
let activeProjectId = $derived(getActiveProjectId()); let activeProjectId = $derived(getActiveProjectId());
@ -194,7 +200,7 @@
name: newName.trim(), name: newName.trim(),
identifier: deriveIdentifier(newName.trim()), identifier: deriveIdentifier(newName.trim()),
description: '', description: '',
icon: '\uf120', icon: '📁',
cwd: newCwd.trim(), cwd: newCwd.trim(),
profile: 'default', profile: 'default',
enabled: true, enabled: true,
@ -455,14 +461,29 @@
</button> </button>
</div> </div>
</label> </label>
<label class="project-field"> <div class="project-field icon-field">
<span class="field-label">Icon</span> <span class="field-label">Icon</span>
<input <button
value={project.icon} class="icon-trigger"
onchange={e => updateProject(activeGroupId, project.id, { icon: (e.target as HTMLInputElement).value })} onclick={(e) => {
style="width: 60px" const btn = e.currentTarget as HTMLElement;
/> const popup = btn.nextElementSibling as HTMLElement;
</label> popup.classList.toggle('visible');
}}
>{project.icon || '📁'}</button>
<div class="icon-picker">
{#each PROJECT_ICONS as emoji}
<button
class="icon-option"
class:active={project.icon === emoji}
onclick={(e) => {
updateProject(activeGroupId, project.id, { icon: emoji });
((e.currentTarget as HTMLElement).closest('.icon-picker') as HTMLElement).classList.remove('visible');
}}
>{emoji}</button>
{/each}
</div>
</div>
<label class="project-field"> <label class="project-field">
<span class="field-label">Enabled</span> <span class="field-label">Enabled</span>
<input <input
@ -806,6 +827,71 @@
width: 100%; width: 100%;
} }
.icon-field {
position: relative;
}
.icon-trigger {
width: 2rem;
height: 2rem;
display: flex;
align-items: center;
justify-content: center;
background: var(--ctp-base);
border: 1px solid var(--ctp-surface1);
border-radius: 3px;
font-size: 1rem;
cursor: pointer;
transition: border-color 0.15s;
}
.icon-trigger:hover {
border-color: var(--ctp-overlay0);
}
.icon-picker {
display: none;
position: absolute;
top: 100%;
left: 0;
z-index: 20;
background: var(--ctp-mantle);
border: 1px solid var(--ctp-surface1);
border-radius: 0.375rem;
padding: 0.375rem;
grid-template-columns: repeat(8, 1fr);
gap: 2px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
width: max-content;
}
.icon-picker.visible {
display: grid;
}
.icon-option {
width: 1.75rem;
height: 1.75rem;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: 1px solid transparent;
border-radius: 3px;
font-size: 0.9rem;
cursor: pointer;
transition: background 0.1s;
}
.icon-option:hover {
background: var(--ctp-surface0);
}
.icon-option.active {
background: var(--ctp-surface1);
border-color: var(--ctp-blue);
}
.add-form { .add-form {
display: flex; display: flex;
gap: 8px; gap: 8px;

View file

@ -103,8 +103,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
gap: 1px; gap: 1px;
height: 26px; padding: 0 0.25rem;
padding: 0 4px;
background: var(--ctp-mantle); background: var(--ctp-mantle);
border-bottom: 1px solid var(--ctp-surface0); border-bottom: 1px solid var(--ctp-surface0);
overflow-x: auto; overflow-x: auto;
@ -114,14 +113,14 @@
.tab { .tab {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 4px; gap: 0.25rem;
padding: 2px 8px; padding: 0.25rem 0.5rem;
background: transparent; background: transparent;
border: none; border: none;
color: var(--ctp-overlay1); color: var(--ctp-overlay1);
font-size: 0.72rem; font-size: 0.72rem;
cursor: pointer; cursor: pointer;
border-radius: 3px 3px 0 0; border-radius: 0.1875rem 0.1875rem 0 0;
white-space: nowrap; white-space: nowrap;
transition: color 0.1s, background 0.1s; transition: color 0.1s, background 0.1s;
} }
@ -137,13 +136,8 @@
border-bottom: 1px solid var(--ctp-blue); border-bottom: 1px solid var(--ctp-blue);
} }
.tab-icon {
font-family: 'NerdFontsSymbols Nerd Font', 'Symbols Nerd Font Mono', monospace;
font-size: 0.75rem;
}
.tab-title { .tab-title {
max-width: 100px; max-width: 6.25rem;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
@ -154,7 +148,7 @@
color: var(--ctp-overlay0); color: var(--ctp-overlay0);
font-size: 0.8rem; font-size: 0.8rem;
cursor: pointer; cursor: pointer;
padding: 0 2px; padding: 0 0.125rem;
line-height: 1; line-height: 1;
} }
@ -168,8 +162,8 @@
color: var(--ctp-overlay0); color: var(--ctp-overlay0);
font-size: 0.85rem; font-size: 0.85rem;
cursor: pointer; cursor: pointer;
padding: 2px 6px; padding: 0.125rem 0.375rem;
border-radius: 3px; border-radius: 0.1875rem;
} }
.tab-add:hover { .tab-add:hover {
@ -201,13 +195,14 @@
} }
.add-first { .add-first {
padding: 6px 16px; padding: 0.375rem 1rem;
background: var(--ctp-surface0); background: var(--ctp-surface0);
border: 1px solid var(--ctp-surface1); border: 1px solid var(--ctp-surface1);
border-radius: 4px; border-radius: 0.25rem;
color: var(--ctp-subtext0); color: var(--ctp-subtext0);
font-size: 0.8rem; font-size: 0.8rem;
cursor: pointer; cursor: pointer;
transition: background 0.15s, color 0.15s;
} }
.add-first:hover { .add-first:hover {