Skip to content

Commit ff21bed

Browse files
committed
feat: primilary mobile work
1 parent 0f8ae4b commit ff21bed

File tree

4 files changed

+377
-220
lines changed

4 files changed

+377
-220
lines changed
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<script setup lang="ts">
2+
import type { CompareResponse, FileChange } from '#shared/types'
3+
4+
const props = defineProps<{
5+
compare: CompareResponse
6+
groupedDeps: Map<string, CompareResponse['dependencyChanges']>
7+
allChanges: FileChange[]
8+
}>()
9+
10+
const selectedFile = defineModel<FileChange | null>('selectedFile', { default: null })
11+
const fileFilter = defineModel<'all' | 'added' | 'removed' | 'modified'>('fileFilter', {
12+
default: 'all',
13+
})
14+
const open = defineModel<boolean>('open', { default: false })
15+
16+
const route = useRoute()
17+
watch(
18+
() => route.fullPath,
19+
() => {
20+
open.value = false
21+
},
22+
)
23+
24+
const isLocked = useScrollLock(document)
25+
watch(open, value => {
26+
isLocked.value = value
27+
})
28+
29+
function handleFileSelect(file: FileChange) {
30+
selectedFile.value = file
31+
open.value = false
32+
}
33+
</script>
34+
35+
<template>
36+
<!-- Backdrop -->
37+
<Transition
38+
enter-active-class="transition-opacity duration-200"
39+
enter-from-class="opacity-0"
40+
enter-to-class="opacity-100"
41+
leave-active-class="transition-opacity duration-200"
42+
leave-from-class="opacity-100"
43+
leave-to-class="opacity-0"
44+
>
45+
<div v-if="open" class="md:hidden fixed inset-0 z-40 bg-black/50" @click="open = false" />
46+
</Transition>
47+
48+
<!-- Drawer -->
49+
<Transition
50+
enter-active-class="transition-transform duration-200"
51+
enter-from-class="-translate-x-full"
52+
enter-to-class="translate-x-0"
53+
leave-active-class="transition-transform duration-200"
54+
leave-from-class="translate-x-0"
55+
leave-to-class="-translate-x-full"
56+
>
57+
<aside
58+
v-if="open"
59+
class="md:hidden fixed inset-y-0 inset-is-0 z-50 w-72 max-w-[85vw] bg-bg-subtle border-ie border-border overflow-y-auto flex flex-col"
60+
>
61+
<div
62+
class="sticky top-0 bg-bg-subtle border-b border-border px-4 py-3 flex items-center justify-between gap-2"
63+
>
64+
<div class="text-xs font-mono text-fg-muted flex items-center gap-2">
65+
<span class="flex items-center gap-1">
66+
<span class="text-green-500">+{{ props.compare.stats.filesAdded }}</span>
67+
<span class="text-fg-subtle">/</span>
68+
<span class="text-red-500">-{{ props.compare.stats.filesRemoved }}</span>
69+
<span class="text-fg-subtle">/</span>
70+
<span class="text-yellow-500">~{{ props.compare.stats.filesModified }}</span>
71+
</span>
72+
<span class="text-fg-subtle">•</span>
73+
<span>{{ props.allChanges.length }} files</span>
74+
</div>
75+
<button
76+
type="button"
77+
class="text-fg-muted hover:text-fg transition-colors"
78+
aria-label="Close files panel"
79+
@click="open = false"
80+
>
81+
<span class="i-carbon:close w-5 h-5" />
82+
</button>
83+
</div>
84+
85+
<DiffSidebarPanel
86+
:compare="props.compare"
87+
:grouped-deps="props.groupedDeps"
88+
:all-changes="props.allChanges"
89+
v-model:selected-file="selectedFile"
90+
v-model:file-filter="fileFilter"
91+
/>
92+
</aside>
93+
</Transition>
94+
</template>
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
<script setup lang="ts">
2+
import type { CompareResponse, FileChange } from '#shared/types'
3+
4+
const props = defineProps<{
5+
compare: CompareResponse
6+
groupedDeps: Map<string, CompareResponse['dependencyChanges']>
7+
allChanges: FileChange[]
8+
showSettings?: boolean
9+
}>()
10+
11+
const emit = defineEmits<{
12+
'file-select': [file: FileChange]
13+
}>()
14+
15+
const selectedFile = defineModel<FileChange | null>('selectedFile', { default: null })
16+
const fileFilter = defineModel<'all' | 'added' | 'removed' | 'modified'>('fileFilter', {
17+
default: 'all',
18+
})
19+
20+
const sectionOrder = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']
21+
const sectionMeta: Record<string, { label: string; icon: string }> = {
22+
dependencies: { label: 'Dependencies', icon: 'i-carbon-cube' },
23+
devDependencies: { label: 'Dev Dependencies', icon: 'i-carbon-tools' },
24+
peerDependencies: { label: 'Peer Dependencies', icon: 'i-carbon-user-multiple' },
25+
optionalDependencies: { label: 'Optional Dependencies', icon: 'i-carbon-help' },
26+
}
27+
28+
const sectionList = computed(() => {
29+
const entries = Array.from(props.groupedDeps.entries())
30+
return entries
31+
.map(([key, changes]) => ({
32+
key,
33+
changes,
34+
label: sectionMeta[key]?.label ?? key,
35+
icon: sectionMeta[key]?.icon ?? 'i-carbon-cube',
36+
order: sectionOrder.indexOf(key) === -1 ? sectionOrder.length + 1 : sectionOrder.indexOf(key),
37+
}))
38+
.sort((a, b) => a.order - b.order)
39+
})
40+
41+
const filteredChanges = computed(() => {
42+
if (fileFilter.value === 'all') return props.allChanges
43+
return props.allChanges.filter(f => f.type === fileFilter.value)
44+
})
45+
46+
function getSemverBadgeClass(semverDiff: string | null | undefined): string {
47+
switch (semverDiff) {
48+
case 'major':
49+
return 'bg-red-500/10 text-red-500'
50+
case 'minor':
51+
return 'bg-yellow-500/10 text-yellow-500'
52+
case 'patch':
53+
return 'bg-green-500/10 text-green-500'
54+
case 'prerelease':
55+
return 'bg-purple-500/10 text-purple-500'
56+
default:
57+
return 'bg-bg-muted text-fg-subtle'
58+
}
59+
}
60+
61+
function handleFileSelect(file: FileChange) {
62+
selectedFile.value = file
63+
emit('file-select', file)
64+
}
65+
</script>
66+
67+
<template>
68+
<div class="flex flex-col min-h-0">
69+
<!-- Summary section -->
70+
<div class="border-b border-border shrink-0">
71+
<div class="px-3 py-2.5 border-b border-border">
72+
<div class="flex flex-wrap items-center justify-between gap-2">
73+
<h2 class="text-xs font-medium">Summary</h2>
74+
<div class="flex items-center gap-3 font-mono text-[10px]">
75+
<span class="flex items-center gap-1">
76+
<span class="text-green-500">+{{ compare.stats.filesAdded }}</span>
77+
<span class="text-fg-subtle">/</span>
78+
<span class="text-red-500">-{{ compare.stats.filesRemoved }}</span>
79+
<span class="text-fg-subtle">/</span>
80+
<span class="text-yellow-500">~{{ compare.stats.filesModified }}</span>
81+
</span>
82+
<span v-if="compare.dependencyChanges.length > 0" class="text-fg-muted">
83+
{{ compare.dependencyChanges.length }} dep{{
84+
compare.dependencyChanges.length !== 1 ? 's' : ''
85+
}}
86+
</span>
87+
</div>
88+
</div>
89+
</div>
90+
91+
<div
92+
v-if="compare.meta.warnings?.length"
93+
class="px-3 py-2 bg-yellow-500/5 border-b border-border"
94+
>
95+
<div class="flex items-start gap-2">
96+
<span class="i-carbon-warning w-3.5 h-3.5 text-yellow-500 shrink-0 mt-0.5" />
97+
<div class="text-[10px] text-fg-muted">
98+
<p v-for="warning in compare.meta.warnings" :key="warning">{{ warning }}</p>
99+
</div>
100+
</div>
101+
</div>
102+
103+
<div v-if="compare.dependencyChanges.length > 0" class="px-3 py-2.5 space-y-2">
104+
<details v-for="section in sectionList" :key="section.key" class="group">
105+
<summary
106+
class="cursor-pointer list-none flex items-center gap-2 text-xs font-medium mb-2 hover:text-fg transition-colors"
107+
>
108+
<span
109+
class="i-carbon-chevron-right w-3.5 h-3.5 transition-transform group-open:rotate-90"
110+
/>
111+
<span :class="section.icon" class="w-3.5 h-3.5" />
112+
{{ section.label }} ({{ section.changes.length }})
113+
</summary>
114+
115+
<div class="space-y-1 ml-5 max-h-40 overflow-y-auto">
116+
<div
117+
v-for="dep in section.changes"
118+
:key="dep.name"
119+
class="flex items-center gap-2 text-xs py-0.5"
120+
>
121+
<span
122+
:class="[
123+
'w-3 h-3 shrink-0',
124+
dep.type === 'added'
125+
? 'i-carbon-add-alt text-green-500'
126+
: dep.type === 'removed'
127+
? 'i-carbon-subtract-alt text-red-500'
128+
: 'i-carbon-arrows-horizontal text-yellow-500',
129+
]"
130+
/>
131+
132+
<NuxtLink
133+
:to="`/${dep.name}`"
134+
class="font-mono hover:text-fg transition-colors truncate min-w-0"
135+
>
136+
{{ dep.name }}
137+
</NuxtLink>
138+
139+
<div
140+
class="flex items-center gap-1.5 text-fg-muted font-mono text-[10px] ml-auto shrink-0"
141+
>
142+
<span
143+
v-if="dep.from"
144+
:class="{ 'line-through opacity-50': dep.type === 'updated' }"
145+
>
146+
{{ dep.from }}
147+
</span>
148+
<span v-if="dep.type === 'updated'" class="i-carbon-arrow-right w-2.5 h-2.5" />
149+
<span v-if="dep.to">{{ dep.to }}</span>
150+
</div>
151+
152+
<span
153+
v-if="dep.semverDiff"
154+
class="text-[9px] px-1.5 py-0.5 rounded font-medium shrink-0"
155+
:class="getSemverBadgeClass(dep.semverDiff)"
156+
>
157+
{{ dep.semverDiff }}
158+
</span>
159+
</div>
160+
</div>
161+
</details>
162+
</div>
163+
164+
<div
165+
v-if="compare.dependencyChanges.length === 0 && !compare.meta.warnings?.length"
166+
class="px-3 py-2 text-[10px] text-fg-muted text-center"
167+
>
168+
No dependency changes
169+
</div>
170+
</div>
171+
172+
<!-- File browser -->
173+
<details class="flex-1 flex flex-col open:flex-1 group min-h-0" open>
174+
<summary
175+
class="border-b border-border px-3 py-2 shrink-0 cursor-pointer list-none flex items-center justify-between gap-2"
176+
>
177+
<span class="text-xs font-medium flex items-center gap-1.5">
178+
<span class="i-carbon-document w-3.5 h-3.5" />
179+
Changed Files
180+
</span>
181+
<span class="flex items-center gap-2">
182+
<select
183+
v-model="fileFilter"
184+
class="text-[10px] px-2 py-1 bg-bg-subtle border border-border rounded font-mono cursor-pointer hover:border-border-hover transition-colors"
185+
>
186+
<option value="all">All ({{ allChanges.length }})</option>
187+
<option value="added">Added ({{ compare.stats.filesAdded }})</option>
188+
<option value="removed">Removed ({{ compare.stats.filesRemoved }})</option>
189+
<option value="modified">Modified ({{ compare.stats.filesModified }})</option>
190+
</select>
191+
<span
192+
class="i-carbon-chevron-right w-3.5 h-3.5 transition-transform group-open:rotate-90"
193+
/>
194+
</span>
195+
</summary>
196+
197+
<div class="flex-1 overflow-y-auto min-h-0">
198+
<div v-if="filteredChanges.length === 0" class="p-8 text-center text-xs text-fg-muted">
199+
No {{ fileFilter === 'all' ? '' : fileFilter }} files
200+
</div>
201+
202+
<DiffFileTree
203+
v-else
204+
:files="filteredChanges"
205+
:selected-path="selectedFile?.path ?? null"
206+
@select="handleFileSelect"
207+
/>
208+
</div>
209+
</details>
210+
</div>
211+
</template>

