mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-30 05:41:12 +00:00
Navbar Icons Improvements (#1246)
Some improvements to the navbar icons. Changes Implemented: - Increase touch target size for navbar icons. - Make icon colors and hover effect consistent with navbar links - Added title for all navbar icons - New key (user.settings) in locales - Updated Dark and Light Mode values in locales - Minor tweaks in active builds indicator - New component NavbarIcon (because trying to match IconButton size and colors felt hacky at best) Co-authored-by: Divya Jain <dvjn.dev+git@gmail.com>
This commit is contained in:
parent
38198f83c4
commit
ed7ecb060e
6 changed files with 112 additions and 46 deletions
1
web/components.d.ts
vendored
1
web/components.d.ts
vendored
|
@ -65,6 +65,7 @@ declare module '@vue/runtime-core' {
|
|||
ListItem: typeof import('./src/components/atomic/ListItem.vue')['default']
|
||||
ManualPipelinePopup: typeof import('./src/components/layout/popups/ManualPipelinePopup.vue')['default']
|
||||
Navbar: typeof import('./src/components/layout/header/Navbar.vue')['default']
|
||||
NavbarIcon: typeof import('./src/components/layout/header/NavbarIcon.vue')['default']
|
||||
NumberField: typeof import('./src/components/form/NumberField.vue')['default']
|
||||
OrgSecretsTab: typeof import('./src/components/org/settings/OrgSecretsTab.vue')['default']
|
||||
Panel: typeof import('./src/components/layout/Panel.vue')['default']
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
"password": "Password",
|
||||
"url": "URL",
|
||||
"back": "Back",
|
||||
"color_scheme_light": "Switch between dark and light mode (currently light mode)",
|
||||
"color_scheme_dark": "Switch between dark and light mode (currently dark mode)",
|
||||
"color_scheme_light": "Switch to dark mode",
|
||||
"color_scheme_dark": "Switch to light mode",
|
||||
"unknown_error": "An unknown error occurred",
|
||||
"not_found": {
|
||||
"not_found": "Whoa 404, either we broke something or you had a typing mishap :-/",
|
||||
|
@ -292,6 +292,7 @@
|
|||
}
|
||||
},
|
||||
"user": {
|
||||
"settings": "User Settings",
|
||||
"oauth_error": "Error while authenticating against OAuth provider",
|
||||
"internal_error": "Some internal error occurred",
|
||||
"access_denied": "You are not allowed to login",
|
||||
|
|
|
@ -8,13 +8,7 @@
|
|||
@click="doClick"
|
||||
>
|
||||
<Icon :name="icon" />
|
||||
<div
|
||||
class="absolute left-0 top-0 right-0 bottom-0 flex items-center justify-center"
|
||||
:class="{
|
||||
'opacity-100': isLoading,
|
||||
'opacity-0': !isLoading,
|
||||
}"
|
||||
>
|
||||
<div v-if="isLoading" class="absolute left-0 top-0 right-0 bottom-0 flex items-center justify-center">
|
||||
<Icon name="loading" class="animate-spin" />
|
||||
</div>
|
||||
</button>
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
<template>
|
||||
<button
|
||||
class="flex rounded-full w-8 h-8 bg-opacity-30 hover:bg-opacity-50 bg-white items-center justify-center cursor-pointer text-white select-none"
|
||||
:class="{
|
||||
spinner: activePipelines.length !== 0,
|
||||
}"
|
||||
type="button"
|
||||
@click="toggle"
|
||||
>
|
||||
<NavbarIcon :title="$t('repo.pipeline.tasks')" class="!p-1.5 relative" @click="toggle">
|
||||
<div v-if="activePipelines.length > 0" class="spinner">
|
||||
<div class="spinner-ring ring1" />
|
||||
<div class="spinner-ring ring2" />
|
||||
<div class="spinner-ring ring3" />
|
||||
<div class="spinner-ring ring4" />
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center justify-center h-full w-full font-bold bg-white bg-opacity-15 dark:bg-black dark:bg-opacity-10 rounded-full"
|
||||
>
|
||||
{{ activePipelines.length || 0 }}
|
||||
</button>
|
||||
</div>
|
||||
</NavbarIcon>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -20,9 +19,13 @@ import { defineComponent, onMounted } from 'vue';
|
|||
|
||||
import usePipelineFeed from '~/compositions/usePipelineFeed';
|
||||
|
||||
import NavbarIcon from './NavbarIcon.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ActivePipelines',
|
||||
|
||||
components: { NavbarIcon },
|
||||
|
||||
setup() {
|
||||
const pipelineFeed = usePipelineFeed();
|
||||
|
||||
|
@ -36,10 +39,13 @@ export default defineComponent({
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
.spinner {
|
||||
@apply absolute top-0 bottom-0 left-0 right-0;
|
||||
}
|
||||
.spinner .spinner-ring {
|
||||
animation: spinner 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
|
||||
border-color: #fff transparent transparent transparent;
|
||||
@apply w-8 h-8 border-2 rounded-full m-4 absolute;
|
||||
@apply border-3 rounded-full absolute top-1.5 bottom-1.5 left-1.5 right-1.5;
|
||||
}
|
||||
.spinner .ring1 {
|
||||
animation-delay: -0.45s;
|
||||
|
|
|
@ -9,36 +9,45 @@
|
|||
<span class="text-xs">{{ version }}</span>
|
||||
</router-link>
|
||||
<!-- Repo Link -->
|
||||
<router-link v-if="user" :to="{ name: 'repos' }" class="navbar-link">
|
||||
<router-link v-if="user" :to="{ name: 'repos' }" class="navbar-link navbar-clickable">
|
||||
<span class="flex md:hidden">{{ $t('repos') }}</span>
|
||||
<span class="hidden md:flex">{{ $t('repositories') }}</span>
|
||||
</router-link>
|
||||
<!-- Docs Link -->
|
||||
<a :href="docsUrl" target="_blank" class="navbar-link hidden md:flex">{{ $t('docs') }}</a>
|
||||
<a :href="docsUrl" target="_blank" class="navbar-link navbar-clickable hidden md:flex">{{ $t('docs') }}</a>
|
||||
</div>
|
||||
<!-- Right Icons Box -->
|
||||
<div class="flex ml-auto items-center space-x-3 text-white dark:text-gray-400">
|
||||
<div class="flex ml-auto -m-1.5 items-center space-x-2 text-white dark:text-gray-400">
|
||||
<!-- Dark Mode Toggle -->
|
||||
<IconButton
|
||||
:icon="darkMode ? 'dark' : 'light'"
|
||||
class="!text-white !dark:text-gray-500 navbar-icon"
|
||||
:title="darkMode ? $t('color_scheme_dark') : $t('color_scheme_light')"
|
||||
<NavbarIcon
|
||||
:title="$t(darkMode ? 'color_scheme_dark' : 'color_scheme_light')"
|
||||
class="navbar-icon navbar-clickable"
|
||||
@click="darkMode = !darkMode"
|
||||
/>
|
||||
>
|
||||
<i-ic-baseline-dark-mode v-if="darkMode" />
|
||||
<i-ic-round-light-mode v-else />
|
||||
</NavbarIcon>
|
||||
<!-- Admin Settings -->
|
||||
<IconButton
|
||||
<NavbarIcon
|
||||
v-if="user?.admin"
|
||||
icon="settings"
|
||||
class="!text-white !dark:text-gray-500 navbar-icon"
|
||||
class="navbar-icon navbar-clickable"
|
||||
:title="$t('admin.settings.settings')"
|
||||
:to="{ name: 'admin-settings' }"
|
||||
/>
|
||||
<!-- Active Builds Indicator -->
|
||||
<ActivePipelines v-if="user" />
|
||||
>
|
||||
<i-clarity-settings-solid />
|
||||
</NavbarIcon>
|
||||
|
||||
<!-- Active Pipelines Indicator -->
|
||||
<ActivePipelines v-if="user" class="navbar-icon navbar-clickable" />
|
||||
<!-- User Avatar -->
|
||||
<router-link v-if="user" :to="{ name: 'user' }" class="rounded-full overflow-hidden">
|
||||
<img v-if="user && user.avatar_url" class="navbar-icon" :src="`${user.avatar_url}`" />
|
||||
</router-link>
|
||||
<NavbarIcon
|
||||
v-if="user"
|
||||
:to="{ name: 'user' }"
|
||||
:title="$t('user.settings')"
|
||||
class="navbar-icon navbar-clickable !p-1.5"
|
||||
>
|
||||
<img v-if="user && user.avatar_url" class="rounded-full" :src="`${user.avatar_url}`" />
|
||||
</NavbarIcon>
|
||||
<!-- Login Button -->
|
||||
<Button v-else :text="$t('login')" @click="doLogin" />
|
||||
</div>
|
||||
|
@ -50,17 +59,17 @@ import { defineComponent } from 'vue';
|
|||
import { useRoute } from 'vue-router';
|
||||
|
||||
import Button from '~/components/atomic/Button.vue';
|
||||
import IconButton from '~/components/atomic/IconButton.vue';
|
||||
import useAuthentication from '~/compositions/useAuthentication';
|
||||
import useConfig from '~/compositions/useConfig';
|
||||
import { useDarkMode } from '~/compositions/useDarkMode';
|
||||
|
||||
import ActivePipelines from './ActivePipelines.vue';
|
||||
import NavbarIcon from './NavbarIcon.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Navbar',
|
||||
|
||||
components: { Button, ActivePipelines, IconButton },
|
||||
components: { Button, ActivePipelines, NavbarIcon },
|
||||
|
||||
setup() {
|
||||
const config = useConfig();
|
||||
|
@ -82,10 +91,10 @@ export default defineComponent({
|
|||
|
||||
<style scoped>
|
||||
.navbar-link {
|
||||
@apply hover:bg-black hover:bg-opacity-10 transition-colors duration-100 px-3 py-2 -my-1 rounded-md;
|
||||
@apply px-3 py-2 -my-1 rounded-md;
|
||||
}
|
||||
|
||||
.navbar-icon {
|
||||
@apply w-8 h-8;
|
||||
.navbar-clickable {
|
||||
@apply hover:bg-black hover:bg-opacity-10 dark:hover:bg-white dark:hover:bg-opacity-5 transition-colors duration-100;
|
||||
}
|
||||
</style>
|
||||
|
|
55
web/src/components/layout/header/NavbarIcon.vue
Normal file
55
web/src/components/layout/header/NavbarIcon.vue
Normal file
|
@ -0,0 +1,55 @@
|
|||
<template>
|
||||
<button type="button" :title="title" :aria-label="title" class="navbar-icon" @click="doClick">
|
||||
<slot />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import { RouteLocationRaw, useRouter } from 'vue-router';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NavbarIcon',
|
||||
|
||||
props: {
|
||||
to: {
|
||||
type: [String, Object, null] as PropType<RouteLocationRaw | null>,
|
||||
default: null,
|
||||
},
|
||||
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const router = useRouter();
|
||||
|
||||
async function doClick() {
|
||||
if (!props.to) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof props.to === 'string' && props.to.startsWith('http')) {
|
||||
window.location.href = props.to;
|
||||
return;
|
||||
}
|
||||
|
||||
await router.push(props.to);
|
||||
}
|
||||
|
||||
return { doClick };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.navbar-icon {
|
||||
@apply w-11 h-11 rounded-full p-2.5;
|
||||
}
|
||||
|
||||
.navbar-icon :deep(svg) {
|
||||
@apply w-full h-full;
|
||||
}
|
||||
</style>
|
Loading…
Reference in a new issue