feat: Response mobile layout support v1
All checks were successful
Queue Release Build / prepare (push) Successful in 1m6s
Deploy Web Apps / deploy (push) Successful in 7m35s
Queue Release Build / build-windows (push) Successful in 29m57s
Queue Release Build / build-linux (push) Successful in 46m28s
Queue Release Build / finalize (push) Successful in 49s
All checks were successful
Queue Release Build / prepare (push) Successful in 1m6s
Deploy Web Apps / deploy (push) Successful in 7m35s
Queue Release Build / build-windows (push) Successful in 29m57s
Queue Release Build / build-linux (push) Successful in 46m28s
Queue Release Build / finalize (push) Successful in 49s
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
<div
|
||||
appThemeNode="profileCardSurface"
|
||||
class="flex w-full flex-col bg-card text-foreground"
|
||||
>
|
||||
@let profileUser = displayedUser();
|
||||
@let statusColor = currentStatusColor();
|
||||
@let statusLabel = currentStatusLabel();
|
||||
@let self = isSelf();
|
||||
@let friend = isFriend();
|
||||
@let isEditable = editable();
|
||||
@let activeField = editingField();
|
||||
|
||||
<div
|
||||
appThemeNode="profileCardBanner"
|
||||
class="h-24 bg-gradient-to-r from-primary/30 to-primary/10"
|
||||
></div>
|
||||
|
||||
<div class="-mt-12 flex flex-col items-center px-6">
|
||||
<div class="relative">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-full"
|
||||
[disabled]="!isEditable || avatarSaving()"
|
||||
(click)="pickAvatar(avatarInput)"
|
||||
>
|
||||
<app-user-avatar
|
||||
[name]="profileUser.displayName"
|
||||
[avatarUrl]="profileUser.avatarUrl"
|
||||
size="xl"
|
||||
[status]="profileUser.status"
|
||||
[showStatusBadge]="true"
|
||||
ringClass="ring-4 ring-card"
|
||||
/>
|
||||
</button>
|
||||
@if (isEditable) {
|
||||
<span
|
||||
class="pointer-events-none absolute bottom-1 right-1 flex h-7 w-7 items-center justify-center rounded-full border-2 border-card bg-primary text-primary-foreground shadow"
|
||||
>
|
||||
<ng-icon
|
||||
name="lucideCamera"
|
||||
class="h-3.5 w-3.5"
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
<input
|
||||
#avatarInput
|
||||
type="file"
|
||||
class="hidden"
|
||||
[accept]="avatarAccept"
|
||||
(change)="onAvatarSelected($event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 w-full text-center">
|
||||
@if (isEditable && activeField === 'displayName') {
|
||||
<input
|
||||
type="text"
|
||||
class="w-full rounded-lg border border-border bg-background/70 px-3 py-2 text-center text-lg font-semibold text-foreground outline-none focus:border-primary/70"
|
||||
[value]="displayNameDraft()"
|
||||
(input)="onDisplayNameInput($event)"
|
||||
(blur)="finishEdit('displayName')"
|
||||
/>
|
||||
} @else if (isEditable) {
|
||||
<button
|
||||
type="button"
|
||||
class="w-full text-center text-xl font-semibold text-foreground hover:underline"
|
||||
(click)="startEdit('displayName')"
|
||||
>
|
||||
{{ profileUser.displayName }}
|
||||
</button>
|
||||
} @else {
|
||||
<h2 class="text-center text-xl font-semibold text-foreground">{{ profileUser.displayName }}</h2>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (profileUser.username && profileUser.username !== profileUser.displayName) {
|
||||
<p class="mt-0.5 text-sm text-muted-foreground">{{ '@' + profileUser.username }}</p>
|
||||
}
|
||||
|
||||
@if (isEditable) {
|
||||
<div class="relative mt-3 w-full max-w-[14rem]">
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full items-center gap-2 rounded-full border border-border bg-secondary/40 px-3 py-1.5 text-sm transition-colors hover:bg-secondary"
|
||||
(click)="toggleStatusMenu()"
|
||||
>
|
||||
<span
|
||||
class="h-2 w-2 rounded-full"
|
||||
[class]="statusColor"
|
||||
></span>
|
||||
<span class="flex-1 text-left text-foreground">{{ statusLabel }}</span>
|
||||
<ng-icon
|
||||
name="lucideChevronDown"
|
||||
class="h-3.5 w-3.5 text-muted-foreground"
|
||||
/>
|
||||
</button>
|
||||
|
||||
@if (showStatusMenu()) {
|
||||
<div class="absolute left-0 right-0 top-full z-10 mt-1 rounded-lg border border-border bg-card py-1 shadow-lg">
|
||||
@for (opt of statusOptions; track opt.label) {
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full items-center gap-2 px-3 py-2 text-left text-sm hover:bg-secondary"
|
||||
[class.bg-secondary]="isStatusOptionSelected(opt.value)"
|
||||
[class.text-foreground]="isStatusOptionSelected(opt.value)"
|
||||
[class.text-muted-foreground]="!isStatusOptionSelected(opt.value)"
|
||||
(click)="setStatus(opt.value)"
|
||||
>
|
||||
<span
|
||||
class="h-2 w-2 rounded-full"
|
||||
[class]="opt.color"
|
||||
></span>
|
||||
<span class="flex-1">{{ opt.label }}</span>
|
||||
@if (isStatusOptionSelected(opt.value)) {
|
||||
<ng-icon
|
||||
name="lucideCheck"
|
||||
class="h-4 w-4 text-primary"
|
||||
/>
|
||||
}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<div class="mt-2 inline-flex items-center gap-1.5 rounded-full bg-secondary/40 px-2.5 py-1 text-xs text-muted-foreground">
|
||||
<span
|
||||
class="h-2 w-2 rounded-full"
|
||||
[class]="statusColor"
|
||||
></span>
|
||||
<span>{{ statusLabel }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="mt-4 space-y-3 px-6 pb-2">
|
||||
@if (isEditable) {
|
||||
@if (activeField === 'description') {
|
||||
<textarea
|
||||
rows="3"
|
||||
class="w-full resize-none rounded-lg border border-border bg-background/70 px-3 py-2 text-sm leading-5 text-foreground outline-none focus:border-primary/70"
|
||||
[value]="descriptionDraft()"
|
||||
placeholder="Add a description"
|
||||
(input)="onDescriptionInput($event)"
|
||||
(blur)="finishEdit('description')"
|
||||
></textarea>
|
||||
} @else {
|
||||
<button
|
||||
type="button"
|
||||
class="block w-full rounded-lg border border-dashed border-border/70 bg-background/30 px-3 py-2 text-left text-sm leading-5"
|
||||
(click)="startEdit('description')"
|
||||
>
|
||||
@if (profileUser.description) {
|
||||
<span class="whitespace-pre-line text-muted-foreground">{{ profileUser.description }}</span>
|
||||
} @else {
|
||||
<span class="text-muted-foreground/70">Add a description</span>
|
||||
}
|
||||
</button>
|
||||
}
|
||||
} @else if (profileUser.description) {
|
||||
<p class="whitespace-pre-line text-center text-sm leading-5 text-muted-foreground">
|
||||
{{ profileUser.description }}
|
||||
</p>
|
||||
}
|
||||
|
||||
@if (avatarError()) {
|
||||
<div class="rounded-md border border-red-500/40 bg-red-500/10 px-3 py-2 text-xs text-red-200">
|
||||
{{ avatarError() }}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (profileUser.gameActivity; as activity) {
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full items-center gap-3 rounded-xl border border-border bg-background/40 px-3 py-2 text-left"
|
||||
[disabled]="!activity.store?.url"
|
||||
(click)="openGameStore($event)"
|
||||
>
|
||||
@if (activity.iconUrl) {
|
||||
<img
|
||||
class="h-10 w-10 shrink-0 rounded-md object-cover"
|
||||
[src]="activity.iconUrl"
|
||||
alt=""
|
||||
/>
|
||||
} @else {
|
||||
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-md bg-primary/10 text-primary">
|
||||
<ng-icon
|
||||
name="lucideGamepad2"
|
||||
class="h-5 w-5"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-sm font-medium text-foreground">Playing {{ activity.name }}</p>
|
||||
<p class="truncate text-xs text-muted-foreground">{{ gameActivityElapsed() }}</p>
|
||||
</div>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (!self) {
|
||||
<div class="grid grid-cols-1 gap-2 px-6 pb-6 pt-4">
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex h-12 w-full items-center justify-center gap-2 rounded-xl bg-primary px-4 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
[disabled]="busy()"
|
||||
(click)="startChat()"
|
||||
>
|
||||
<ng-icon
|
||||
name="lucideMessageCircle"
|
||||
class="h-5 w-5"
|
||||
/>
|
||||
<span>Start chat</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex h-12 w-full items-center justify-center gap-2 rounded-xl border border-border bg-secondary/40 px-4 text-sm font-medium text-foreground transition-colors hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"
|
||||
[disabled]="busy()"
|
||||
(click)="startCall()"
|
||||
>
|
||||
<ng-icon
|
||||
name="lucidePhone"
|
||||
class="h-5 w-5"
|
||||
/>
|
||||
<span>Call</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex h-12 w-full items-center justify-center gap-2 rounded-xl border border-border bg-secondary/20 px-4 text-sm font-medium text-foreground transition-colors hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"
|
||||
[disabled]="busy()"
|
||||
(click)="toggleFriend()"
|
||||
>
|
||||
@if (friend) {
|
||||
<ng-icon
|
||||
name="lucideUserMinus"
|
||||
class="h-5 w-5"
|
||||
/>
|
||||
<span>Remove friend</span>
|
||||
} @else {
|
||||
<ng-icon
|
||||
name="lucideUserPlus"
|
||||
class="h-5 w-5"
|
||||
/>
|
||||
<span>Add friend</span>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="px-6 pb-6 pt-2"></div>
|
||||
}
|
||||
</div>
|
||||
Reference in New Issue
Block a user