Writing on a phone: the composer rises as a sheet
On a phone the composer opened at the top of the feed whether or not the feed was scrolled there, and a reply, quote or edit opened inside its card's 228px column. New PhoneSheet shows them as a sheet from the bottom of the screen below md, over a dimmed page, with a heading and a close button (a tap outside or Escape closes it too); from md they stay in place as before. On a phone a new post starts from a round button under the thumb (the feed keeps room for it at the bottom), and the tabs' Post button shows from md. Opening the composer scrolls the feed to its top, and the composer's words take the focus (DTextarea.Element). Picking a suggested mention or hashtag keeps the focus, and the phone's keyboard, in the words (DButton.KeepFocus, and the words are focused again). The character count sits under the words instead of over their last line, and the visibility menu names each choice. 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
b3bb2a28c3
commit
071bcce4b0
9 files changed
+108
-30
No files matched your search
+26
-10
@@ -17,18 +17,19 @@
|
||||
<Prefix><i class="ion-md-warning text-warning"></i></Prefix>
|
||||
</DInput>
|
||||
}
|
||||
<div class="relative">
|
||||
<DTextarea TValue="string" @bind-Value="form.Content" @bind-Value:after="Typed" Immediate="true"
|
||||
<div class="flex flex-col gap-1">
|
||||
<DTextarea @ref="textarea" TValue="string" @bind-Value="form.Content" @bind-Value:after="Typed" Immediate="true"
|
||||
class="@(form.HasContentWarning ? "w-full rounded-b-[1.4rem] rounded-t-lg" : "w-full rounded-[1.4rem]")" maxlength="@ComposeForm.MaxContentLength" autocomplete="off"
|
||||
placeholder="@Placeholder" rows="3" data-compose />
|
||||
<span class="absolute text-xs opacity-50 right-3 bottom-2">@ContentLength/@ComposeForm.MaxContentLength</span>
|
||||
@* under the words, not over their last line *@
|
||||
<span class="self-end text-xs opacity-50 tabular-nums">@ContentLength/@ComposeForm.MaxContentLength</span>
|
||||
</div>
|
||||
@if (suggestedAccounts.Count > 0 || suggestedTags.Count > 0)
|
||||
{
|
||||
<div class="flex flex-wrap gap-2" data-suggestions>
|
||||
@foreach (var account in suggestedAccounts)
|
||||
{
|
||||
<DButton Size="DSize.Xs" class="max-w-full" OnClick="@(() => Complete("@" + account.Acct))" data-suggestion="@("@" + account.Acct)">
|
||||
<DButton Size="DSize.Xs" class="max-w-full" KeepFocus="true" OnClick="@(() => Complete("@" + account.Acct))" data-suggestion="@("@" + account.Acct)">
|
||||
@* alt="": the handle is next to it. *@
|
||||
<img alt="" class="h-4 w-4 flex-none rounded-full object-cover" src="@(account.Avatar ?? "imgs/icon-192.png")" />
|
||||
<span class="truncate">@("@" + account.Acct)</span>
|
||||
@@ -36,7 +37,7 @@
|
||||
}
|
||||
@foreach (var tag in suggestedTags)
|
||||
{
|
||||
<DButton Size="DSize.Xs" OnClick="@(() => Complete("#" + tag.Name))" data-suggestion="@("#" + tag.Name)">#@tag.Name</DButton>
|
||||
<DButton Size="DSize.Xs" KeepFocus="true" OnClick="@(() => Complete("#" + tag.Name))" data-suggestion="@("#" + tag.Name)">#@tag.Name</DButton>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@@ -131,16 +132,18 @@
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
@if (!form.IsLocated && GroupId is null && Editing is null)
|
||||
{
|
||||
<DDropdown @bind-IsOpen="form.IsScopeOptionsOpen" TriggerClass="@(form.IsScopeOptionsOpen ? "btn btn-sm btn-active" : "btn btn-sm")"
|
||||
ContentClass="mt-2 flex gap-2 p-2 rounded-box" title="@Localizer["Message scope type: {0}", Localizer[form.MessageType.ToString()]]">
|
||||
<DDropdown @bind-IsOpen="form.IsScopeOptionsOpen" TriggerClass="@(form.IsScopeOptionsOpen ? "btn btn-sm btn-active" : "btn btn-sm")" SheetOnNarrow="true"
|
||||
ContentClass="mt-2 flex flex-col gap-2 p-2 rounded-box" title="@Localizer["Message scope type: {0}", Localizer[form.MessageType.ToString()]]"
|
||||
aria-label="@Localizer["Message scope type: {0}", Localizer[form.MessageType.ToString()]]">
|
||||
<Trigger>
|
||||
<i class="@form.MessageType.GetMessageTypeIcon() text-base"></i>
|
||||
<i class="@form.MessageType.GetMessageTypeIcon() text-base" aria-hidden="true"></i>
|
||||
</Trigger>
|
||||
<ChildContent>
|
||||
@foreach (var messageType in Enum.GetValues<MessageType>())
|
||||
{
|
||||
<DButton Size="DSize.Sm" Active="form.MessageType == messageType" OnClick="() => SetMessageType(messageType)" title="@Localizer[messageType.ToString()]">
|
||||
<i class="@messageType.GetMessageTypeIcon() text-base"></i>
|
||||
<DButton Size="DSize.Sm" class="justify-start whitespace-nowrap" Active="form.MessageType == messageType" OnClick="() => SetMessageType(messageType)">
|
||||
<i class="@messageType.GetMessageTypeIcon() text-base" aria-hidden="true"></i>
|
||||
<span>@Localizer[messageType.ToString()]</span>
|
||||
</DButton>
|
||||
}
|
||||
</ChildContent>
|
||||
@@ -293,6 +296,7 @@
|
||||
if (!typed.Success)
|
||||
return;
|
||||
form.Content = form.Content[..typed.Groups[1].Index] + written + " ";
|
||||
focusPending = true;
|
||||
}
|
||||
|
||||
string Placeholder => Editing is not null ? Localizer["Edit your post"]
|
||||
@@ -340,6 +344,18 @@
|
||||
_ => MessageType.Public
|
||||
};
|
||||
|
||||
DTextarea<string> textarea;
|
||||
// the words take the focus when the composer opens, and again after a suggestion is picked
|
||||
bool focusPending = true;
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (!focusPending)
|
||||
return;
|
||||
focusPending = false;
|
||||
await textarea.Element.FocusAsync();
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
form.GroupId = GroupId;
|
||||
|
||||
Reference in new issue
Block a user