Skip to content

Commit fe3a101

Browse files
staredclaude
andcommitted
Make app fully responsive for mobile devices
- Configure Monaco Editor with mobile-optimized settings (larger font, better touch support) - Refactor layout with proper responsive breakpoints (mobile < 768px, tablet < 1024px) - Stack editor and output vertically on mobile for better usability - Optimize all interactive elements with 44px minimum touch targets - Use responsive font sizing (clamp) in header for better mobile display - Fix toolbar overflow with flex-wrap and responsive spacing - Add font-size: 16px to form inputs to prevent iOS zoom - Simplify FileUpload UI on mobile (hide drag text, stack buttons) - Make bottom bar flexible for mobile screens - Clean up and consolidate CSS media queries across all components 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent a1bc863 commit fe3a101

File tree

8 files changed

+267
-41
lines changed

8 files changed

+267
-41
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.6",
4+
"version": "0.2.8",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

src/App.vue

Lines changed: 64 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -230,29 +230,34 @@ onMounted(async () => {
230230
display: flex;
231231
flex-direction: column;
232232
overflow: hidden;
233+
min-height: 0;
233234
}
234235
235236
.toolbar {
236237
background: white;
237238
border-bottom: 1px solid #e5e7eb;
238-
padding: 1rem 2rem;
239+
padding: 0.75rem 1rem;
239240
display: flex;
240241
justify-content: space-between;
241242
align-items: center;
242243
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
243244
flex-shrink: 0;
245+
gap: 1rem;
246+
flex-wrap: wrap;
244247
}
245248
246249
.toolbar-left {
247250
display: flex;
248-
gap: 1.5rem;
251+
gap: 0.75rem;
249252
align-items: center;
253+
flex-wrap: wrap;
250254
}
251255
252256
.toolbar-right {
253257
display: flex;
254-
gap: 1rem;
258+
gap: 0.75rem;
255259
align-items: center;
260+
flex-wrap: wrap;
256261
}
257262
258263
.container {
@@ -261,37 +266,39 @@ onMounted(async () => {
261266
grid-template-columns: 1fr 1fr;
262267
gap: 0;
263268
overflow: hidden;
269+
min-height: 0;
264270
}
265271
266-
267272
.editor-section {
268273
background: white;
269274
border-right: 1px solid #e5e7eb;
270275
padding: 1rem;
271276
overflow: auto;
272277
display: flex;
273278
flex-direction: column;
279+
min-width: 0;
274280
}
275281
276282
.output-section {
277283
background: white;
278284
padding: 0;
279285
overflow: auto;
280286
position: relative;
287+
min-width: 0;
281288
}
282289
283-
284290
/* Bottom bar styling */
285291
.bottom-bar {
286292
background: white;
287293
border-top: 1px solid #e5e7eb;
288-
padding: 1rem 2rem;
294+
padding: 0.75rem 1rem;
289295
display: flex;
290296
justify-content: space-between;
291297
align-items: center;
292298
box-shadow: 0 -1px 3px rgba(0, 0, 0, 0.1);
293-
height: 60px;
299+
min-height: 52px;
294300
flex-shrink: 0;
301+
gap: 1rem;
295302
}
296303
297304
.bottom-bar-left {
@@ -304,30 +311,67 @@ onMounted(async () => {
304311
align-items: center;
305312
}
306313
314+
/* Tablet styles */
315+
@media (max-width: 1024px) {
316+
.toolbar {
317+
padding: 0.75rem;
318+
}
319+
320+
.container {
321+
grid-template-columns: 1fr 1fr;
322+
}
323+
}
307324
308-
325+
/* Mobile styles */
309326
@media (max-width: 768px) {
310327
.toolbar {
311-
flex-direction: column;
312-
gap: 1rem;
313-
align-items: stretch;
328+
padding: 0.5rem;
329+
gap: 0.5rem;
314330
}
315331
316-
.bottom-bar {
317-
flex-direction: column;
318-
gap: 1rem;
319-
align-items: stretch;
320-
height: auto;
321-
padding: 1rem;
332+
.toolbar-left,
333+
.toolbar-right {
334+
width: 100%;
335+
justify-content: space-between;
336+
gap: 0.5rem;
322337
}
323338
324339
.container {
325340
grid-template-columns: 1fr;
326-
height: auto;
341+
grid-template-rows: 1fr 1fr;
342+
}
343+
344+
.editor-section {
345+
border-right: none;
346+
border-bottom: 1px solid #e5e7eb;
347+
padding: 0.5rem;
348+
}
349+
350+
.output-section {
351+
min-height: 40vh;
352+
}
353+
354+
.bottom-bar {
355+
padding: 0.5rem;
356+
min-height: 48px;
357+
}
358+
}
359+
360+
/* Small mobile styles */
361+
@media (max-width: 480px) {
362+
.main {
363+
height: 100vh;
364+
}
365+
366+
.container {
367+
display: flex;
368+
flex-direction: column;
327369
}
328370
329-
.title {
330-
font-size: 1.75rem;
371+
.editor-section,
372+
.output-section {
373+
flex: 1;
374+
min-height: 0;
331375
}
332376
}
333377
</style>

src/components/AppHeader.vue

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ onMounted(() => {
105105
.header {
106106
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
107107
color: white;
108-
padding: 1.5rem 2rem;
108+
padding: 1rem;
109109
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
110110
flex-shrink: 0;
111111
}
@@ -116,18 +116,20 @@ onMounted(() => {
116116
align-items: center;
117117
max-width: 1400px;
118118
margin: 0 auto;
119+
gap: 1rem;
120+
flex-wrap: wrap;
119121
}
120122
121123
.title {
122124
margin: 0;
123-
font-size: 2rem;
125+
font-size: clamp(1.25rem, 4vw, 2rem);
124126
font-weight: 700;
125-
margin-bottom: 0.5rem;
127+
margin-bottom: 0.25rem;
126128
}
127129
128130
.subtitle {
129131
margin: 0;
130-
font-size: 1.125rem;
132+
font-size: clamp(0.875rem, 2vw, 1.125rem);
131133
opacity: 0.9;
132134
font-weight: 300;
133135
}
@@ -137,12 +139,13 @@ onMounted(() => {
137139
text-decoration: none;
138140
display: flex;
139141
align-items: center;
140-
gap: 0.75rem;
141-
padding: 0.5rem 1rem;
142+
gap: 0.5rem;
143+
padding: 0.5rem 0.75rem;
142144
border-radius: 6px;
143145
transition: all 0.3s ease;
144146
background-color: rgba(255, 255, 255, 0.1);
145147
border: 1px solid rgba(255, 255, 255, 0.2);
148+
white-space: nowrap;
146149
}
147150
148151
.github-link:hover {
@@ -152,8 +155,8 @@ onMounted(() => {
152155
}
153156
154157
.github-icon {
155-
width: 20px;
156-
height: 20px;
158+
width: 18px;
159+
height: 18px;
157160
flex-shrink: 0;
158161
}
159162
@@ -168,7 +171,7 @@ onMounted(() => {
168171
gap: 0.25rem;
169172
font-size: 0.875rem;
170173
font-weight: 600;
171-
padding-left: 0.75rem;
174+
padding-left: 0.5rem;
172175
border-left: 1px solid rgba(255, 255, 255, 0.3);
173176
}
174177
@@ -187,4 +190,36 @@ onMounted(() => {
187190
.subtitle-link:hover {
188191
opacity: 1;
189192
}
193+
194+
/* Mobile adjustments */
195+
@media (max-width: 768px) {
196+
.header {
197+
padding: 0.75rem;
198+
}
199+
200+
.header-content {
201+
flex-direction: column;
202+
align-items: flex-start;
203+
gap: 0.75rem;
204+
}
205+
206+
.github-link {
207+
align-self: stretch;
208+
justify-content: center;
209+
}
210+
211+
.github-text {
212+
display: none;
213+
}
214+
215+
.github-stars {
216+
padding-left: 0.5rem;
217+
}
218+
}
219+
220+
@media (min-width: 769px) and (max-width: 1024px) {
221+
.header {
222+
padding: 1rem 1.5rem;
223+
}
224+
}
190225
</style>

src/components/CodeEditor.vue

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { ref, onMounted, onUnmounted, watch } from 'vue'
2+
import { ref, onMounted, onUnmounted, watch, computed } from 'vue'
33
import * as monaco from 'monaco-editor'
44
55
interface Props {
@@ -18,16 +18,48 @@ const emit = defineEmits<{
1818
const editorRef = ref<HTMLElement>()
1919
let editor: monaco.editor.IStandaloneCodeEditor | null = null
2020
21+
// Detect if we're on a mobile device
22+
const isMobile = computed(() => {
23+
return window.innerWidth < 768 || 'ontouchstart' in window
24+
})
25+
2126
onMounted(() => {
2227
if (!editorRef.value) {return}
2328
24-
editor = monaco.editor.create(editorRef.value, {
29+
// Mobile-optimized settings
30+
const mobileConfig = {
31+
fontSize: 16, // Larger for mobile
32+
lineHeight: 22,
33+
scrollbar: {
34+
vertical: 'auto',
35+
horizontal: 'auto',
36+
verticalScrollbarSize: 10,
37+
horizontalScrollbarSize: 10,
38+
},
39+
overviewRulerLanes: 0,
40+
hideCursorInOverviewRuler: true,
41+
scrollBeyondLastColumn: 0,
42+
renderLineHighlight: 'none',
43+
cursorBlinking: 'smooth',
44+
contextmenu: false, // Disable right-click menu on mobile
45+
}
46+
47+
// Desktop settings
48+
const desktopConfig = {
49+
fontSize: 14,
50+
lineHeight: 20,
51+
scrollbar: {
52+
vertical: 'auto',
53+
horizontal: 'auto',
54+
},
55+
}
56+
57+
const baseConfig = {
2558
value: props.modelValue,
2659
language: 'r',
2760
theme: 'vs-light',
2861
minimap: { enabled: false },
2962
readOnly: props.readonly,
30-
fontSize: 14,
3163
lineNumbers: 'on',
3264
roundedSelection: false,
3365
scrollBeyondLastLine: false,
@@ -47,7 +79,15 @@ onMounted(() => {
4779
quickSuggestions: false,
4880
parameterHints: { enabled: false },
4981
hover: { enabled: false },
50-
})
82+
}
83+
84+
// Merge configs based on device type
85+
const config = {
86+
...baseConfig,
87+
...(isMobile.value ? mobileConfig : desktopConfig),
88+
}
89+
90+
editor = monaco.editor.create(editorRef.value, config)
5191
5292
editor.onDidChangeModelContent(() => {
5393
if (editor) {

src/components/ConsoleToggle.vue

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ const toggleConsole = (): void => {
9494
align-items: center;
9595
gap: 0.5rem;
9696
color: #6b7280;
97+
min-height: 38px;
98+
white-space: nowrap;
9799
}
98100
99101
.console-toggle:hover {
@@ -132,4 +134,13 @@ const toggleConsole = (): void => {
132134
.toggle-arrow.open {
133135
transform: rotate(180deg);
134136
}
137+
138+
@media (max-width: 768px) {
139+
.console-toggle {
140+
min-height: 44px;
141+
font-size: 16px; /* Prevents zoom on iOS */
142+
flex: 1;
143+
justify-content: center;
144+
}
145+
}
135146
</style>

0 commit comments

Comments
 (0)