refactor(home): improved landing page ui

This commit is contained in:
Corentin Thomasset 2022-12-17 14:32:57 +01:00
parent 274ff02b54
commit 0bd8fabb85
No known key found for this signature in database
GPG Key ID: DBD997E935996158
10 changed files with 353 additions and 114 deletions

View File

@ -7,7 +7,7 @@ import { layouts } from './layouts';
import { useStyleStore } from './stores/style.store'; import { useStyleStore } from './stores/style.store';
const route = useRoute(); const route = useRoute();
const layout = computed(() => route?.meta?.layout ?? layouts.base); const layout = computed(() => route?.meta?.layout ?? layouts.navbar);
const styleStore = useStyleStore(); const styleStore = useStyleStore();
const theme = computed(() => (styleStore.isDarkTheme ? darkTheme : null)); const theme = computed(() => (styleStore.isDarkTheme ? darkTheme : null));

View File

@ -1,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 275"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 275" preserveAspectRatio="none">
<defs> <defs>
<linearGradient id="small-hero-gradient-1" x1="13.74" y1="183.7" x2="303.96" y2="45.59" gradientUnits="userSpaceOnUse"> <linearGradient id="small-hero-gradient-1" x1="13.74" y1="183.7" x2="303.96" y2="45.59" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#25636c"/> <stop offset="0" stop-color="#25636c"/>

Before

Width:  |  Height:  |  Size: 894 B

After

Width:  |  Height:  |  Size: 922 B

View File

@ -39,13 +39,6 @@ const siderPosition = computed(() => (isSmallScreen.value ? 'absolute' : 'static
cursor: pointer; cursor: pointer;
} }
.content {
// background-color: #f1f5f9;
::v-deep(.n-layout-scroll-container) {
padding: 26px;
}
}
.n-layout { .n-layout {
height: 100vh; height: 100vh;
} }

View File

