aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/App.vue7
-rw-r--r--src/components/CollapsibleToolMenu.vue4
-rw-r--r--src/layouts/base.layout.vue18
-rw-r--r--src/modules/command-palette/command-palette.vue2
-rw-r--r--src/modules/i18n/components/locale-selector.vue28
-rw-r--r--src/pages/Home.page.vue3
-rw-r--r--src/tools/tools.store.ts65
-rw-r--r--src/ui/c-select/c-select.demo.vue15
-rw-r--r--src/ui/c-select/c-select.vue16
9 files changed, 116 insertions, 42 deletions
diff --git a/src/App.vue b/src/App.vue
index fec26bf..8e65335 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -11,6 +11,13 @@ const styleStore = useStyleStore();
const theme = computed(() => (styleStore.isDarkTheme ? darkTheme : null));
const themeOverrides = computed(() => (styleStore.isDarkTheme ? darkThemeOverrides : lightThemeOverrides));
+
+const { locale } = useI18n();
+
+syncRef(
+ locale,
+ useStorage('locale', locale),
+);
</script>
<template>
diff --git a/src/components/CollapsibleToolMenu.vue b/src/components/CollapsibleToolMenu.vue
index 3a025ba..9a73ddd 100644
--- a/src/components/CollapsibleToolMenu.vue
+++ b/src/components/CollapsibleToolMenu.vue
@@ -36,7 +36,7 @@ const menuOptions = computed(() =>
tools: components.map(tool => ({
label: makeLabel(tool),
icon: makeIcon(tool),
- key: tool.name,
+ key: tool.path,
})),
})),
);
@@ -62,7 +62,7 @@ const themeVars = useThemeVars();
<n-menu
class="menu"
- :value="route.name as string"
+ :value="route.path"
:collapsed-width="64"
:collapsed-icon-size="22"
:options="tools"
diff --git a/src/layouts/base.layout.vue b/src/layouts/base.layout.vue
index 2bbb67f..950b01d 100644
--- a/src/layouts/base.layout.vue
+++ b/src/layouts/base.layout.vue
@@ -4,10 +4,10 @@ import { NIcon, useThemeVars } from 'naive-ui';
import { RouterLink } from 'vue-router';
import { Heart, Home2, Menu2 } from '@vicons/tabler';
+import { storeToRefs } from 'pinia';
import HeroGradient from '../assets/hero-gradient.svg?component';
import MenuLayout from '../components/MenuLayout.vue';
import NavbarButtons from '../components/NavbarButtons.vue';
-import { toolsByCategory } from '@/tools';
import { useStyleStore } from '@/stores/style.store';
import { config } from '@/config';
import type { ToolCategory } from '@/tools/tools.types';
@@ -21,12 +21,14 @@ const version = config.app.version;
const commitSha = config.app.lastCommitSha.slice(0, 7);
const { tracker } = useTracker();
+const { t } = useI18n();
const toolStore = useToolStore();
+const { favoriteTools, toolsByCategory } = storeToRefs(toolStore);
const tools = computed<ToolCategory[]>(() => [
- ...(toolStore.favoriteTools.length > 0 ? [{ name: 'Your favorite tools', components: toolStore.favoriteTools }] : []),
- ...toolsByCategory,
+ ...(favoriteTools.value.length > 0 ? [{ name: t('tools.categories.favorite-tools'), components: favoriteTools.value }] : []),
+ ...toolsByCategory.value,
]);
</script>
@@ -47,8 +49,12 @@ const tools = computed<ToolCategory[]>(() => [
</RouterLink>
<div class="sider-content">
- <div v-if="styleStore.isSmallScreen" flex justify-center>
- <NavbarButtons />
+ <div v-if="styleStore.isSmallScreen" flex flex-col items-center>
+ <locale-selector w="90%" />
+
+ <div flex justify-center>
+ <NavbarButtons />
+ </div>
</div>
<CollapsibleToolMenu :tools-by-category="tools" />
@@ -108,6 +114,8 @@ const tools = computed<ToolCategory[]>(() => [
<command-palette />
+ <locale-selector v-if="!styleStore.isSmallScreen" />
+
<div>
<NavbarButtons v-if="!styleStore.isSmallScreen" />
</div>
diff --git a/src/modules/command-palette/command-palette.vue b/src/modules/command-palette/command-palette.vue
index 7531aac..bceef5c 100644
--- a/src/modules/command-palette/command-palette.vue
+++ b/src/modules/command-palette/command-palette.vue
@@ -116,7 +116,7 @@ function activateOption(option: PaletteOption) {
<span flex items-center gap-3 op-40>
<icon-mdi-search />
- Search...
+ {{ $t('search.label') }}
<span hidden flex-1 border border-current border-op-40 rounded border-solid px-5px py-3px sm:inline>
{{ isMac ? 'Cmd' : 'Ctrl' }}&nbsp;+&nbsp;K
diff --git a/src/modules/i18n/components/locale-selector.vue b/src/modules/i18n/components/locale-selector.vue
new file mode 100644
index 0000000..29dc0e5
--- /dev/null
+++ b/src/modules/i18n/components/locale-selector.vue
@@ -0,0 +1,28 @@
+<script setup lang="ts">
+const { availableLocales, locale } = useI18n();
+
+const localesLong: Record<string, string> = {
+ en: 'English',
+ es: 'Español',
+ fr: 'Français',
+ pt: 'Português',
+ ru: 'Русский',
+ zh: '中文',
+};
+
+const localeOptions = computed(() =>
+ availableLocales.map(locale => ({
+ label: localesLong[locale] ?? locale,
+ value: locale,
+ })),
+);
+</script>
+
+<template>
+ <c-select
+ v-model:value="locale"
+ :options="localeOptions"
+ placeholder="Select a language"
+ w-100px
+ />
+</template>
diff --git a/src/pages/Home.page.vue b/src/pages/Home.page.vue
index 7f34081..859418e 100644
--- a/src/pages/Home.page.vue
+++ b/src/pages/Home.page.vue
@@ -31,7 +31,8 @@ const { t } = useI18n();
rel="noopener"
target="_blank"
:aria-label="$t('home.follow.twitterAccount')"
- >Twitter</a>{{ $t('home.follow.thankYou') }}
+ >Twitter</a>.
+ {{ $t('home.follow.thankYou') }}
<n-icon :component="Heart" />
</ColoredCard>
</n-gi>
diff --git a/src/tools/tools.store.ts b/src/tools/tools.store.ts
index 769b4d8..d952b7c 100644
--- a/src/tools/tools.store.ts
+++ b/src/tools/tools.store.ts
@@ -1,44 +1,57 @@
import { type MaybeRef, get, useStorage } from '@vueuse/core';
import { defineStore } from 'pinia';
import type { Ref } from 'vue';
-import type { Tool, ToolWithCategory } from './tools.types';
+import _ from 'lodash';
+import type { Tool, ToolCategory, ToolWithCategory } from './tools.types';
import { toolsWithCategory } from './index';
-export const useToolStore = defineStore('tools', {
- state: () => ({
- favoriteToolsName: useStorage('favoriteToolsName', []) as Ref<string[]>,
- }),
- getters: {
- favoriteTools(state) {
- return state.favoriteToolsName
- .map(favoriteName => toolsWithCategory.find(({ name }) => name === favoriteName))
- .filter(Boolean) as ToolWithCategory[]; // cast because .filter(Boolean) does not remove undefined from type
- },
+export const useToolStore = defineStore('tools', () => {
+ const favoriteToolsName = useStorage('favoriteToolsName', []) as Ref<string[]>;
+ const { t } = useI18n();
- notFavoriteTools(state): ToolWithCategory[] {
- return toolsWithCategory.filter(tool => !state.favoriteToolsName.includes(tool.name));
- },
+ const tools = computed<ToolWithCategory[]>(() => toolsWithCategory.map((tool) => {
+ const toolI18nKey = tool.path.replace(/\//g, '');
- tools(): ToolWithCategory[] {
- return toolsWithCategory;
- },
+ return ({
+ ...tool,
+ name: t(`tools.${toolI18nKey}.title`, tool.name),
+ description: t(`tools.${toolI18nKey}.description`, tool.description),
+ category: t(`tools.categories.${tool.category.toLowerCase()}`, tool.category),
+ });
+ }));
- newTools(): ToolWithCategory[] {
- return this.tools.filter(({ isNew }) => isNew);
- },
- },
+ const toolsByCategory = computed<ToolCategory[]>(() => {
+ return _.chain(tools.value)
+ .groupBy('category')
+ .map((components, name) => ({
+ name,
+ components,
+ }))
+ .value();
+ });
+
+ const favoriteTools = computed(() => {
+ return favoriteToolsName.value
+ .map(favoriteName => tools.value.find(({ name }) => name === favoriteName))
+ .filter(Boolean) as ToolWithCategory[]; // cast because .filter(Boolean) does not remove undefined from type
+ });
+
+ return {
+ tools,
+ favoriteTools,
+ toolsByCategory,
+ newTools: computed(() => tools.value.filter(({ isNew }) => isNew)),
- actions: {
addToolToFavorites({ tool }: { tool: MaybeRef<Tool> }) {
- this.favoriteToolsName.push(get(tool).name);
+ favoriteToolsName.value.push(get(tool).name);
},
removeToolFromFavorites({ tool }: { tool: MaybeRef<Tool> }) {
- this.favoriteToolsName = this.favoriteToolsName.filter(name => get(tool).name !== name);
+ favoriteToolsName.value = favoriteToolsName.value.filter(name => get(tool).name !== name);
},
isToolFavorite({ tool }: { tool: MaybeRef<Tool> }) {
- return this.favoriteToolsName.includes(get(tool).name);
+ return favoriteToolsName.value.includes(get(tool).name);
},
- },
+ };
});
diff --git a/src/ui/c-select/c-select.demo.vue b/src/ui/c-select/c-select.demo.vue
index ae553bb..f656c01 100644
--- a/src/ui/c-select/c-select.demo.vue
+++ b/src/ui/c-select/c-select.demo.vue
@@ -33,4 +33,19 @@ const value = ref('');
<c-select label="Label" label-position="left" label-align="left" mb-2 label-width="200px" />
<c-select label="Label" label-position="left" label-align="center" mb-2 label-width="200px" />
<c-select label="Label" label-position="left" label-align="right" mb-2 label-width="200px" />
+
+ <h2>Custom displayed value</h2>
+ <c-select v-model:value="value" :options="optionsA" mb-2>
+ <template #displayed-value>
+ <span class="font-bold lh-normal">Hello</span>
+ </template>
+ </c-select>
+
+ <c-select v-model:value="value" :options="optionsA">
+ <template #displayed-value>
+ <span lh-normal>
+ <icon-mdi-translate />
+ </span>
+ </template>
+ </c-select>
</template>
diff --git a/src/ui/c-select/c-select.vue b/src/ui/c-select/c-select.vue
index fb34038..7b3607c 100644
--- a/src/ui/c-select/c-select.vue
+++ b/src/ui/c-select/c-select.vue
@@ -150,13 +150,15 @@ function onSearchInput() {
@keydown="handleKeydown"
>
<div flex-1 truncate>
- <input v-if="searchable && isOpen" ref="searchInputRef" v-model="searchQuery" type="text" placeholder="Search..." class="search-input" w-full lh-normal color-current @input="onSearchInput">
- <span v-else-if="selectedOption" lh-normal>
- {{ selectedOption.label }}
- </span>
- <span v-else class="placeholder" lh-normal>
- {{ placeholder ?? 'Select an option' }}
- </span>
+ <slot name="displayed-value">
+ <input v-if="searchable && isOpen" ref="searchInputRef" v-model="searchQuery" type="text" placeholder="Search..." class="search-input" w-full lh-normal color-current @input="onSearchInput">
+ <span v-else-if="selectedOption" lh-normal>
+ {{ selectedOption.label }}
+ </span>
+ <span v-else class="placeholder" lh-normal>
+ {{ placeholder ?? 'Select an option' }}
+ </span>
+ </slot>
</div>
<icon-mdi-chevron-down class="chevron" />