app/components/diff/ViewerPanel.vue

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -214,9 +214,9 @@ function getCodeUrl(version: string): string {
214214
<div class="h-full flex flex-col bg-bg">
215215
<!-- Header -->
216216
<div
217-
class="flex items-center justify-between px-4 py-3 bg-bg-subtle border-b border-border shrink-0"
217+
class="flex flex-wrap items-center justify-between gap-3 px-4 py-3 bg-bg-subtle border-b border-border shrink-0"
218218
>
219-
<div class="flex items-center gap-3 min-w-0">
219+
<div class="flex items-center gap-3 min-w-0 flex-1">
220220
<!-- File icon based on type -->
221221
<span
222222
:class="[
@@ -230,7 +230,7 @@ function getCodeUrl(version: string): string {
230230
/>
231231

232232
<!-- File path -->
233-
<span class="font-mono text-sm truncate">{{ file.path }}</span>
233+
<span class="font-mono text-sm truncate max-w-[65vw] sm:max-w-none">{{ file.path }}</span>
234234

235235
<!-- Stats -->
236236
<template v-if="diff?.stats">
@@ -276,20 +276,18 @@ function getCodeUrl(version: string): string {
276276
<!-- Dropdown menu -->
277277
<motion.div
278278
v-if="showOptions"
279-
class="absolute right-0 top-full mt-2 z-20 p-4 bg-bg-elevated border border-border shadow-2xl overflow-hidden"
280-
:initial="{ width: 220, height: 100, borderRadius: 20 }"
281-
:animate="{
282-
width: mergeModifiedLines ? 400 : 220,
283-
height: mergeModifiedLines ? 220 : 100,
284-
borderRadius: mergeModifiedLines ? 14 : 20,
285-
}"
286-
:transition="{
287-
type: 'spring',
288-
stiffness: 550,
289-
damping: 45,
290-
mass: 0.7,
291-
delay: mergeModifiedLines ? 0 : 0.08,
279+
class="absolute right-0 top-full mt-2 z-20 p-4 bg-bg-elevated border border-border shadow-2xl overflow-auto"
280+
:style="{
281+
width: mergeModifiedLines
282+
? 'min(420px, calc(100vw - 24px))'
283+
: 'min(320px, calc(100vw - 24px))',
284+
maxWidth: 'calc(100vw - 24px)',
285+
maxHeight: '70vh',
286+
borderRadius: mergeModifiedLines ? '14px' : '20px',
292287
}"
288+
:initial="{ opacity: 0, y: -4, scale: 0.98 }"
289+
:animate="{ opacity: 1, y: 0, scale: 1 }"
290+
:transition="{ type: 'spring', stiffness: 550, damping: 45, mass: 0.7 }"
293291
>
294292
<div class="flex flex-col gap-2">
295293
<!-- Merge modified lines toggle -->
@@ -308,7 +306,7 @@ function getCodeUrl(version: string): string {
308306
<label for="change-ratio">Change ratio</label>
309307
</div>
310308
<div
311-
class="slider-shell min-w-[360px]"
309+
class="slider-shell w-full min-w-0"
312310
:class="{ 'is-disabled': !mergeModifiedLines }"
313311
>
314312
<div class="slider-labels">
@@ -341,7 +339,7 @@ function getCodeUrl(version: string): string {
341339
<label for="diff-distance">Diff distance</label>
342340
</div>
343341
<div
344-
class="slider-shell min-w-[360px]"
342+
class="slider-shell w-full min-w-0"
345343
:class="{ 'is-disabled': !mergeModifiedLines }"
346344
>
347345
<div class="slider-labels">
@@ -374,7 +372,7 @@ function getCodeUrl(version: string): string {
374372
<label for="char-edits">Char edits</label>
375373
</div>
376374
<div
377-
class="slider-shell min-w-[360px]"
375+
class="slider-shell w-full min-w-0"
378376
:class="{ 'is-disabled': !mergeModifiedLines }"
379377
>
380378
<div class="slider-labels">

0 commit comments

Comments
 (0)