@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { NIcon, useThemeVars, type MenuGroupOption } from 'naive-ui'; import { NIcon, useThemeVars, type MenuGroupOption } from 'naive-ui';
import { h } from 'vue'; import { computed, h } from 'vue';
import { RouterLink, useRoute } from 'vue-router'; import { RouterLink, useRoute } from 'vue-router';
import { Heart, Menu2, Home2 } from '@vicons/tabler'; import { Heart, Menu2, Home2 } from '@vicons/tabler';
import { toolsByCategory } from '@/tools'; import { toolsByCategory } from '@/tools';
@ -8,6 +8,7 @@ import { useStyleStore } from '@/stores/style.store';
import { config } from '@/config'; import { config } from '@/config';
import MenuIconItem from '@/components/MenuIconItem.vue'; import MenuIconItem from '@/components/MenuIconItem.vue';
import type { Tool } from '@/tools/tools.types'; import type { Tool } from '@/tools/tools.types';
import { useToolStore } from '@/tools/tools.store';
import SearchBar from '../components/SearchBar.vue'; import SearchBar from '../components/SearchBar.vue';
import HeroGradient from '../assets/hero-gradient.svg?component'; import HeroGradient from '../assets/hero-gradient.svg?component';
import MenuLayout from '../components/MenuLayout.vue'; import MenuLayout from '../components/MenuLayout.vue';
@ -22,16 +23,25 @@ const commitSha = config.app.lastCommitSha.slice(0, 7);
const makeLabel = (tool: Tool) => () => h(RouterLink, { to: tool.path }, { default: () => tool.name }); const makeLabel = (tool: Tool) => () => h(RouterLink, { to: tool.path }, { default: () => tool.name });
const makeIcon = (tool: Tool) => () => h(MenuIconItem, { tool }); const makeIcon = (tool: Tool) => () => h(MenuIconItem, { tool });
const menuOptions: MenuGroupOption[] = toolsByCategory.map((category) => ({ const toolStore = useToolStore();
label: category.name,
key: category.name, const menuOptions = computed<MenuGroupOption[]>(() =>
type: 'group', [
children: category.components.map((tool) => ({ ...(toolStore.favoriteTools.length > 0
label: makeLabel(tool), ? [{ name: 'Your favorite tools', components: toolStore.favoriteTools }]
icon: makeIcon(tool), : []),
key: tool.name, ...toolsByCategory,
].map((category) => ({
label: category.name,
key: category.name,
type: 'group',
children: category.components.map((tool) => ({
label: makeLabel(tool),
icon: makeIcon(tool),
key: tool.name,
})),
})), })),
})); );
</script> </script>
<template> <template>
@ -102,60 +112,6 @@ const menuOptions: MenuGroupOption[] = toolsByCategory.map((category) => ({
</template> </template>
<template #content> <template #content>
<div class="navigation">
<n-button
:size="styleStore.isSmallScreen ? 'medium' : 'large'"
circle
quaternary
aria-label="Toggle menu"
@click="styleStore.isMenuCollapsed = !styleStore.isMenuCollapsed"
>
<n-icon size="25" :component="Menu2" />
</n-button>
<router-link to="/" #="{ navigate, href }" custom>
<n-tooltip trigger="hover">
<template #trigger>
<n-button
tag="a"
:href="href"
:size="styleStore.isSmallScreen ? 'medium' : 'large'"
circle
quaternary
aria-label="Home"
@click="navigate"
>
<n-icon size="25" :component="Home2" />
</n-button>
</template>
Home
</n-tooltip>
</router-link>
<search-bar />
<navbar-buttons v-if="!styleStore.isSmallScreen" />
<n-tooltip trigger="hover">
<template #trigger>
<n-button
round
type="primary"
tag="a"
href="https://github.com/sponsors/CorentinTh"
rel="noopener"
target="_blank"
class="support-button"
:bordered="false"
>
Buy me a coffee
<n-icon v-if="!styleStore.isSmallScreen" :component="Heart" style="margin-left: 8px" size="20px" />
</n-button>
</template>
Support IT Tools development !
</n-tooltip>
</div>
<slot /> <slot />
</template> </template>
</menu-layout> </menu-layout>

View File

@ -1,7 +1,9 @@
import BaseLayout from './base.layout.vue'; import BaseLayout from './base.layout.vue';
import NavbarLayout from './navbar.layout.vue';
import ToolLayout from './tool.layout.vue'; import ToolLayout from './tool.layout.vue';
export const layouts = { export const layouts = {
base: BaseLayout, base: BaseLayout,
toolLayout: ToolLayout, toolLayout: ToolLayout,
navbar: NavbarLayout,
}; };

View File

@ -0,0 +1,174 @@
<script lang="ts" setup>
import { NIcon, useThemeVars } from 'naive-ui';
import { RouterLink } from 'vue-router';
import { Heart, Menu2, Home2 } from '@vicons/tabler';
import { useStyleStore } from '@/stores/style.store';
import SearchBar from '../components/SearchBar.vue';
import BaseLayout from './base.layout.vue';
import NavbarButtons from '../components/NavbarButtons.vue';
const themeVars = useThemeVars();
const styleStore = useStyleStore();
</script>
<template>
<base-layout class="base-layout">
<div class="navigation">
<n-button
:size="styleStore.isSmallScreen ? 'medium' : 'large'"
circle
quaternary
aria-label="Toggle menu"
@click="styleStore.isMenuCollapsed = !styleStore.isMenuCollapsed"
>
<n-icon size="25" :component="Menu2" />
</n-button>
<router-link to="/" #="{ navigate, href }" custom>
<n-tooltip trigger="hover">
<template #trigger>
<n-button
tag="a"
:href="href"
:size="styleStore.isSmallScreen ? 'medium' : 'large'"
circle
quaternary
aria-label="Home"
@click="navigate"
>
<n-icon size="25" :component="Home2" />
</n-button>
</template>
Home
</n-tooltip>
</router-link>
<search-bar />
<navbar-buttons v-if="!styleStore.isSmallScreen" />
<n-tooltip trigger="hover">
<template #trigger>
<n-button
round
type="primary"
tag="a"
href="https://github.com/sponsors/CorentinTh"
rel="noopener"
target="_blank"
class="support-button"
:bordered="false"
>
Buy me a coffee
<n-icon v-if="!styleStore.isSmallScreen" :component="Heart" style="margin-left: 8px" size="20px" />
</n-button>
</template>
Support IT Tools development !
</n-tooltip>
</div>
<slot />
</base-layout>
</template>
<style lang="less" scoped>
// ::v-deep(.n-layout-scroll-container) {
// @percent: 4%;
// @position: 25px;
// @size: 50px;
// @color: #eeeeee25;
// background-image: radial-gradient(@color @percent, transparent @percent),
// radial-gradient(@color @percent, transparent @percent);
// background-position: 0 0, @position @position;
// background-size: @size @size;
// }
::v-deep(.content .n-layout-scroll-container) {
padding: 26px;
}
.support-button {
background: rgb(37, 99, 108);
background: linear-gradient(48deg, rgba(37, 99, 108, 1) 0%, rgba(59, 149, 111, 1) 60%, rgba(20, 160, 88, 1) 100%);
color: #fff;
transition: all ease 0.2s;
&:hover {
color: #fff;
padding-left: 30px;
padding-right: 30px;
}
}
.footer {
text-align: center;
color: #838587;
margin-top: 20px;
padding: 20px 0;
}
.sider-content {
padding-top: 160px;
padding-bottom: 200px;
}
.hero-wrapper {
position: absolute;
display: block;
left: 0;
width: 100%;
z-index: 10;
overflow: hidden;
.gradient {
margin-top: -65px;
}
.text-wrapper {
position: absolute;
left: 0;
width: 100%;
text-align: center;
top: 16px;
color: #fff;
.title {
font-size: 25px;
font-weight: 600;
}
.divider {
width: 50px;
height: 2px;
border-radius: 4px;
background-color: v-bind('themeVars.primaryColor');
margin: 0 auto 5px;
}
.subtitle {
font-size: 16px;
}
}
}
// ::v-deep(.n-menu-item-content-header) {
// overflow: visible !important;
// // overflow-x: hidden !important;
// }
.navigation {
display: flex;
align-items: center;
justify-content: center;
flex-direction: row;
& > *:not(:last-child) {
margin-right: 5px;
}
.search-bar {
// width: 100%;
flex-grow: 1;
}
}
</style>

View File

@ -4,7 +4,9 @@ import { useHead } from '@vueuse/head';
import type { HeadObject } from '@vueuse/head'; import type { HeadObject } from '@vueuse/head';
import { computed } from 'vue'; import { computed } from 'vue';
import { useThemeVars } from 'naive-ui'; import { useThemeVars } from 'naive-ui';
import BaseLayout from './base.layout.vue'; import FavoriteButton from '@/components/FavoriteButton.vue';
import type { Tool } from '@/tools/tools.types';
import NavbarLayout from './navbar.layout.vue';
const route = useRoute(); const route = useRoute();
const theme = useThemeVars(); const theme = useThemeVars();
@ -14,11 +16,11 @@ const head = computed<HeadObject>(() => ({
meta: [ meta: [
{ {
name: 'description', name: 'description',
content: route.meta.description, content: route.meta?.description as string,
}, },
{ {
name: 'keywords', name: 'keywords',
content: route.meta.keywords, content: ((route.meta.keywords ?? []) as string[]).join(','),
}, },
], ],
})); }));
@ -26,25 +28,21 @@ useHead(head);
</script> </script>
<template> <template>
<base-layout> <navbar-layout>
<div class="tool-layout"> <div class="tool-layout">
<div class="tool-header"> <div class="tool-header">
<n-h1> <n-space align="center" justify="space-between" :wrap="false">
{{ route.meta.name }} <n-h1>
{{ route.meta.name }}
</n-h1>
<n-tag <div>
v-if="route.meta.isNew" <favorite-button :tool="{name: route.meta.name} as Tool" />
round </div>
type="success" </n-space>
:bordered="false"
:color="{ color: theme.primaryColor, textColor: theme.tagColor }"
>
New tool
</n-tag>
<!-- <span class="new-tool-badge">New !</span> -->
</n-h1>
<div class="separator" /> <div class="separator" />
<div class="description"> <div class="description">
{{ route.meta.description }} {{ route.meta.description }}
</div> </div>
@ -54,7 +52,7 @@ useHead(head);
<div class="tool-content"> <div class="tool-content">
<slot /> <slot />
</div> </div>
</base-layout> </navbar-layout>
</template> </template>
<style lang="less" scoped> <style lang="less" scoped>
@ -92,6 +90,7 @@ useHead(head);
width: 200px; width: 200px;
height: 2px; height: 2px;
background: rgb(161, 161, 161); background: rgb(161, 161, 161);
opacity: 0.2;
margin: 10px 0; margin: 10px 0;
} }

