Dialogs close from a button and rise from the bottom on phones
DModal draws a close button (✕, named by CloseText) beside its heading, in a header that stays in view while the box scrolls; a long list of who liked a post left only the thin margins around the box to tap out of it. A new ResponsiveBottom placement makes the box a sheet from the bottom below md and keeps it in the middle from md; the report, liked-by and boosted-by, edit history and public notice dialogs use it. Boxes are at most 100dvh tall, so a phone's browser bars don't cut them, and a bottom sheet leaves 3rem above itself and clears the home indicator. The report dialog gets a Cancel button. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
1 parent
87f3f2402a
commit
6731077191
7 files changed
+41
-8
No files matched your search
@@ -398,7 +398,7 @@
|
|||||||
}
|
}
|
||||||
@if (isHistoryOpen)
|
@if (isHistoryOpen)
|
||||||
{
|
{
|
||||||
<DModal IsOpen="true" IsOpenChanged="HistoryClosed" CloseText="@Localizer["Close"]" data-history-list>
|
<DModal Placement="DModalPlacement.ResponsiveBottom" IsOpen="true" IsOpenChanged="HistoryClosed" CloseText="@Localizer["Close"]" data-history-list>
|
||||||
<Heading>@Localizer["Edit history"]</Heading>
|
<Heading>@Localizer["Edit history"]</Heading>
|
||||||
<ChildContent>
|
<ChildContent>
|
||||||
<ol class="flex flex-col gap-4 mt-3">
|
<ol class="flex flex-col gap-4 mt-3">
|
||||||
|
|||||||
@@ -2,13 +2,30 @@
|
|||||||
|
|
||||||
@* daisyUI Modal: https://daisyui.com/components/modal/ . Always rendered; the open state lives in C# (@bind-IsOpen)
|
@* daisyUI Modal: https://daisyui.com/components/modal/ . Always rendered; the open state lives in C# (@bind-IsOpen)
|
||||||
and shows as modal-open. On opening, the box takes the focus (Blazor's FocusAsync) so that Escape, handled on the
|
and shows as modal-open. On opening, the box takes the focus (Blazor's FocusAsync) so that Escape, handled on the
|
||||||
root, closes it; the backdrop button stretches over the area outside the box and closes it on click. *@
|
root, closes it; the backdrop button stretches over the area outside the box and closes it on click, and a visible
|
||||||
|
close button (ShowClose, named by CloseText) sits beside the heading, which stays in view while the box scrolls. *@
|
||||||
<div @attributes="AdditionalAttributes" role="dialog" aria-modal="true" aria-labelledby="@(Heading is null ? null : uid)"
|
<div @attributes="AdditionalAttributes" role="dialog" aria-modal="true" aria-labelledby="@(Heading is null ? null : uid)"
|
||||||
class="@CssClass" @onkeydown="OnKeyDown">
|
class="@CssClass" @onkeydown="OnKeyDown">
|
||||||
<div @ref="box" tabindex="-1" class="@Cx("modal-box focus:outline-none", BoxClass)">
|
<div @ref="box" tabindex="-1" class="@Cx("modal-box focus:outline-none", BoxClass)">
|
||||||
|
@if (Heading is not null || ShowClose)
|
||||||
|
{
|
||||||
|
<div class="sticky top-0 z-10 flex items-start gap-3 bg-base-100">
|
||||||
@if (Heading is not null)
|
@if (Heading is not null)
|
||||||
{
|
{
|
||||||
<h3 id="@uid" class="text-lg font-bold">@Heading</h3>
|
<h3 id="@uid" class="flex-1 min-w-0 text-lg font-bold">@Heading</h3>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="flex-1"></span>
|
||||||
|
}
|
||||||
|
@if (ShowClose)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-sm btn-circle btn-ghost flex-none" aria-label="@CloseText" title="@CloseText"
|
||||||
|
@onclick="Close" data-modal-close>
|
||||||
|
<span aria-hidden="true">✕</span>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
@ChildContent
|
@ChildContent
|
||||||
@if (Actions is not null)
|
@if (Actions is not null)
|
||||||
@@ -35,8 +52,10 @@
|
|||||||
[Parameter] public DModalPlacement Placement { get; set; }
|
[Parameter] public DModalPlacement Placement { get; set; }
|
||||||
/// <summary>Close on a click outside the box (renders the modal-backdrop).</summary>
|
/// <summary>Close on a click outside the box (renders the modal-backdrop).</summary>
|
||||||
[Parameter] public bool CloseOnBackdrop { get; set; } = true;
|
[Parameter] public bool CloseOnBackdrop { get; set; } = true;
|
||||||
/// <summary>Text of the invisible backdrop button, read by screen readers.</summary>
|
/// <summary>Name of the close button and of the invisible backdrop button, read by screen readers.</summary>
|
||||||
[Parameter] public string CloseText { get; set; } = "close";
|
[Parameter] public string CloseText { get; set; } = "close";
|
||||||
|
/// <summary>A visible close button beside the heading.</summary>
|
||||||
|
[Parameter] public bool ShowClose { get; set; } = true;
|
||||||
[Parameter] public string BoxClass { get; set; }
|
[Parameter] public string BoxClass { get; set; }
|
||||||
|
|
||||||
readonly string uid = "d" + Guid.NewGuid().ToString("N")[..8];
|
readonly string uid = "d" + Guid.NewGuid().ToString("N")[..8];
|
||||||
@@ -52,6 +71,7 @@
|
|||||||
DModalPlacement.Bottom => "modal-bottom",
|
DModalPlacement.Bottom => "modal-bottom",
|
||||||
DModalPlacement.Start => "modal-start",
|
DModalPlacement.Start => "modal-start",
|
||||||
DModalPlacement.End => "modal-end",
|
DModalPlacement.End => "modal-end",
|
||||||
|
DModalPlacement.ResponsiveBottom => "modal-bottom md:modal-middle",
|
||||||
_ => null
|
_ => null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,5 +7,7 @@ public enum DModalPlacement
|
|||||||
Top,
|
Top,
|
||||||
Bottom,
|
Bottom,
|
||||||
Start,
|
Start,
|
||||||
End
|
End,
|
||||||
|
/// <summary>A sheet rising from the bottom below md (phones), in the middle from md.</summary>
|
||||||
|
ResponsiveBottom
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
@* Who liked a post, or boosted it (/api/v1/statuses/:id/favourited_by, reblogged_by): the accounts PrivaPub knows to
|
@* Who liked a post, or boosted it (/api/v1/statuses/:id/favourited_by, reblogged_by): the accounts PrivaPub knows to
|
||||||
have done so, newest first, each opening its profile. *@
|
have done so, newest first, each opening its profile. *@
|
||||||
<DModal IsOpen="true" IsOpenChanged="Closed" CloseText="@Localizer["Close"]" data-interacted-by="@(Boosts ? "boosts" : "likes")">
|
<DModal Placement="DModalPlacement.ResponsiveBottom" IsOpen="true" IsOpenChanged="Closed" CloseText="@Localizer["Close"]" data-interacted-by="@(Boosts ? "boosts" : "likes")">
|
||||||
<Heading>@(Boosts ? Localizer["Boosted by"] : Localizer["Liked by"])</Heading>
|
<Heading>@(Boosts ? Localizer["Boosted by"] : Localizer["Liked by"])</Heading>
|
||||||
<ChildContent>
|
<ChildContent>
|
||||||
@if (IsLoading)
|
@if (IsLoading)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
@* The one-time notice before a persona's first reaction or poll vote, and before it first joins an event
|
@* The one-time notice before a persona's first reaction or poll vote, and before it first joins an event
|
||||||
(PublicNoticeService), in MainLayout. *@
|
(PublicNoticeService), in MainLayout. *@
|
||||||
<DModal IsOpen="Notices.IsAsking" IsOpenChanged="Closed" CloseOnBackdrop="false" CloseText="@Localizer["Close"]">
|
<DModal Placement="DModalPlacement.ResponsiveBottom" IsOpen="Notices.IsAsking" IsOpenChanged="Closed" CloseOnBackdrop="false" CloseText="@Localizer["Close"]">
|
||||||
<Heading>@(Notices.Act == PublicAct.Participation ? Localizer["Joining an event is public"] : Localizer["Reactions and votes are public"])</Heading>
|
<Heading>@(Notices.Act == PublicAct.Participation ? Localizer["Joining an event is public"] : Localizer["Reactions and votes are public"])</Heading>
|
||||||
<ChildContent>
|
<ChildContent>
|
||||||
<p class="text-sm">
|
<p class="text-sm">
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
@* Reports an account, and the post it was opened from, to the persona's server's moderators (/api/v1/reports).
|
@* Reports an account, and the post it was opened from, to the persona's server's moderators (/api/v1/reports).
|
||||||
Forwarded, a remote account's server gets the report too, from PrivaPub's own actor: it never names the persona. *@
|
Forwarded, a remote account's server gets the report too, from PrivaPub's own actor: it never names the persona. *@
|
||||||
<DModal IsOpen="true" IsOpenChanged="Closed" CloseText="@Localizer["Close"]" data-report-dialog>
|
<DModal Placement="DModalPlacement.ResponsiveBottom" IsOpen="true" IsOpenChanged="Closed" CloseText="@Localizer["Close"]" data-report-dialog>
|
||||||
<Heading>@Localizer["Report {0}", Account.Name]</Heading>
|
<Heading>@Localizer["Report {0}", Account.Name]</Heading>
|
||||||
<ChildContent>
|
<ChildContent>
|
||||||
<EditForm Model="form" OnValidSubmit="Send" class="flex flex-col gap-3 mt-3">
|
<EditForm Model="form" OnValidSubmit="Send" class="flex flex-col gap-3 mt-3">
|
||||||
@@ -20,7 +20,10 @@
|
|||||||
<DCheckbox @bind-Value="form.Forward" LabelText="@Localizer["Send a copy to {0}", host]" data-report-forward />
|
<DCheckbox @bind-Value="form.Forward" LabelText="@Localizer["Send a copy to {0}", host]" data-report-forward />
|
||||||
<p class="text-xs opacity-70">@Localizer["The copy comes from the server, never from you."]</p>
|
<p class="text-xs opacity-70">@Localizer["The copy comes from the server, never from you."]</p>
|
||||||
}
|
}
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<DButton OnClick="() => Closed(false)" data-report-cancel>@Localizer["Cancel"]</DButton>
|
||||||
<DButton Type="submit" Color="DColor.Error" Disabled="@(IsDefaultDisabled || isSending)" data-report-send>@Localizer["Report"]</DButton>
|
<DButton Type="submit" Color="DColor.Error" Disabled="@(IsDefaultDisabled || isSending)" data-report-send>@Localizer["Report"]</DButton>
|
||||||
|
</div>
|
||||||
</EditForm>
|
</EditForm>
|
||||||
</ChildContent>
|
</ChildContent>
|
||||||
</DModal>
|
</DModal>
|
||||||
|
|||||||
@@ -262,8 +262,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* At most the screen's height as the phone shows it (dvh: its browser bars come and go), and a bottom sheet leaves room
|
||||||
|
above it and clears the home indicator. */
|
||||||
@utility modal-box {
|
@utility modal-box {
|
||||||
box-shadow: -6px -6px 12px var(--neo-light), 6px 6px 12px var(--neo-dark);
|
box-shadow: -6px -6px 12px var(--neo-light), 6px 6px 12px var(--neo-dark);
|
||||||
|
max-height: 100dvh;
|
||||||
|
|
||||||
|
:where(.modal-bottom) > & {
|
||||||
|
max-height: calc(100dvh - 3rem);
|
||||||
|
padding-bottom: max(1.5rem, env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility dropdown-content {
|
@utility dropdown-content {
|
||||||
|
|||||||
Reference in new issue
Block a user