Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/dashboard-mobile-polish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@getmunin/dashboard-pages': patch
---

Fix several mobile/touch dashboard UI issues

- Email channel dialog: the focused-input ring on the leftmost field was clipped, because `overflow-y-auto` forces `overflow-x` to compute as `auto` too, and the scrolling wrapper had no padding of its own to give the ring room. Added a small inset/outset pair so the ring renders without shifting any content.
- Settings topbar: the mobile menu button had a border (`variant="outline"`) inconsistent with the borderless cog icon elsewhere; switched to `variant="ghost"`.
- Settings topbar back arrow and dashboard topbar cog icon were styled gray-by-default with hover turning them black — correct for a mouse, but permanently gray on a touch device with no hover state. Both now force black via `[@media(hover:none)]`, leaving the hover-capable (desktop) behavior unchanged.
- Team page: the members and pending-invitations tables used a fixed `min-w-[640px]` with horizontal scroll on narrow viewports. Below `md`, both now render as a stacked card list instead — members show name/email, then role select + edit/remove actions in one row (role stays visible since it's the only place to change it, unlike the edit dialog which only renames); invitations show email, then role chip + revoke in one row, matching the same alignment pattern.
6 changes: 3 additions & 3 deletions packages/dashboard-pages/src/components/munin-topbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export function DashboardTopbar({
href={settingsHref}
aria-label={settingsLabel}
title={settingsLabel}
className="group inline-flex size-9 items-center justify-center text-ink-mute transition-colors duration-fast hover:text-ink dark:hover:text-foreground"
className="group inline-flex size-9 items-center justify-center text-ink-mute transition-colors duration-fast hover:text-ink dark:hover:text-foreground [@media(hover:none)]:!text-ink dark:[@media(hover:none)]:!text-foreground"
>
<SettingsIcon
className="size-5 transition-transform duration-300 group-hover:rotate-[30deg]"
Expand Down Expand Up @@ -92,7 +92,7 @@ export function SettingsTopbar({
href={backHref}
aria-label={backLabel}
title={backLabel}
className="group inline-flex items-center gap-2.5 self-center px-1 py-1.5 font-mono text-[10px] uppercase tracking-eyebrow text-ink-mute transition-colors duration-fast hover:text-ink dark:hover:text-foreground"
className="group inline-flex items-center gap-2.5 self-center px-1 py-1.5 font-mono text-[10px] uppercase tracking-eyebrow text-ink-mute transition-colors duration-fast hover:text-ink dark:hover:text-foreground [@media(hover:none)]:!text-ink dark:[@media(hover:none)]:!text-foreground"
>
<ArrowLeft
aria-hidden
Expand All @@ -110,7 +110,7 @@ export function SettingsTopbar({
<div className="ml-auto flex h-full items-center">
{onMenuToggle ? (
<Button
variant="outline"
variant="ghost"
size="icon"
onClick={onMenuToggle}
aria-label={openMenuLabel ?? 'Open menu'}
Expand Down
2 changes: 1 addition & 1 deletion packages/dashboard-pages/src/pages/channels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1350,7 +1350,7 @@ function EmailChannelDialog({
void submit();
}}
>
<div className="flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto">
<div className="-mx-1 flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto px-1">
<div className="grid gap-3 sm:grid-cols-2">
<FormField label={t('nameLabel')}>
<Input
Expand Down
66 changes: 62 additions & 4 deletions packages/dashboard-pages/src/pages/team.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,9 @@ export function TeamPage() {
]}
/>
) : (
<div className="-mx-6 overflow-x-auto px-6 md:mx-0 md:px-0">
<table className="w-full min-w-[640px]">
<>
<div className="hidden md:block">
<table className="w-full">
<thead>
<tr className="border-b-[1px] border-rule-soft dark:border-rule-on-dark text-left">
<Th>{t('membersTableName')}</Th>
Expand Down Expand Up @@ -255,6 +256,41 @@ export function TeamPage() {
</tbody>
</table>
</div>
<ul className="space-y-3 md:hidden">
{members.map((m) => (
<li
key={m.userId}
className="space-y-3 border-b-[1px] border-rule-soft pb-3 dark:border-rule-on-dark"
>
<div className="min-w-0">
<p className="truncate text-sm font-medium text-ink dark:text-foreground">
{m.name ?? '—'}
</p>
<p className="truncate font-mono text-xs text-ink-mute">{m.email}</p>
</div>
<div className="flex items-center justify-between gap-2">
<RoleSelect
value={m.role as MemberRole}
onChange={(role) => void changeRole(m.userId, role)}
labelOwner={t('roleOwner')}
labelAdmin={t('roleAdmin')}
labelMember={t('roleMember')}
/>
<div className="inline-flex items-center gap-2">
{(canEditAnyone || m.userId === currentUserId) && (
<Button variant="outline" size="sm" onClick={() => setEditing(m)}>
{tCommon('edit')}
</Button>
)}
<Button variant="outline" size="sm" onClick={() => void removeMember(m.userId)}>
{t('remove')}
</Button>
</div>
</div>
</li>
))}
</ul>
</>
)}
</section>

Expand All @@ -276,8 +312,9 @@ export function TeamPage() {
) : invites.length === 0 ? (
<EmptyCallout title={t('invitesEmptyTitle')} body={t('invitesEmptyBody')} />
) : (
<div className="-mx-6 overflow-x-auto px-6 md:mx-0 md:px-0">
<table className="w-full min-w-[640px]">
<>
<div className="hidden md:block">
<table className="w-full">
<thead>
<tr className="border-b-[1px] border-rule-soft dark:border-rule-on-dark text-left">
<Th>{t('invitesTable.email')}</Th>
Expand Down Expand Up @@ -312,6 +349,27 @@ export function TeamPage() {
</tbody>
</table>
</div>
<ul className="space-y-3 md:hidden">
{invites.map((inv) => (
<li
key={inv.id}
className="space-y-3 border-b-[1px] border-rule-soft pb-3 dark:border-rule-on-dark"
>
<div className="min-w-0">
<p className="truncate font-mono text-sm font-medium text-ink dark:text-foreground">
{inv.email}
</p>
</div>
<div className="flex items-center justify-between gap-2">
<RoleChip role={inv.role as MemberRole} t={t} />
<Button variant="outline" size="sm" onClick={() => void revokeInvite(inv)}>
{tCommon('revoke')}
</Button>
</div>
</li>
))}
</ul>
</>
)}
</section>

Expand Down
Loading