View File

@ -4,6 +4,7 @@ import { Heart } from '@vicons/tabler';
import { useHead } from '@vueuse/head'; import { useHead } from '@vueuse/head';
import ColoredCard from '../components/ColoredCard.vue'; import ColoredCard from '../components/ColoredCard.vue';
import ToolCard from '../components/ToolCard.vue'; import ToolCard from '../components/ToolCard.vue';
import Hero from './home/components/hero.vue';
const toolStore = useToolStore(); const toolStore = useToolStore();
@ -12,7 +13,7 @@ useHead({ title: 'IT Tools - Handy online tools for developers' });
<template> <template>
<div class="home-page"> <div class="home-page">
<n-grid x-gap="12" y-gap="12" cols="1 400:2 800:3 1200:4 2000:8"> <!-- <n-grid x-gap="12" y-gap="12" cols="1 400:2 800:3 1200:4 2000:8">
<n-gi> <n-gi>
<colored-card title="You like it-tools?" :icon="Heart"> <colored-card title="You like it-tools?" :icon="Heart">
Give us a star on Give us a star on
@ -34,42 +35,45 @@ useHead({ title: 'IT Tools - Handy online tools for developers' });
<n-icon :component="Heart" /> <n-icon :component="Heart" />
</colored-card> </colored-card>
</n-gi> </n-gi>
</n-grid> </n-grid> -->
<transition name="height"> <hero />
<div v-if="toolStore.favoriteTools.length > 0"> <div class="grid-wrapper">
<n-h3>Your favorite tools</n-h3> <transition name="height">
<div v-if="toolStore.favoriteTools.length > 0">
<n-h3>Your favorite tools</n-h3>
<n-grid x-gap="12" y-gap="12" cols="1 400:2 800:3 1200:4 2000:8">
<n-gi v-for="tool in toolStore.favoriteTools" :key="tool.name">
<tool-card :tool="tool" />
</n-gi>
</n-grid>
</div>
</transition>
<div v-if="toolStore.newTools.length > 0">
<n-h3>Newest tools</n-h3>
<n-grid x-gap="12" y-gap="12" cols="1 400:2 800:3 1200:4 2000:8"> <n-grid x-gap="12" y-gap="12" cols="1 400:2 800:3 1200:4 2000:8">
<n-gi v-for="tool in toolStore.favoriteTools" :key="tool.name"> <n-gi v-for="tool in toolStore.newTools" :key="tool.name">
<tool-card :tool="tool" /> <tool-card :tool="tool" />
</n-gi> </n-gi>
</n-grid> </n-grid>
</div> </div>
</transition>
<div v-if="toolStore.newTools.length > 0"> <n-h3>All the tools</n-h3>
<n-h3>Newest tools</n-h3>
<n-grid x-gap="12" y-gap="12" cols="1 400:2 800:3 1200:4 2000:8"> <n-grid x-gap="12" y-gap="12" cols="1 400:2 800:3 1200:4 2000:8">
<n-gi v-for="tool in toolStore.newTools" :key="tool.name"> <n-gi v-for="tool in toolStore.tools" :key="tool.name">
<tool-card :tool="tool" /> <transition>
<tool-card :tool="tool" />
</transition>
</n-gi> </n-gi>
</n-grid> </n-grid>
</div> </div>
<n-h3>All the tools</n-h3>
<n-grid x-gap="12" y-gap="12" cols="1 400:2 800:3 1200:4 2000:8">
<n-gi v-for="tool in toolStore.tools" :key="tool.name">
<transition>
<tool-card :tool="tool" />
</transition>
</n-gi>
</n-grid>
</div> </div>
</template> </template>
<style scoped lang="less"> <style scoped lang="less">
.home-page { .grid-wrapper {
padding-top: 50px; padding: 26px;
} }
::v-deep(.n-grid) { ::v-deep(.n-grid) {

View File

@ -0,0 +1,110 @@
<template>
<div class="hero">
<!-- <img :src="HeroGradientUrl" alt="Hero background image" class="hero-background" /> -->
<div class="background-wrapper" :style="{ backgroundImage: `url(${HeroGradientUrl})` }">
<div class="navigation">
<n-button
:size="styleStore.isSmallScreen ? 'medium' : 'large'"
circle
quaternary
aria-label="Toggle menu"
color="#fff"
@click="styleStore.isMenuCollapsed = !styleStore.isMenuCollapsed"
>
<n-icon size="25" :component="Menu2" />
</n-button>
<div class="spacer"></div>
<navbar-buttons v-if="!styleStore.isSmallScreen" />
<n-tooltip trigger="hover">
<template #trigger>
<n-button
round
type="primary"
tag="a"
href="https://github.com/sponsors/CorentinTh"
rel="noopener"
target="_blank"
ghost
color="#fff"
>
Buy me a coffee
<n-icon v-if="!styleStore.isSmallScreen" :component="Heart" style="margin-left: 8px" size="20px" />
</n-button>
</template>
Support IT Tools development !
</n-tooltip>
</div>
<n-space justify="center" class="content" vertical>
<n-h1> Hello, world! </n-h1>
<div class="subtitle">
Welcome to IT-Tools! The collection of handy online tool for devs. Find everything you need to work in IT!
</div>
<search-bar />
</n-space>
</div>
</div>
</template>
<script setup lang="ts">
import HeroGradientUrl from '@/assets/hero-gradient.svg?url';
import NavbarButtons from '@/components/NavbarButtons.vue';
import SearchBar from '@/components/SearchBar.vue';
import { useStyleStore } from '@/stores/style.store';
import { Heart, Menu2 } from '@vicons/tabler';
const styleStore = useStyleStore();
</script>
<style scoped lang="less">
.hero {
position: relative;
color: #fff !important;
.n-h1 {
margin-bottom: 0;
line-height: 1;
color: #fff !important;
}
.subtitle {
opacity: 0.8;
margin-bottom: 20px;
}
.background-wrapper {
// background: rgb(37, 99, 108);
// background: linear-gradient(48deg, rgba(37, 99, 108, 1) 0%, rgba(59, 149, 111, 1) 60%, rgba(20, 160, 88, 1) 100%);
background-size: 100% 150%;
background-position: bottom;
}
.content {
padding: 50px 0 300px;
max-width: 900px;
margin: 0 auto;
}
.navigation {
padding: 26px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: row;
& > *:not(:last-child) {
margin-right: 5px;
}
.spacer {
// width: 100%;
flex-grow: 1;
}
}
}
</style>

View File

@ -24,6 +24,7 @@ const router = createRouter({
path: '/', path: '/',
name: 'home', name: 'home',
component: HomePage, component: HomePage,
meta: { layout: layouts.base },
}, },
{ {
path: '/about', path: '/about',