feat(ui): [WIP] new `Link` component, use for sidebar links
This commit is contained in:
		
							parent
							
								
									8ea49dd251
								
							
						
					
					
						commit
						ad9e3dd4b0
					
				| 
						 | 
					@ -0,0 +1,182 @@
 | 
				
			||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import { computed } from 'vue';
 | 
				
			||||||
 | 
					import { type RouterLinkProps, RouterLink } from 'vue-router';
 | 
				
			||||||
 | 
					const { to, icon } = defineProps<RouterLinkProps & {
 | 
				
			||||||
 | 
					    icon?: string;
 | 
				
			||||||
 | 
					    variant?: 'solid' | 'outline' | 'ghost'
 | 
				
			||||||
 | 
					  }>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const isExternalLink = computed(() => {
 | 
				
			||||||
 | 
					  return typeof to === 'string' && to.startsWith('http')
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <a v-if="isExternalLink" :class="$style.external" :href="to?.toString()" target="_blank">
 | 
				
			||||||
 | 
					      <slot />
 | 
				
			||||||
 | 
					  </a>
 | 
				
			||||||
 | 
					  <RouterLink v-if="to && !isExternalLink" :to="to" v-slot="{ isActive, href, navigate }">
 | 
				
			||||||
 | 
					     <a :href="href" @click="navigate" :class="{ [$style.active]: isActive }">
 | 
				
			||||||
 | 
					      <slot />
 | 
				
			||||||
 | 
					      <i v-if="icon" :class="['bi', icon]" />
 | 
				
			||||||
 | 
					    </a>
 | 
				
			||||||
 | 
					  </RouterLink>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style module lang="scss">
 | 
				
			||||||
 | 
					  .active { background:red; }
 | 
				
			||||||
 | 
					  .external { outline: 3px dotted blue; }
 | 
				
			||||||
 | 
					  a {
 | 
				
			||||||
 | 
					    background-color: var(--fw-bg-color);
 | 
				
			||||||
 | 
					    color: var(--fw-text-color);
 | 
				
			||||||
 | 
					    border: 1px solid var(--fw-bg-color);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    display: inline-flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    white-space: nowrap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    font-family: $font-main;
 | 
				
			||||||
 | 
					    font-weight: 900;
 | 
				
			||||||
 | 
					    font-size: 0.875em;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    line-height: 1em;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    padding: 0.642857142857em;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    border-radius: var(--fw-border-radius);
 | 
				
			||||||
 | 
					    margin: 0 0.5ch;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    transform: translateX(var(--fw-translate-x)) translateY(var(--fw-translate-y)) scale(var(--fw-scale));
 | 
				
			||||||
 | 
					    transition: all .2s ease;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @include light-theme {
 | 
				
			||||||
 | 
					      &.is-secondary.is-outline {
 | 
				
			||||||
 | 
					        --fw-bg-color: var(--fw-gray-600);
 | 
				
			||||||
 | 
					        --fw-text-color: var(--fw-gray-700);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        &[disabled] {
 | 
				
			||||||
 | 
					          --fw-bg-color: var(--fw-gray-600) !important;
 | 
				
			||||||
 | 
					          --fw-text-color: var(--fw-gray-600) !important;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        &.is-hovered,
 | 
				
			||||||
 | 
					        &:hover {
 | 
				
			||||||
 | 
					          --fw-bg-color: var(--fw-gray-700) !important;
 | 
				
			||||||
 | 
					          --fw-text-color: var(--fw-gray-800) !important;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        &.is-active,
 | 
				
			||||||
 | 
					        &:active {
 | 
				
			||||||
 | 
					          --fw-text-color: var(--fw-red-010) !important;
 | 
				
			||||||
 | 
					          border: 1px solid var(--fw-gray-600) !important;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      &.is-outline {
 | 
				
			||||||
 | 
					        &:not(:active):not(.is-active) {
 | 
				
			||||||
 | 
					          background-color: transparent !important;
 | 
				
			||||||
 | 
					          --fw-text-color:--fw-gray-400;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      &.is-ghost {
 | 
				
			||||||
 | 
					        &:not(:active):not(.is-active):not(:hover):not(.is-hovered) {
 | 
				
			||||||
 | 
					          background-color: transparent !important;
 | 
				
			||||||
 | 
					          border-color: transparent !important;
 | 
				
			||||||
 | 
					          --fw-text-color:--fw-gray-400;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @include dark-theme {
 | 
				
			||||||
 | 
					      &.is-secondary.is-outline {
 | 
				
			||||||
 | 
					        --fw-bg-color: var(--fw-gray-500);
 | 
				
			||||||
 | 
					        --fw-text-color: var(--fw-gray-400);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        &[disabled] {
 | 
				
			||||||
 | 
					          --fw-bg-color: var(--fw-gray-600) !important;
 | 
				
			||||||
 | 
					          --fw-text-color: var(--fw-gray-700) !important;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        &.is-hovered,
 | 
				
			||||||
 | 
					        &:hover {
 | 
				
			||||||
 | 
					          --fw-bg-color: var(--fw-gray-600) !important;
 | 
				
			||||||
 | 
					          --fw-text-color: var(--fw-gray-500) !important;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        &.is-active,
 | 
				
			||||||
 | 
					        &:active {
 | 
				
			||||||
 | 
					          --fw-text-color: var(--fw-red-010) !important;
 | 
				
			||||||
 | 
					          --fw-bg-color: var(--fw-gray-700) !important;
 | 
				
			||||||
 | 
					          border: 1px solid var(--fw-gray-600) !important;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      &.is-outline {
 | 
				
			||||||
 | 
					        &:not(:active):not(.is-active) {
 | 
				
			||||||
 | 
					          background-color: transparent !important;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      &.is-ghost {
 | 
				
			||||||
 | 
					        &:not(:active):not(.is-active):not(:hover):not(.is-hovered) {
 | 
				
			||||||
 | 
					          background-color: transparent !important;
 | 
				
			||||||
 | 
					          border-color: transparent !important;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &.is-aligned-center {
 | 
				
			||||||
 | 
					      justify-content: center;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &.is-aligned-left {
 | 
				
			||||||
 | 
					      justify-content: flex-start;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &.is-aligned-right {
 | 
				
			||||||
 | 
					      justify-content: flex-end;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &.is-shadow {
 | 
				
			||||||
 | 
					      box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.2);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &:not(.icon-only):not(.is-auto) {
 | 
				
			||||||
 | 
					      min-width: 8.5rem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &.is-full {
 | 
				
			||||||
 | 
					      display: block;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &.is-round {
 | 
				
			||||||
 | 
					      border-radius: 100vh;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &[disabled] {
 | 
				
			||||||
 | 
					      font-weight: normal;
 | 
				
			||||||
 | 
					      pointer-events: none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &.is-loading {
 | 
				
			||||||
 | 
					      @extend .is-active;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      > span {
 | 
				
			||||||
 | 
					        opacity: 0;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    i.bi {
 | 
				
			||||||
 | 
					      font-size: 1.2rem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    i.bi + span:not(:empty) {
 | 
				
			||||||
 | 
					      margin-left: 1ch;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -7,6 +7,7 @@ import { useStore } from '~/store'
 | 
				
			||||||
import Button from '~/components/ui/Button.vue'
 | 
					import Button from '~/components/ui/Button.vue'
 | 
				
			||||||
import Input from '~/components/ui/Input.vue'
 | 
					import Input from '~/components/ui/Input.vue'
 | 
				
			||||||
import Pill from '~/components/ui/Pill.vue'
 | 
					import Pill from '~/components/ui/Pill.vue'
 | 
				
			||||||
 | 
					import Link from '~/components/ui/Link.vue'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const searchQuery = ref('')
 | 
					const searchQuery = ref('')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -99,13 +100,13 @@ const uploads = useUploadsStore()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <h3>Explore</h3>
 | 
					      <h3>Explore</h3>
 | 
				
			||||||
      <nav class="button-list">
 | 
					      <nav class="button-list">
 | 
				
			||||||
        <Button
 | 
					        <Link to="/content"
 | 
				
			||||||
          color="secondary"
 | 
					          color="secondary"
 | 
				
			||||||
          variant="ghost"
 | 
					          variant="ghost"
 | 
				
			||||||
          icon="bi-compass"
 | 
					          icon="bi-compass"
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          All Funkwhale
 | 
					          All Funkwhale
 | 
				
			||||||
        </Button>
 | 
					      </Link>
 | 
				
			||||||
        <Button
 | 
					        <Button
 | 
				
			||||||
          color="secondary"
 | 
					          color="secondary"
 | 
				
			||||||
          variant="ghost"
 | 
					          variant="ghost"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -39,6 +39,9 @@ export default defineConfig({
 | 
				
			||||||
              { text: 'Tabs', link: '/components/ui/tabs' },
 | 
					              { text: 'Tabs', link: '/components/ui/tabs' },
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
 | 
					          { text: 'Link',
 | 
				
			||||||
 | 
					            link: 'components/ui/link'
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
            text: 'Form',
 | 
					            text: 'Form',
 | 
				
			||||||
            items: [
 | 
					            items: [
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,23 @@
 | 
				
			||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import Link from '~/components/ui/Link.vue'
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Link
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Will render an `<a href...>` element.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This component only adds some styles to a `<RouterLink>`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```vue-html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<Link to="/">
 | 
				
			||||||
 | 
					  Home
 | 
				
			||||||
 | 
					</Link>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<Link to="/">
 | 
				
			||||||
 | 
					  Home
 | 
				
			||||||
 | 
					</Link>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Instead of a route, you can set the prop `to` to any web address starting with `http`.
 | 
				
			||||||
		Loading…
	
		Reference in New Issue