Improve UI chrome

master
John Montagu, the 4th Earl of Sandvich 2024-12-21 17:18:52 -08:00
parent 95efd01eef
commit 54daa904be
Signed by: sandvich
GPG Key ID: 9A39BE37E602B22D
19 changed files with 231 additions and 96 deletions

View File

@ -34,7 +34,7 @@
--surface-0: #ccd0da;
--base: #eff1f5;
--base-extra: #f5f6f7;
--base-0: #fcfdfe;
--mantle: #e6e9ef;
--crust: #dce0e8;
@ -58,6 +58,7 @@
--lavender: #7287fd;
--accent: var(--lavender);
--accent-0: color-mix(in srgb, var(--accent), var(--text) 50%);
--lavender-transparent: color-mix(in srgb, var(--lavender), transparent 80%);
--accent-transparent-80: color-mix(in srgb, var(--accent), transparent 80%);
--accent-transparent-50: color-mix(in srgb, var(--accent), transparent 50%);
@ -84,18 +85,19 @@
--sapphire: #7dc4e4;
--blue: #8aadf4;
--lavender: #b7bdf8;
--text: #cad3f5;
--subtext-1: #b8c0e0;
--subtext-0: #a5adcb;
--overlay-2: #939ab7;
--overlay-1: #8087a2;
--overlay-0: #6e738d;
--surface-2: #5b6078;
--surface-1: #494d64;
--surface-0: #363a4f;
--base: #24273a;
--mantle: #1e2030;
--crust: #181926;
--text: #dad3d5;
--subtext-1: #bec0c0;
--subtext-0: #abada5;
--overlay-2: #9a9a9a;
--overlay-1: #8b8b8b;
--overlay-0: #717171;
--surface-2: #575757;
--surface-1: #3f3f3f;
--surface-0: #2f2f2f;
--base: #181818;
--base-0: #242424;
--mantle: #121212;
--crust: #0f0f0f;
--destructive: var(--red);
*/
}

View File

