Skip to content

Commit

Permalink
feat(ui): add hover card for project environments in dashboard
Browse files Browse the repository at this point in the history
  • Loading branch information
andrasbacsai committed Feb 13, 2025
1 parent 5c7a053 commit 237907d
Show file tree
Hide file tree
Showing 24 changed files with 556 additions and 21 deletions.
18 changes: 15 additions & 3 deletions app/Http/Controllers/InertiaController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,20 @@ class InertiaController extends Controller
public function dashboard()
{
$servers = Server::isUsable()->get();

$projects = Project::ownedByCurrentTeam()->orderBy('created_at')->with('environments')->get();
$projects = $projects->map(function ($project) {
return [
'name' => $project->name,
'description' => $project->description,
'uuid' => $project->uuid,
'environments' => $project->environments()->get()->map(function ($environment) {
return [
'name' => $environment->name,
'uuid' => $environment->uuid,
];
}),
];
});
$destinations = collect($servers)->flatMap(function ($server) {
return $server->destinations();
});
Expand All @@ -30,9 +43,8 @@ public function dashboard()
'uuid' => $server->uuid,
];
});

return Inertia::render('Dashboard', [
'projects' => Project::ownedByCurrentTeam()->orderBy('created_at')->get(['name', 'description', 'uuid']),
'projects' => $projects,
// Should not add proxy
'servers' => $servers,
'sources' => currentTeam()->sources(),
Expand Down
10 changes: 7 additions & 3 deletions resources/js/Pages/Dashboard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import ResourceBox from '@/components/ResourceBox.vue'
import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area'
import MainView from '@/components/MainView.vue'
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from '@/components/ui/hover-card'
import type { User } from '@/types/UserType'
import type { Project } from '@/types/ProjectType'
Expand Down Expand Up @@ -111,7 +115,7 @@ const breadcrumb = ref<CustomBreadcrumbItem[]>([
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 gap-2">
<div v-for="project in projects" :key="project.uuid">
<ResourceBox type="project" :href="route('next_project', project.uuid)" :name="project.name"
:description="project.description" />
:description="project.description" :environments="project.environments" />
</div>
<div v-for="server in servers" :key="server.uuid">
<ResourceBox type="server" :href="route('next_project', server.uuid)" :name="server.name"
Expand Down Expand Up @@ -161,7 +165,7 @@ const breadcrumb = ref<CustomBreadcrumbItem[]>([
<div class="resource-box-container">
<div v-for="project in projects" :key="project.uuid">
<ResourceBox type="project" :href="route('next_project', project.uuid)" :name="project.name"
:description="project.description" />
:description="project.description" :environments="project.environments" />
</div>
<ResourceBox :new="true" type="project" :href="route('next_projects')" name="New Project" />
</div>
Expand Down
68 changes: 53 additions & 15 deletions resources/js/components/ResourceBox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,21 @@
import { Link } from '@inertiajs/vue3'
import { Server, GitBranch, Map, BriefcaseBusiness, Plus, Earth } from 'lucide-vue-next'
import { computed } from 'vue';
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'
import { Environment } from '@/types/EnvironmentType';
const props = defineProps<{
type: 'project' | 'server' | 'source' | 'destination' | 'environment';
href: string;
name: string;
description?: string;
new?: boolean;
environments?: Environment[];
}>();
const isNew = computed(() => props.new)
const environments = computed(() => props.environments)
console.log(environments.value)
</script>

<template>
Expand All @@ -24,20 +29,53 @@ const isNew = computed(() => props.new)
</div>
</div>
</div>
<Link prefetch v-else :href="href"
class="flex rounded-r-xl bg-coolgray-100 border dark:border-black cursor-pointer h-24 group">
<div class=" text-xs text-muted-foreground group-hover:dark:text-white font-bold h-full bg-coolgray-200 p-2
group-hover:bg-coollabs rounded-l-xl transition-all">
<BriefcaseBusiness :size="20" v-if="type === 'project'" />
<Server :size="20" v-else-if="type === 'server'" />
<GitBranch :size="20" v-else-if="type === 'source'" />
<Map :size="20" v-else-if="type === 'destination'" />
<Earth :size="20" v-else-if="type === 'environment'" />
</div>
<div class="flex flex-col p-2">
<div class="text-sm font-bold text-foreground">{{ name }}</div>
<p class="text-xs text-muted-foreground group-hover:dark:text-white font-bold">{{ description
}}</p>
<div v-else>
<HoverCard v-if="type === 'project' && environments" :open-delay="100" :close-delay="100">
<HoverCardTrigger>
<Link prefetch :href="href"
class="flex rounded-r-xl bg-coolgray-100 border dark:border-black cursor-pointer h-24 group">
<div class=" text-xs text-muted-foreground group-hover:dark:text-white font-bold h-full bg-coolgray-200 p-2
group-hover:bg-coollabs rounded-l-xl transition-all">
<BriefcaseBusiness :size="20" v-if="type === 'project'" />
<Server :size="20" v-else-if="type === 'server'" />
<GitBranch :size="20" v-else-if="type === 'source'" />
<Map :size="20" v-else-if="type === 'destination'" />
<Earth :size="20" v-else-if="type === 'environment'" />
</div>
<div class="flex flex-col p-2">
<div class="text-sm font-bold text-foreground">{{ name }}</div>
<p class="text-xs text-muted-foreground group-hover:dark:text-white font-bold">{{ description
}}</p>
</div>
</Link>
</HoverCardTrigger>
<HoverCardContent class="w-64 p-2 rounded-xl dark:bg-coolgray-100 shadow-xl dark:border-black"
:side-offset="5" align="center">
<h3 class="text-sm font-bold text-foreground pb-2 px-2">Environments</h3>
<div v-for="environment in environments" :key="environment.uuid" class="flex flex-col gap-2 text-xs">
<Link class="hover:dark:bg-coolgray-300 p-2 rounded-md flex gap-2 items-center"
:href="route('next_project', environment.uuid)">
<Earth :size="16" class="text-muted-foreground/60" />
{{ environment.name }}
</Link>
</div>
</HoverCardContent>
</HoverCard>
<Link v-else prefetch :href="href"
class="flex rounded-r-xl bg-coolgray-100 border dark:border-black cursor-pointer h-24 group">
<div class=" text-xs text-muted-foreground group-hover:dark:text-white font-bold h-full bg-coolgray-200 p-2
group-hover:bg-coollabs rounded-l-xl transition-all">
<BriefcaseBusiness :size="20" v-if="type === 'project'" />
<Server :size="20" v-else-if="type === 'server'" />
<GitBranch :size="20" v-else-if="type === 'source'" />
<Map :size="20" v-else-if="type === 'destination'" />
<Earth :size="20" v-else-if="type === 'environment'" />
</div>
<div class="flex flex-col p-2">
<div class="text-sm font-bold text-foreground">{{ name }}</div>
<p class="text-xs text-muted-foreground group-hover:dark:text-white font-bold">{{ description
}}</p>
</div>
</Link>
</div>
</Link>
</template>
14 changes: 14 additions & 0 deletions resources/js/components/ui/hover-card/HoverCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<script setup lang="ts">
import { HoverCardRoot, type HoverCardRootEmits, type HoverCardRootProps, useForwardPropsEmits } from 'radix-vue'
const props = defineProps<HoverCardRootProps>()
const emits = defineEmits<HoverCardRootEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>

<template>
<HoverCardRoot v-bind="forwarded">
<slot />
</HoverCardRoot>
</template>
37 changes: 37 additions & 0 deletions resources/js/components/ui/hover-card/HoverCardContent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
import {
HoverCardContent,
type HoverCardContentProps,
HoverCardPortal,
useForwardProps,
} from 'radix-vue'
import { computed, type HTMLAttributes } from 'vue'
const props = withDefaults(
defineProps<HoverCardContentProps & { class?: HTMLAttributes['class'] }>(),
{
sideOffset: 4,
},
)
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>

<template>
<HoverCardPortal>
<HoverCardContent v-bind="forwardedProps" :class="cn(
'z-50 w-64 rounded-md border border-neutral-200 bg-white p-4 text-neutral-950 shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50',
props.class,
)
">
<slot />
</HoverCardContent>
</HoverCardPortal>
</template>
11 changes: 11 additions & 0 deletions resources/js/components/ui/hover-card/HoverCardTrigger.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script setup lang="ts">
import { HoverCardTrigger, type HoverCardTriggerProps } from 'radix-vue'
const props = defineProps<HoverCardTriggerProps>()
</script>

<template>
<HoverCardTrigger v-bind="props">
<slot />
</HoverCardTrigger>
</template>
3 changes: 3 additions & 0 deletions resources/js/components/ui/hover-card/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { default as HoverCard } from './HoverCard.vue'
export { default as HoverCardContent } from './HoverCardContent.vue'
export { default as HoverCardTrigger } from './HoverCardTrigger.vue'
35 changes: 35 additions & 0 deletions resources/js/components/ui/menubar/Menubar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
import {
MenubarRoot,
type MenubarRootEmits,
type MenubarRootProps,
useForwardPropsEmits,
} from 'radix-vue'
import { computed, type HTMLAttributes } from 'vue'
const props = defineProps<MenubarRootProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<MenubarRootEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>

<template>
<MenubarRoot
v-bind="forwarded"
:class="
cn(
'flex h-10 items-center gap-x-1 rounded-md border border-neutral-200 bg-white p-1 dark:border-neutral-800 dark:bg-neutral-950',
props.class,
)
"
>
<slot />
</MenubarRoot>
</template>
40 changes: 40 additions & 0 deletions resources/js/components/ui/menubar/MenubarCheckboxItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
import { Check } from 'lucide-vue-next'
import {
MenubarCheckboxItem,
type MenubarCheckboxItemEmits,
type MenubarCheckboxItemProps,
MenubarItemIndicator,
useForwardPropsEmits,
} from 'radix-vue'
import { computed, type HTMLAttributes } from 'vue'
const props = defineProps<MenubarCheckboxItemProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<MenubarCheckboxItemEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>

<template>
<MenubarCheckboxItem
v-bind="forwarded"
:class="cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50',
props.class,
)"
>
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<MenubarItemIndicator>
<Check class="w-4 h-4" />
</MenubarItemIndicator>
</span>
<slot />
</MenubarCheckboxItem>
</template>
43 changes: 43 additions & 0 deletions resources/js/components/ui/menubar/MenubarContent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
import {
MenubarContent,
type MenubarContentProps,
MenubarPortal,
useForwardProps,
} from 'radix-vue'
import { computed, type HTMLAttributes } from 'vue'
const props = withDefaults(
defineProps<MenubarContentProps & { class?: HTMLAttributes['class'] }>(),
{
align: 'start',
alignOffset: -4,
sideOffset: 8,
},
)
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwardedProps = useForwardProps(delegatedProps)
</script>

<template>
<MenubarPortal>
<MenubarContent
v-bind="forwardedProps"
:class="
cn(
'z-50 min-w-48 overflow-hidden rounded-md border border-neutral-200 bg-white p-1 text-neutral-950 shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50',
props.class,
)
"
>
<slot />
</MenubarContent>
</MenubarPortal>
</template>
11 changes: 11 additions & 0 deletions resources/js/components/ui/menubar/MenubarGroup.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script setup lang="ts">
import { MenubarGroup, type MenubarGroupProps } from 'radix-vue'
const props = defineProps<MenubarGroupProps>()
</script>

<template>
<MenubarGroup v-bind="props">
<slot />
</MenubarGroup>
</template>
35 changes: 35 additions & 0 deletions resources/js/components/ui/menubar/MenubarItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
import {
MenubarItem,
type MenubarItemEmits,
type MenubarItemProps,
useForwardPropsEmits,
} from 'radix-vue'
import { computed, type HTMLAttributes } from 'vue'
const props = defineProps<MenubarItemProps & { class?: HTMLAttributes['class'], inset?: boolean }>()
const emits = defineEmits<MenubarItemEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>

<template>
<MenubarItem
v-bind="forwarded"
:class="cn(
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50',
inset && 'pl-8',
props.class,
)"
>
<slot />
</MenubarItem>
</template>
Loading

0 comments on commit 237907d

Please sign in to comment.