Skip to content

Commit 73ceb63

Browse files
staredclaude
andcommitted
Improve toolbar for both mobile and desktop with unified responsive design
- Create unified toolbar component that works on both mobile and desktop - Mobile toolbar is compact (40px height) with proper touch targets - Desktop toolbar maintains standard spacing (44px height) - CSV area now has consistent height with other toolbar buttons - Added compact mode to FileUpload showing "CSV" and "URL" labels - ExampleSelector now has proper dropdown arrow and consistent styling - LibrarySelector shows WebR status on mobile with same colors as desktop - WebR status shows loading state for all operations (init, execute, package install) - Removed checkmark from mobile WebR status to reduce visual noise - All components use consistent border radius, padding, and hover states - Fixed toolbar visibility issues on mobile devices 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 73b17db commit 73ceb63

File tree

8 files changed

+176
-94
lines changed

8 files changed

+176
-94
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "webr-ggplot2-demo",
33
"private": true,
4-
"version": "0.2.9",
4+
"version": "0.2.10",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

src/App.vue

Lines changed: 53 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -166,49 +166,34 @@ onMounted(async () => {
166166
<AppHeader />
167167

168168
<main class="main">
169-
<!-- Mobile toolbar -->
170-
<div
171-
v-if="isMobile"
172-
class="mobile-toolbar"
173-
>
174-
<FileUpload
175-
:uploaded-file="currentCsvData"
176-
@file-uploaded="handleFileUpload"
177-
@file-removed="handleFileRemoved"
178-
/>
179-
<ExampleSelector @example-selected="handleExampleSelect" />
180-
<LibrarySelector
181-
:installed-libraries="installedLibraries"
182-
:is-loading="isInitializing"
183-
:package-versions="packageVersions"
184-
@toggle-library="toggleLibrary"
185-
/>
186-
</div>
187-
188-
<!-- Desktop toolbar -->
189-
<div
190-
v-else
191-
class="toolbar"
192-
>
193-
<div class="toolbar-left">
169+
<!-- Unified responsive toolbar -->
170+
<div class="toolbar" :class="{ 'mobile': isMobile }">
171+
<div class="toolbar-group toolbar-left">
194172
<FileUpload
195173
:uploaded-file="currentCsvData"
174+
:compact="isMobile"
196175
@file-uploaded="handleFileUpload"
197176
@file-removed="handleFileRemoved"
198177
/>
199-
<ExampleSelector @example-selected="handleExampleSelect" />
178+
<ExampleSelector
179+
:compact="isMobile"
180+
@example-selected="handleExampleSelect"
181+
/>
200182
</div>
201-
<div class="toolbar-right">
183+
<div class="toolbar-group toolbar-right">
202184
<WebRStatus
185+
v-if="!isMobile"
203186
:is-ready="isReady"
204-
:is-loading="isInitializing"
187+
:is-loading="isLoading"
205188
:loading-status="loadingStatus"
206189
:webr-version="webrVersion"
207190
/>
208191
<LibrarySelector
209192
:installed-libraries="installedLibraries"
210-
:is-loading="isInitializing"
193+
:is-loading="isLoading"
194+
:is-ready="isReady"
211195
:package-versions="packageVersions"
196+
:show-status="isMobile"
212197
@toggle-library="toggleLibrary"
213198
/>
214199
</div>
@@ -322,28 +307,17 @@ onMounted(async () => {
322307
flex-direction: column;
323308
overflow: hidden;
324309
min-height: 0;
310+
position: relative;
325311
}
326312
327-
/* Mobile toolbar */
328-
.mobile-toolbar {
329-
background: white;
330-
border-bottom: 1px solid #e5e7eb;
331-
padding: 0.25rem;
332-
display: flex;
333-
align-items: center;
334-
gap: 0.25rem;
335-
flex-shrink: 0;
336-
height: 32px;
337-
}
338-
339-
340313
/* Mobile split view */
341314
.mobile-container {
342315
flex: 1;
343316
display: flex;
344317
overflow: hidden;
345318
position: relative;
346319
width: 100%;
320+
min-height: 0;
347321
}
348322
349323
.panel-wrapper {
@@ -409,28 +383,61 @@ onMounted(async () => {
409383
pointer-events: none;
410384
}
411385
412-
/* Desktop toolbar */
386+
/* Unified responsive toolbar */
413387
.toolbar {
414388
background: white;
415389
border-bottom: 1px solid #e5e7eb;
416390
padding: 0.75rem 1rem;
417-
display: flex;
391+
display: flex !important;
418392
justify-content: space-between;
419393
align-items: center;
420394
flex-shrink: 0;
421395
gap: 1rem;
396+
min-height: 44px;
397+
visibility: visible !important;
398+
opacity: 1 !important;
399+
position: relative;
400+
z-index: 10;
422401
}
423402
424-
.toolbar-left {
403+
.toolbar.mobile {
404+
padding: 0.375rem 0.5rem;
405+
gap: 0.375rem;
406+
min-height: 40px;
407+
height: 40px;
408+
display: flex !important;
409+
}
410+
411+
.toolbar-group {
425412
display: flex;
413+
align-items: center;
426414
gap: 0.75rem;
415+
min-width: 0;
416+
}
417+
418+
.toolbar.mobile .toolbar-group {
419+
gap: 0.375rem;
420+
}
421+
422+
.toolbar-left {
423+
flex: 1;
424+
display: flex;
427425
align-items: center;
426+
gap: 0.75rem;
427+
}
428+
429+
.toolbar.mobile .toolbar-left {
430+
gap: 0.375rem;
428431
}
429432
430433
.toolbar-right {
431434
display: flex;
432-
gap: 0.75rem;
433435
align-items: center;
436+
gap: 0.75rem;
437+
}
438+
439+
.toolbar.mobile .toolbar-right {
440+
gap: 0.375rem;
434441
}
435442
436443
@@ -499,10 +506,6 @@ onMounted(async () => {
499506
500507
/* Mobile styles */
501508
@media (max-width: 768px) {
502-
.mobile-toolbar {
503-
display: flex;
504-
}
505-
506509
.mobile-container {
507510
display: flex;
508511
flex: 1;

src/components/ConsoleToggle.vue

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,12 @@ const toggleConsole = (): void => {
8989
padding: 0.5rem 0.75rem;
9090
font-size: 0.875rem;
9191
cursor: pointer;
92-
transition: all 0.3s ease;
92+
transition: all 0.2s ease;
9393
display: flex;
9494
align-items: center;
9595
gap: 0.5rem;
9696
color: #6b7280;
97-
min-height: 38px;
97+
min-height: 36px;
9898
white-space: nowrap;
9999
}
100100
@@ -137,9 +137,9 @@ const toggleConsole = (): void => {
137137
138138
@media (max-width: 768px) {
139139
.console-toggle {
140-
min-height: 36px;
141-
font-size: 0.875rem;
142-
padding: 0.5rem 0.625rem;
140+
min-height: 32px;
141+
font-size: 0.8125rem;
142+
padding: 0.375rem 0.5rem;
143143
}
144144
}
145145
</style>

src/components/ExampleSelector.vue

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ import { ref, computed } from 'vue'
33
import { examples } from '@/data/examples'
44
import type { RExample } from '@/types'
55
6+
interface Props {
7+
compact?: boolean
8+
}
9+
10+
defineProps<Props>()
11+
612
const emit = defineEmits<{
713
exampleSelected: [example: RExample]
814
}>()
@@ -46,43 +52,56 @@ const handleExampleChange = (): void => {
4652
align-items: center;
4753
min-width: 0;
4854
flex: 1;
55+
max-width: 250px;
4956
}
5057
5158
.select {
5259
background: #f3f4f6;
5360
border: 1px solid #d1d5db;
5461
border-radius: 6px;
55-
padding: 0.5rem 0.75rem;
62+
padding: 0.375rem 0.625rem;
63+
padding-right: 1.5rem;
5664
font-size: 0.875rem;
5765
cursor: pointer;
58-
transition: all 0.3s ease;
66+
transition: all 0.2s ease;
5967
width: 100%;
60-
max-width: 250px;
61-
min-height: 38px;
68+
max-width: 200px;
69+
min-height: 32px;
70+
appearance: none;
71+
background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D%2714%27%20height%3D%278%27%20viewBox%3D%270%200%2014%208%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%3Cpath%20d%3D%27M1%201l6%206%206-6%27%20stroke%3D%27%236B7280%27%20stroke-width%3D%272%27%20fill%3D%27none%27%20fill-rule%3D%27evenodd%27%2F%3E%3C%2Fsvg%3E');
72+
background-repeat: no-repeat;
73+
background-position: right 0.5rem center;
74+
background-size: 12px;
6275
}
6376
6477
.select:hover {
6578
background: #e5e7eb;
66-
border-color: #9ca3af;
79+
border-color: #3b82f6;
6780
}
6881
6982
.select:focus {
7083
outline: none;
7184
border-color: #3b82f6;
72-
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
85+
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1);
7386
}
7487
7588
@media (max-width: 768px) {
7689
.example-selector {
7790
flex: 1;
91+
min-width: 0;
92+
display: flex;
93+
align-items: center;
7894
}
7995
8096
.select {
8197
max-width: none;
82-
min-height: 32px;
98+
min-height: 28px;
8399
padding: 0.25rem 0.5rem;
100+
padding-right: 1.25rem;
84101
font-size: 0.8125rem;
85102
border-radius: 4px;
103+
background-size: 10px;
104+
background-position: right 0.375rem center;
86105
}
87106
}
88107
</style>

src/components/FileUpload.vue

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { CsvData } from '@/types'
44
55
const props = defineProps<{
66
uploadedFile?: CsvData | null
7+
compact?: boolean
78
}>()
89
910
const emit = defineEmits<{
@@ -196,13 +197,13 @@ onUnmounted(() => {
196197
class="thin-btn"
197198
@click="fileInputRef?.click()"
198199
>
199-
Upload file
200+
{{ props.compact ? 'CSV' : 'Upload CSV file' }}
200201
</button>
201202
<button
202203
class="thin-btn"
203204
@click="showUrlInput = true"
204205
>
205-
Load from URL
206+
{{ props.compact ? 'URL' : 'Load from URL' }}
206207
</button>
207208
</div>
208209
</div>
@@ -278,18 +279,21 @@ onUnmounted(() => {
278279
position: relative;
279280
display: flex;
280281
align-items: center;
282+
min-width: 0;
281283
}
282284
283285
.csv-drop-zone {
284286
background: #fafafa;
285287
border: 2px dashed #d1d5db;
286288
border-radius: 6px;
287-
padding: 0.375rem 0.5rem;
289+
padding: 0.375rem 0.625rem;
288290
transition: all 0.3s ease;
289-
min-height: 36px;
291+
min-height: 32px;
292+
max-height: 32px;
290293
display: flex;
291294
align-items: center;
292295
justify-content: center;
296+
box-sizing: border-box;
293297
}
294298
295299
.csv-drop-zone:hover {
@@ -319,6 +323,8 @@ onUnmounted(() => {
319323
cursor: pointer;
320324
transition: all 0.2s ease;
321325
white-space: nowrap;
326+
font-weight: 500;
327+
line-height: 1;
322328
}
323329
324330
.thin-btn:hover {
@@ -371,11 +377,12 @@ onUnmounted(() => {
371377
background: #f3f4f6;
372378
border: 1px solid #d1d5db;
373379
border-radius: 6px;
374-
padding: 0.5rem 0.75rem;
380+
padding: 0.375rem 0.625rem;
375381
font-size: 0.875rem;
376382
cursor: pointer;
377383
transition: all 0.3s ease;
378384
white-space: nowrap;
385+
min-height: 32px;
379386
}
380387
381388
.csv-button:hover {
@@ -525,8 +532,14 @@ onUnmounted(() => {
525532
526533
/* Mobile styles */
527534
@media (max-width: 768px) {
535+
.file-upload {
536+
display: flex;
537+
align-items: center;
538+
}
539+
528540
.csv-drop-zone {
529541
min-height: 28px;
542+
max-height: 28px;
530543
padding: 0.25rem 0.375rem;
531544
border-radius: 4px;
532545
}
@@ -538,11 +551,13 @@ onUnmounted(() => {
538551
.thin-btn {
539552
padding: 0.125rem 0.25rem;
540553
font-size: 0.625rem;
554+
line-height: 1;
541555
}
542556
543557
.csv-button {
544-
padding: 0.25rem 0.375rem;
545-
font-size: 0.75rem;
558+
padding: 0.25rem 0.5rem;
559+
font-size: 0.8125rem;
560+
min-height: 28px;
546561
}
547562
548563
.csv-dropdown {

0 commit comments

Comments
 (0)