refactor: sidebar active states, notification bell, phone mockup, UI polish
Sidebar: - Fix double active state: "All" uses excludeActive to skip filter paths - Add excludeActive option to urlIsActive composable and NavItem type - Move notification bell next to user menu (icon only, no text) - Remove Discord from sidebar footer - Rename "Docs" to "Documentation" in EN Phone mockup: - Increase size from 270x585 to 360x780 for better preview Header & Layout: - Add center slot to AppHeader (used by Calendar for date title) - Calendar controls split: left (nav), center (date), right (tabs + new post) - Remove duplicate New Post button from day view - Fix month view scroll (remove overflow-hidden) UI: - Move action buttons to header-right: posts, hashtags, labels - Settings breadcrumbs: "Settings > Profile" pattern for all pages - Add Cancel button to hashtags create/edit dialogs - Add common.cancel i18n key for reuse - Social accounts grid: use shadcn Button ghost for actions All 753 tests passing.
This commit is contained in:
parent
aa7ca8ae21
commit
91ad58cd01
8 changed files with 25 additions and 31 deletions
|
|
@ -50,6 +50,6 @@
|
|||
'discord' => 'Discord',
|
||||
'share_feedback' => 'Share feedback',
|
||||
'last_updates' => 'Last Updates',
|
||||
'docs' => 'Docs',
|
||||
'docs' => 'Documentation',
|
||||
],
|
||||
];
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -2,7 +2,6 @@
|
|||
import { Link, router, usePage } from '@inertiajs/vue3';
|
||||
import {
|
||||
IconAffiliate,
|
||||
IconBrandDiscord,
|
||||
IconCalendar,
|
||||
IconChevronRight,
|
||||
IconClock,
|
||||
|
|
@ -75,7 +74,7 @@ const postsNavItems = computed<NavItem[]>(() => [
|
|||
title: trans('sidebar.posts.all'),
|
||||
href: postsIndex.url(),
|
||||
icon: IconFileText,
|
||||
exact: true,
|
||||
excludeActive: [postsIndex.url('scheduled'), postsIndex.url('published'), postsIndex.url('draft')],
|
||||
},
|
||||
{
|
||||
title: trans('sidebar.posts.scheduled'),
|
||||
|
|
@ -198,17 +197,6 @@ const switchWorkspace = (workspaceId: string) => {
|
|||
|
||||
<SidebarFooter>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem v-if="currentWorkspace">
|
||||
<NotificationBell />
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton as-child tooltip="Discord">
|
||||
<a href="https://trypost.it/discord" target="_blank" rel="noopener noreferrer">
|
||||
<IconBrandDiscord />
|
||||
<span>Discord</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton as-child tooltip="Feedback">
|
||||
<a href="https://github.com/trypost-it/trypost/discussions" target="_blank"
|
||||
|
|
@ -227,7 +215,12 @@ const switchWorkspace = (workspaceId: string) => {
|
|||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
<NavUser />
|
||||
<div class="flex items-center gap-1">
|
||||
<div class="flex-1">
|
||||
<NavUser />
|
||||
</div>
|
||||
<NotificationBell v-if="currentWorkspace" />
|
||||
</div>
|
||||
</SidebarFooter>
|
||||
</Sidebar>
|
||||
</template>
|
||||
|
|
@ -28,7 +28,7 @@ const { urlIsActive } = useActiveUrl();
|
|||
<SidebarMenuItem v-for="item in items" :key="item.title">
|
||||
<SidebarMenuButton
|
||||
as-child
|
||||
:is-active="urlIsActive(item.activePattern ?? item.href, { exact: item.exact })"
|
||||
:is-active="urlIsActive(item.activePattern ?? item.href, { exact: item.exact, exclude: item.excludeActive })"
|
||||
:tooltip="item.title"
|
||||
>
|
||||
<Link :href="item.href">
|
||||
|
|
|
|||
|
|
@ -11,9 +11,6 @@ import {
|
|||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import {
|
||||
SidebarMenuButton,
|
||||
} from '@/components/ui/sidebar';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
|
|
@ -117,16 +114,15 @@ onMounted(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<SidebarMenuButton :tooltip="$t('sidebar.notifications')" @click="openDialog">
|
||||
<IconBell />
|
||||
<span>{{ $t('sidebar.notifications') }}</span>
|
||||
<Button variant="ghost" size="icon" class="relative size-8 shrink-0" :title="$t('sidebar.notifications')" @click="openDialog">
|
||||
<IconBell class="size-4" />
|
||||
<span
|
||||
v-if="unreadCount > 0"
|
||||
class="ml-auto flex size-5 items-center justify-center rounded-full bg-destructive text-[10px] font-bold text-destructive-foreground"
|
||||
class="absolute -top-0.5 -right-0.5 flex size-4 items-center justify-center rounded-full bg-destructive text-[8px] font-bold text-destructive-foreground"
|
||||
>
|
||||
{{ unreadCount > 9 ? '9+' : unreadCount }}
|
||||
</span>
|
||||
</SidebarMenuButton>
|
||||
</Button>
|
||||
|
||||
<Dialog v-model:open="dialogOpen">
|
||||
<DialogContent class="sm:max-w-lg">
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ withDefaults(defineProps<Props>(), {
|
|||
<!-- Inner frame -->
|
||||
<div class="relative bg-[#0a0a0a] rounded-[34px] overflow-hidden">
|
||||
<!-- Screen Content - iPhone 14 Pro proportions (393x852 scaled down) -->
|
||||
<div class="relative bg-white dark:bg-black w-[270px] h-[585px] overflow-hidden">
|
||||
<div class="relative bg-white dark:bg-black w-[360px] h-[780px] overflow-hidden">
|
||||
<!-- iOS Status Bar - dark text on light mode, light text on dark mode -->
|
||||
<div class="absolute top-0 left-0 right-0 h-[44px] flex items-center justify-between px-5 z-40 text-black dark:text-white">
|
||||
<!-- Left: Time -->
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ const toPathname = (url: string): string => {
|
|||
export function useActiveUrl() {
|
||||
function urlIsActive(
|
||||
urlToCheck: NonNullable<InertiaLinkProps['href']>,
|
||||
options?: { prefix?: boolean; exact?: boolean },
|
||||
options?: { prefix?: boolean; exact?: boolean; exclude?: string[] },
|
||||
) {
|
||||
const current = currentUrlReactive.value;
|
||||
const pathname = toPathname(toUrl(urlToCheck));
|
||||
|
|
@ -33,10 +33,14 @@ export function useActiveUrl() {
|
|||
return current.startsWith(pathname);
|
||||
}
|
||||
|
||||
return (
|
||||
current === pathname ||
|
||||
(pathname !== '/' && current.startsWith(pathname + '/'))
|
||||
);
|
||||
const isMatch = current === pathname ||
|
||||
(pathname !== '/' && current.startsWith(pathname + '/'));
|
||||
|
||||
if (isMatch && options?.exclude) {
|
||||
return !options.exclude.some((ex) => current === toPathname(ex) || current.startsWith(toPathname(ex) + '/'));
|
||||
}
|
||||
|
||||
return isMatch;
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
|||
1
resources/js/types/index.d.ts
vendored
1
resources/js/types/index.d.ts
vendored
|
|
@ -18,6 +18,7 @@ export interface NavItem {
|
|||
isActive?: boolean;
|
||||
activePattern?: string;
|
||||
exact?: boolean;
|
||||
excludeActive?: string[];
|
||||
}
|
||||
|
||||
export interface SharedData {
|
||||
|
|
|
|||
Loading…
Reference in a new issue