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 Input from '~/components/ui/Input.vue'
 | 
			
		||||
import Pill from '~/components/ui/Pill.vue'
 | 
			
		||||
import Link from '~/components/ui/Link.vue'
 | 
			
		||||
 | 
			
		||||
const searchQuery = ref('')
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -99,13 +100,13 @@ const uploads = useUploadsStore()
 | 
			
		|||
 | 
			
		||||
      <h3>Explore</h3>
 | 
			
		||||
      <nav class="button-list">
 | 
			
		||||
        <Button
 | 
			
		||||
        <Link to="/content"
 | 
			
		||||
          color="secondary"
 | 
			
		||||
          variant="ghost"
 | 
			
		||||
          icon="bi-compass"
 | 
			
		||||
        >
 | 
			
		||||
          All Funkwhale
 | 
			
		||||
        </Button>
 | 
			
		||||
      </Link>
 | 
			
		||||
        <Button
 | 
			
		||||
          color="secondary"
 | 
			
		||||
          variant="ghost"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,6 +39,9 @@ export default defineConfig({
 | 
			
		|||
              { text: 'Tabs', link: '/components/ui/tabs' },
 | 
			
		||||
            ],
 | 
			
		||||
          },
 | 
			
		||||
          { text: 'Link',
 | 
			
		||||
            link: 'components/ui/link'
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            text: 'Form',
 | 
			
		||||
            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