update organization switcher to use shadcn dropdownmenu

This commit is contained in:
Gregor Vostrak
2025-09-30 16:28:17 +02:00
parent 250379d4bd
commit d2f75cca6e
3 changed files with 64 additions and 57 deletions

View File

@@ -1,12 +1,23 @@
<script setup lang="ts"> <script setup lang="ts">
import { ChevronDownIcon } from '@heroicons/vue/20/solid'; import { ChevronDownIcon } from '@heroicons/vue/20/solid';
import Dropdown from '@/packages/ui/src/Input/Dropdown.vue'; import { Link, usePage } from '@inertiajs/vue3';
import DropdownLink from '@/Components/DropdownLink.vue'; import {
import { usePage } from '@inertiajs/vue3'; Cog6ToothIcon,
PlusCircleIcon,
CheckCircleIcon,
ArrowRightIcon,
} from '@heroicons/vue/24/solid';
import type { Organization, User } from '@/types/models'; import type { Organization, User } from '@/types/models';
import { isBillingActivated } from '@/utils/billing'; import { isBillingActivated } from '@/utils/billing';
import { canManageBilling } from '@/utils/permissions'; import { canManageBilling } from '@/utils/permissions';
import { switchOrganization } from '@/utils/useOrganization'; import { switchOrganization } from '@/utils/useOrganization';
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
} from '@/Components/ui/dropdown-menu';
const page = usePage<{ const page = usePage<{
jetstream: { jetstream: {
@@ -28,84 +39,79 @@ const switchToTeam = (organization: Organization) => {
</script> </script>
<template> <template>
<Dropdown v-if="page.props.jetstream.hasTeamFeatures" align="center" width="60"> <DropdownMenu v-if="page.props.jetstream.hasTeamFeatures">
<template #trigger> <DropdownMenuTrigger class="w-full text-left">
<div <div
data-testid="organization_switcher" data-testid="organization_switcher"
class="flex hover:bg-white/10 cursor-pointer transition px-2 py-1 rounded-lg w-full items-center justify-between font-medium"> class="flex hover:bg-white/10 cursor-pointer transition pl-2 py-1 rounded w-full items-center justify-between">
<div class="flex flex-1 space-x-2 items-center w-[calc(100%-30px)]"> <div class="flex flex-1 space-x-2 items-center w-[calc(100%-30px)]">
<div <div
class="rounded sm:rounded-lg bg-blue-900 font-semibold text-xs sm:text-sm flex-shrink-0 text-white w-5 sm:w-6 h-5 sm:h-6 flex items-center justify-center"> class="rounded bg-blue-900 font-medium text-xs flex-shrink-0 text-white w-5 h-5 flex items-center justify-center">
{{ page.props.auth.user.current_team.name.slice(0, 1).toUpperCase() }} {{ page.props.auth.user.current_team.name.slice(0, 1).toUpperCase() }}
</div> </div>
<span class="text-sm flex-1 truncate font-semibold"> <span class="text-xs flex-1 truncate font-medium">
{{ page.props.auth.user.current_team.name }} {{ page.props.auth.user.current_team.name }}
</span> </span>
</div> </div>
<div class="w-[30px]"> <div class="w-[30px]">
<button <div class="p-1 rounded-full flex items-center w-6 h-6">
class="p-1 transition hover:bg-white/10 rounded-full flex items-center w-8 h-8"> <ChevronDownIcon class="w-4 sm:w-full mt-[1px]"></ChevronDownIcon>
<ChevronDownIcon class="w-5 sm:w-full mt-[1px]"></ChevronDownIcon> </div>
</button>
</div> </div>
</div> </div>
</template> </DropdownMenuTrigger>
<template #content> <DropdownMenuContent align="start">
<div class="w-60"> <div class="w-60">
<!-- Organization Management --> <DropdownMenuLabel>Manage Organization</DropdownMenuLabel>
<div class="block px-4 py-2 text-xs text-text-secondary">Manage Organization</div>
<!-- Organization Settings --> <DropdownMenuItem as-child>
<DropdownLink :href="route('teams.show', page.props.auth.user.current_team.id)"> <Link
Organization Settings :href="route('teams.show', page.props.auth.user.current_team.id)"
</DropdownLink> class="inline-flex items-center gap-2.5 w-full">
<Cog6ToothIcon class="w-5 h-5 text-icon-default" />
<span>Organization Settings</span>
</Link>
</DropdownMenuItem>
<DropdownLink v-if="canManageBilling() && isBillingActivated()" href="/billing"> <DropdownMenuItem v-if="canManageBilling() && isBillingActivated()" as-child>
Billing <Link href="/billing" class="inline-flex items-center w-full"> Billing </Link>
</DropdownLink> </DropdownMenuItem>
<DropdownLink <DropdownMenuItem v-if="page.props.jetstream.canCreateTeams" as-child>
v-if="page.props.jetstream.canCreateTeams" <Link
:href="route('teams.create')"> :href="route('teams.create')"
Create new organization class="inline-flex items-center gap-2.5 w-full">
</DropdownLink> <PlusCircleIcon class="w-5 h-5 text-icon-default" />
<span>Create new organization</span>
</Link>
</DropdownMenuItem>
<!-- Organization Switcher -->
<template v-if="page.props.auth.user.all_teams.length > 1"> <template v-if="page.props.auth.user.all_teams.length > 1">
<div class="border-t border-card-background-separator" /> <div class="border-t border-card-background-separator" />
<div class="block px-4 py-2 text-xs text-text-secondary"> <DropdownMenuLabel>Switch Organizations</DropdownMenuLabel>
Switch Organizations
</div>
<template v-for="team in page.props.auth.user.all_teams" :key="team.id"> <template v-for="team in page.props.auth.user.all_teams" :key="team.id">
<form @submit.prevent="switchToTeam(team)"> <form @submit.prevent="switchToTeam(team)">
<DropdownLink as="button"> <DropdownMenuItem
<div class="flex items-center"> as-child
<svg class="inline-flex gap-2.5 items-center w-full">
<button type="submit">
<CheckCircleIcon
v-if="team.id == page.props.auth.user.current_team_id" v-if="team.id == page.props.auth.user.current_team_id"
class="me-2 h-5 w-5 text-green-400" class="h-5 w-5 text-green-400" />
xmlns="http://www.w3.org/2000/svg" <ArrowRightIcon v-else class="h-5 w-5 text-icon-default" />
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<div> <div class="w-full truncate text-left">
{{ team.name }} {{ team.name }}
</div> </div>
</div> </button>
</DropdownLink> </DropdownMenuItem>
</form> </form>
</template> </template>
</template> </template>
</div> </div>
</template> </DropdownMenuContent>
</Dropdown> </DropdownMenu>
</template> </template>

View File

@@ -6,6 +6,7 @@ import {
DropdownMenuTrigger, DropdownMenuTrigger,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuLabel,
} from '@/Components/ui/dropdown-menu'; } from '@/Components/ui/dropdown-menu';
import { import {
UserCircleIcon, UserCircleIcon,
@@ -83,13 +84,13 @@ const openFeedback = () => {
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="center" class="min-w-40"> <DropdownMenuContent align="center" class="min-w-40">
<div class="block px-2 py-2 text-xs text-gray-400">Manage Account</div> <DropdownMenuLabel>Manage Account</DropdownMenuLabel>
<DropdownMenuItem as-child> <DropdownMenuItem as-child>
<Link <Link
:href="route('profile.show')" :href="route('profile.show')"
class="inline-flex items-center gap-2.5 w-full"> class="inline-flex items-center gap-2.5 w-full">
<UserCircleIcon class="w-5 h-5 text-text-secondary" /> <UserCircleIcon class="w-5 h-5 text-icon-default" />
<span>Profile</span> <span>Profile</span>
</Link> </Link>
</DropdownMenuItem> </DropdownMenuItem>
@@ -98,7 +99,7 @@ const openFeedback = () => {
<Link <Link
:href="route('api-tokens.index')" :href="route('api-tokens.index')"
class="inline-flex items-center gap-2.5 w-full"> class="inline-flex items-center gap-2.5 w-full">
<KeyIcon class="w-5 h-5 text-text-secondary" /> <KeyIcon class="w-5 h-5 text-icon-default" />
<span>API Tokens</span> <span>API Tokens</span>
</Link> </Link>
</DropdownMenuItem> </DropdownMenuItem>
@@ -108,7 +109,7 @@ const openFeedback = () => {
type="button" type="button"
class="inline-flex items-center gap-2.5 w-full" class="inline-flex items-center gap-2.5 w-full"
@click="openFeedback"> @click="openFeedback">
<ChatBubbleLeftRightIcon class="w-5 h-5 text-text-secondary" /> <ChatBubbleLeftRightIcon class="w-5 h-5 text-icon-default" />
<span>Feedback</span> <span>Feedback</span>
</button> </button>
</DropdownMenuItem> </DropdownMenuItem>
@@ -116,7 +117,7 @@ const openFeedback = () => {
<form class="w-full" @submit.prevent="logout"> <form class="w-full" @submit.prevent="logout">
<DropdownMenuItem as-child class="inline-flex items-center gap-2.5 w-full"> <DropdownMenuItem as-child class="inline-flex items-center gap-2.5 w-full">
<button type="submit" data-testid="logout_button"> <button type="submit" data-testid="logout_button">
<ArrowLeftOnRectangleIcon class="w-5 h-5 text-text-secondary" /> <ArrowLeftOnRectangleIcon class="w-5 h-5 text-icon-default" />
<span>Log Out</span> <span>Log Out</span>
</button> </button>
</DropdownMenuItem> </DropdownMenuItem>

View File

@@ -19,7 +19,7 @@ const forwardedProps = useForwardProps(delegatedProps);
<template> <template>
<DropdownMenuLabel <DropdownMenuLabel
v-bind="forwardedProps" v-bind="forwardedProps"
:class="cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', props.class)"> :class="cn('block px-2 py-2 text-xs text-gray-400', inset && 'pl-8', props.class)">
<slot /> <slot />
</DropdownMenuLabel> </DropdownMenuLabel>
</template> </template>