@ -24,8 +24,8 @@ button {
align-items: center;
gap: 4px;
color: var(--text);
background-color: var(--crust);
border: none;
background-color: var(--base-0);
border: 1px solid var(--surface-0);
padding: 6px 20px;
line-height: 1.6;
border-radius: 4px;
@ -46,6 +46,10 @@ button {
transition-duration: 200ms;
}
button.no-border {
border: none;
}
button.icon-end {
justify-content: space-between;
}
@ -53,6 +57,7 @@ button.icon-end {
button.icon {
background-color: transparent;
padding: 8px;
border: none;
}
button.icon:hover {
@ -94,6 +99,7 @@ button:hover {
button.accent {
background-color: var(--accent);
color: var(--base);
border-color: var(--accent-0);
/*text-transform: uppercase;*/
}
@ -248,6 +254,11 @@ textarea {
resize: vertical;
}
input:focus, textarea:focus {
box-shadow: 1px 1px 8px var(--surface-0);
outline: none;
}
.form-group {
display: flex;
flex-direction: column;
@ -281,7 +292,7 @@ hr {
}
[role="menu"], [role="listbox"] {
background-color: var(--base);
background-color: var(--base-0);
border: 1px solid var(--overlay-0);
border-radius: 4px;
padding: 4px 0 4px 0;
@ -299,9 +310,10 @@ hr {
}
[role="menu"] button {
background-color: var(--base);
background-color: var(--base-0);
width: 100%;
border-radius: 0;
border: none;
}
[role="menu"] button.destructive {
@ -335,7 +347,7 @@ hr {
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
background-color: var(--base);
background-color: var(--base-0);
z-index: 10;
animation: smooth-appear 0.2s ease;
}
@ -363,3 +375,49 @@ div.banner.info {
background-color: var(--lavender-transparent);
color: var(--lavender);
}
table {
border-collapse: separate;
border-spacing: 0;
}
th:first-child {
border-top-left-radius: 4px;
border-left: 1px solid var(--surface-0);
}
th {
text-align: left;
padding: 0.5rem 1rem;
border-top: 1px solid var(--surface-0);
border-bottom: 1px solid var(--surface-0);
font-size: 10pt;
}
th:last-child {
border-top-right-radius: 4px;
border-right: 1px solid var(--surface-0);
}
td {
padding: 0.5rem 1rem;
border-bottom: 1px solid var(--surface-0);
background-color: var(--base-0);
font-size: 10pt;
}
td:first-child {
border-left: 1px solid var(--surface-0);
}
td:last-child {
border-right: 1px solid var(--surface-0);
}
tr:last-child > td:first-child {
border-bottom-left-radius: 4px;
}
tr:last-child > td:last-child {
border-bottom-right-radius: 4px;
}

View File

@ -8,6 +8,7 @@ const scheduleStore = useScheduleStore();
const model = defineModel<number[]>({ required: true });
const selectedTime = defineModel("selectedTime");
const selectedIndex = defineModel("selectedIndex");
const hoveredIndex = defineModel("hoveredIndex");
@ -142,11 +143,20 @@ function onSlotMouseUp(_: MouseEvent) {
}
function onSlotClick(dayIndex: number, hour: number) {
let index = dayIndex * 24 + hour;
if (isEditing.value) {
return;
}
if (selectedIndex.value == index) {
selectedIndex.value = -1;
selectedTime.value = undefined;
return;
}
selectedTime.value = getTimeAtCell(dayIndex, hour);
selectedIndex.value = index;
scheduleStore.selectIndex(24 * dayIndex + hour);
}
@ -235,6 +245,7 @@ function getHour(offset: number, tz?: string) {
:class="{
'time-slot': true,
'height-24px': true,
'selected': selectedIndex == 24 * dayIndex + hour,
}"
:selection="
selectionInside(dayIndex, hour) ? selectionValue
@ -315,9 +326,34 @@ function getHour(offset: number, tz?: string) {
font-weight: 700;
}
.time-slot:hover, .time-slot.selected {
outline: 2px inset var(--subtext-0);
}
.time-slot.selected {
outline-style: solid;
animation: pulse 1s infinite;
}
.time-slot.selected:hover {
outline-style: solid;
}
@keyframes pulse {
0% {
outline-color: var(--overlay-0);
}
50% {
outline-color: var(--text);
}
100% {
outline-color: var(--overlay-0);
}
}
.time-slot:hover {
background-color: var(--crust);
outline: 2px inset var(--subtext-0);
outline-style: dashed;
}
.time-slot:nth-child(2n):not(:last-child) {

View File

@ -109,7 +109,8 @@ h3 {
.event-card {
display: flex;
align-items: center;
/*background-color: white;*/
background-color: var(--base-0);
border-radius: 8px;
align-items: stretch;
}
@ -139,7 +140,7 @@ h3 {
.details {
padding: 1rem;
border: 1px solid var(--text);
border: 1px solid var(--surface-0);
border-radius: 0 8px 8px 0;
display: flex;
flex-direction: column;

View File

@ -113,11 +113,12 @@ const selectedOption = computed({
<style scoped>
.event-confirm-button {
display: flex;
gap: 2px;
gap: 0px;
}
.left {
border-radius: 4px 0 0 4px;
border-right: none;
}
.right {
@ -128,5 +129,10 @@ const selectedOption = computed({
.confirmed button.recolor {
background-color: var(--text);
color: var(--base);
border-color: var(--text);
}
.confirmed .left.recolor {
border-right: 1px solid var(--surface-0);
}
</style>

View File

@ -1,16 +1,36 @@
<script setup lang="ts">
import type { EventWithPlayerSchema, TeamSchema } from "@/client";
import EventCard from "./EventCard.vue";
import { onMounted, ref } from "vue";
import { useTeamDetails } from "@/composables/team-details";
import { useTeamsStore } from "@/stores/teams";
import { useTeamsEventsStore } from "@/stores/teams/events";
import LoaderContainer from "./LoaderContainer.vue";
import { computed } from "@vue/reactivity";
const props = defineProps<{
events: EventWithPlayerSchema[];
teamContext: TeamSchema;
}>();
const { teamId, team } = useTeamDetails();
const teamsStore = useTeamsStore();
const teamsEventsStore = useTeamsEventsStore();
const isLoading = ref(false);
const events = computed(() => teamsEventsStore.teamEvents[teamId.value]);
onMounted(() => {
isLoading.value = true;
teamsStore.fetchTeam(teamId.value)
.then(() => {
teamsEventsStore.fetchTeamEvents(teamId.value)
.finally(() => isLoading.value = false);
});
});
</script>
<template>
<div class="events-list" v-if="props.events?.length > 0">
<EventCard v-for="event in props.events" :key="event.event.id" :event="event" />
<LoaderContainer v-if="isLoading" height="160">
<rect x="0" y="0" rx="4" ry="4" width="100%" height="160" />
</LoaderContainer>
<div class="events-list" v-else-if="events?.length > 0">
<EventCard v-for="event in events" :key="event.event.id" :event="event" />
</div>
<div class="events-list" v-else>
<em class="subtext">
@ -19,7 +39,7 @@ const props = defineProps<{
:to="{
name: 'schedule',
query: {
teamId: props.teamContext.id
teamId,
}
}"
>

View File

@ -42,14 +42,16 @@ function revokeInvite() {
<td>
{{ createdAt }}
</td>
<td class="buttons">
<button @click="copyLink">
<i class="bi bi-link margin" />
Copy Link
</button>
<button class="destructive" @click="revokeInvite">
<i class="bi bi-trash" />
</button>
<td>
<div class="buttons">
<button @click="copyLink">
<i class="bi bi-link margin" />
Copy Link
</button>
<button class="icon" @click="revokeInvite">
<i class="bi bi-trash" />
</button>
</div>
</td>
</tr>
</template>

View File

@ -65,7 +65,8 @@ const props = defineProps<{
display: flex;
flex-direction: column;
padding: 1rem;
border: 1px solid var(--text);
border: 1px solid var(--surface-0);
background-color: var(--base-0);
border-radius: 8px;
gap: 0.5rem;
}

View File

@ -1,9 +1,10 @@
<script setup lang="ts">
import { useTeamsStore } from "../stores/teams";
import { useRoute, useRouter, RouterLink } from "vue-router";
import { computed } from "vue";
import { computed, onMounted, ref } from "vue";
import { useTeamDetails } from "../composables/team-details";
import PlayerTeamCard from "../components/PlayerTeamCard.vue";
import LoaderContainer from "./LoaderContainer.vue";
const route = useRoute();
const router = useRouter();
@ -15,16 +16,13 @@ const {
availableMembersNextHour,
teamMembers,
} = useTeamDetails();
const isLoading = ref(false);
function leaveTeam() {
teamsStore.leaveTeam(team.value.id)
.then(() => {
teamsStore.fetchTeams()
.then(() => {
router.push("/");
})
});
}
onMounted(() => {
isLoading.value = true;
teamsStore.fetchTeamMembers(team.value.id)
.finally(() => isLoading.value = false);
});
</script>
<template>
@ -39,7 +37,21 @@ function leaveTeam() {
<div class="team-details-button-group">
</div>
</div>
<table class="member-table">
<LoaderContainer v-if="isLoading">
<rect x="0" y="10" rx="3" ry="3" width="100%" height="10" />
<rect x="0" y="30" rx="3" ry="3" width="100%" height="10" />
<rect x="0" y="50" rx="3" ry="3" width="100%" height="10" />
<rect x="0" y="70" rx="3" ry="3" width="100%" height="10" />
</LoaderContainer>
<table class="member-table" v-else>
<thead>
<tr>
<th>Username</th>
<th>Roles</th>
<th>Playtime</th>
<th></th>
</tr>
</thead>
<tbody>
<PlayerTeamCard
v-for="member in teamMembers"
@ -67,12 +79,6 @@ table.member-table {
width: 100%;
}
table.member-table th {
text-align: left;
padding-left: 2em;
font-weight: 700;
}
.team-details-button-group {
flex: 1;
display: flex;

View File

@ -82,14 +82,14 @@ const playtime = computed(() => {
<style scoped>
.player-card {
background-color: white;
background-color: var(--base-0);
padding: 1em;
border-radius: 8px;
user-select: none;
display: flex;
gap: 1em;
align-items: center;
border: 2px solid white;
border: 2px solid var(--surface-0);
box-shadow: 1px 1px 8px var(--surface-0);
}
@ -122,13 +122,13 @@ const playtime = computed(() => {
}
.player-card.no-player {
border: 2px solid var(--overlay-0);
border: 2px dashed var(--overlay-0);
box-shadow: none;
}
.player-card.no-player.selected {
background-color: var(--accent-transparent);
border: 2px solid var(--accent);
border: 2px dashed var(--accent);
color: var(--accent);
}

View File

@ -180,7 +180,7 @@ const rightIndicator = computed(() => {
No roles
</span>
<div class="edit-group">
<button v-if="!isEditing" @click="isEditing = true">
<button v-if="!isEditing" class="icon" @click="isEditing = true">
<i class="bi bi-pencil-fill edit-icon" />
</button>
</div>
@ -197,10 +197,10 @@ const rightIndicator = computed(() => {
<td>
<div class="edit-group">
<template v-if="isEditing">
<button class="editing" @click="cancelEdit()">
<button class="editing icon" @click="cancelEdit()">
<i class="bi bi-x-lg" />
</button>
<button class="editing" @click="updateRoles()">
<button class="editing icon" @click="updateRoles()">
<i class="bi bi-check-lg" />
</button>
</template>
@ -314,10 +314,6 @@ a.player-name:hover {
opacity: 1;
}
.edit-group > button.editing {
opacity: 1;
}
@media (max-width: 1024px) {
.player-card > td {
padding: 0.5em 0em;

View File

@ -18,7 +18,7 @@ function logout() {
<template>
<DropdownMenuRoot>
<DropdownMenuTrigger className="profile-button">
<DropdownMenuTrigger className="profile-button no-border">
{{ authStore.username }}
<i class="bi bi-chevron-down" />
</DropdownMenuTrigger>

View File

@ -47,6 +47,7 @@ function toggle(isMain: boolean) {
</div>
<button
:class="{
'no-border': true,
'center': true,
'selected': roleObject?.isMain
}"
@ -56,6 +57,7 @@ function toggle(isMain: boolean) {
</button>
<button
:class="{
'no-border': true,
'right': true,
'selected': !(roleObject?.isMain ?? true)
}"

View File

@ -21,11 +21,11 @@ function incrementDate(delta: number) {
<template>
<div class="scroll-box">
<button class="transparent eq" @click="incrementDate(-1)" :disabled="isDisabled">
<button class="transparent icon" @click="incrementDate(-1)" :disabled="isDisabled">
<i class="bi bi-caret-left-fill"></i>
</button>
<span class="date-range">{{ dateStart }} &ndash; {{ dateEnd }}</span>
<button class="transparent eq" @click="incrementDate(1)" :disabled="isDisabled">
<button class="transparent icon" @click="incrementDate(1)" :disabled="isDisabled">
<i class="bi bi-caret-right-fill"></i>
</button>
</div>

View File

@ -189,7 +189,6 @@ main {
.radio-group {
display: flex;
gap: 2px;
}
button.radio {
@ -207,6 +206,7 @@ button.radio.selected {
button.left {
border-radius: 4px 0 0 4px;
border-right-width: 0px;
}
button.radio.left.selected {

View File

@ -37,8 +37,8 @@ onMounted(() => {
let doFetchTeam = () => {
teamsStore.fetchTeam(teamId.value)
.then(() => {
teamsStore.fetchTeamMembers(teamId.value);
teamsEventsStore.fetchTeamEvents(teamId.value);
//teamsStore.fetchTeamMembers(teamId.value);
//teamsEventsStore.fetchTeamEvents(teamId.value);
matchesStore.fetchRecentMatchesForTeam(teamId.value, 5);
isLoading.value = false;
});
@ -89,7 +89,7 @@ onMounted(() => {
</div>
<div class="right">
<h2>Upcoming Events</h2>
<EventList :events="events" :team-context="team" />
<EventList />
<h2 id="recent-matches-header">
Recent Matches
<RouterLink class="button" :to="{ name: 'team-settings/matches' }">
@ -98,15 +98,15 @@ onMounted(() => {
</button>
</RouterLink>
</h2>
<em class="subtext" v-if="!matches">
No recent matches.
</em>
<MatchCard
v-else
v-if="matches?.length > 0"
v-for="match in matches"
:team-match="match"
:team="team"
/>
<em class="subtext" v-else>
No recent matches.
</em>
</div>
</div>
</template>
@ -123,6 +123,7 @@ onMounted(() => {
.content-container {
display: flex;
justify-content: space-between;
gap: 1rem;
}
.content-container > div.left {
@ -164,6 +165,7 @@ onMounted(() => {
@media (max-width: 1024px) {
.content-container {
flex-direction: column;
gap: unset;
}
}
</style>

View File

@ -27,6 +27,16 @@ onMounted(() => {
<template>
<div class="invites" v-if="team">
<h2>Invites</h2>
<p class="small aside">
Invite players to your team by creating an invite link.
All invites are usable only once.
</p>
<div class="create-invite-group">
<button class="accent" @click="createInvite">
<i class="bi bi-person-fill-add margin" />
Create Invite
</button>
</div>
<table id="invite-table" v-if="invites?.length > 0">
<thead>
<tr>
@ -36,6 +46,8 @@ onMounted(() => {
<th>
Creation time
</th>
<th>
</th>
</tr>
</thead>
<tbody>
@ -45,22 +57,12 @@ onMounted(() => {
/>
</tbody>
</table>
<div class="create-invite-group">
<button class="accent" @click="createInvite">
<i class="bi bi-person-fill-add margin" />
Create Invite
</button>
<span class="small aside">
Invites are usable once and expire after 24 hours.
</span>
</div>
</div>
</template>
<style scoped>
#invite-table {
width: 100%;
border: 1px solid var(--overlay-0);
border-radius: 8px;
margin: 8px 0;
}
@ -75,5 +77,6 @@ onMounted(() => {
display: flex;
gap: 8px;
align-items: center;
justify-content: end;
}
</style>

View File

@ -25,9 +25,9 @@ onMounted(() => {
<i class="bi bi-trophy-fill margin"></i>
Matches
</h2>
<div class="button-group">
<AddMatchDialog />
</div>
</div>
<div class="button-group">
<AddMatchDialog />
</div>
<table>
<thead>
@ -71,9 +71,4 @@ onMounted(() => {
table {
width: 100%;
}
th {
text-align: left;
font-weight: 800;
}
</style>

View File

@ -46,7 +46,7 @@ onMounted(() => {
Matches
</RouterLink>
<hr>
<button class="destructive-on-hover icon-end" @click="leaveTeam">
<button class="destructive-on-hover icon-end no-border" @click="leaveTeam">
Leave team
<i class="bi bi-box-arrow-left" />
</button>
@ -122,4 +122,9 @@ nav.sidebar button:hover {
background-color: var(--crust);
color: var(--text);
}
nav.sidebar button.destructive-on-hover:hover {
background-color: var(--destructive);
color: var(--base);
}
</style>