fix(spa): visual fixes for SPA (#106)

* fix(spa): prevent more-link button from wrapping in block header

Long block titles squeezed the "Смотреть всё" link in BaseBlock,
causing its text to wrap onto two lines. Allow the title column to
shrink and keep the link in a single line.

* fix(spa): enable typography styles for product description

Add @tailwindcss/typography plugin so the `prose` classes used in the
product description actually apply: paragraph spacing, headings, hr,
links and tables. Also make wide tables inside `.prose` horizontally
scrollable instead of overflowing the viewport.

* fix(spa): open description links via Telegram WebApp API

Links inside the v-html product description used to navigate the
Mini App's WebView away from the app on Telegram mobile. Intercept
clicks on anchors in the description and route them through
Telegram.WebApp.openTelegramLink for t.me/tg:// targets and
Telegram.WebApp.openLink for everything else, falling back to
window.open when running outside Telegram.

* fix(spa): refine categories list layout

Tighten category item typography (text-base, leading-snug) and slightly
enlarge the icon for a better balance with long titles. Drop the
duplicate "Назад к ..." link and replace the parent-category CTA with
a daisyUI soft block button "Показать все товары категории" so the
primary action is visually distinct from the list.
This commit is contained in:
2026-05-25 10:13:42 +03:00
committed by GitHub
parent d43564481d
commit c24e30f14c
7 changed files with 99 additions and 27 deletions

View File

@@ -23,6 +23,7 @@
"vue-router": "^4.6.3" "vue-router": "^4.6.3"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/typography": "^0.5.19",
"@testing-library/jest-dom": "^6.9.1", "@testing-library/jest-dom": "^6.9.1",
"@testing-library/vue": "^8.1.0", "@testing-library/vue": "^8.1.0",
"@vitejs/plugin-vue": "^6.0.1", "@vitejs/plugin-vue": "^6.0.1",
@@ -1588,6 +1589,19 @@
"node": ">= 20" "node": ">= 20"
} }
}, },
"node_modules/@tailwindcss/typography": {
"version": "0.5.19",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz",
"integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==",
"dev": true,
"license": "MIT",
"dependencies": {
"postcss-selector-parser": "6.0.10"
},
"peerDependencies": {
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
}
},
"node_modules/@tailwindcss/vite": { "node_modules/@tailwindcss/vite": {
"version": "4.2.1", "version": "4.2.1",
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz",
@@ -2520,6 +2534,19 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true,
"license": "MIT",
"bin": {
"cssesc": "bin/cssesc"
},
"engines": {
"node": ">=4"
}
},
"node_modules/cssstyle": { "node_modules/cssstyle": {
"version": "5.3.7", "version": "5.3.7",
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.7.tgz", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.7.tgz",
@@ -4276,6 +4303,20 @@
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/postcss-selector-parser": {
"version": "6.0.10",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
"dev": true,
"license": "MIT",
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
},
"engines": {
"node": ">=4"
}
},
"node_modules/postcss-value-parser": { "node_modules/postcss-value-parser": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
@@ -5051,6 +5092,13 @@
"browserslist": ">= 4.21.0" "browserslist": ">= 4.21.0"
} }
}, },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true,
"license": "MIT"
},
"node_modules/vite": { "node_modules/vite": {
"version": "7.3.3", "version": "7.3.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.3.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.3.tgz",

View File

@@ -28,6 +28,7 @@
"vue-router": "^4.6.3" "vue-router": "^4.6.3"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/typography": "^0.5.19",
"@testing-library/jest-dom": "^6.9.1", "@testing-library/jest-dom": "^6.9.1",
"@testing-library/vue": "^8.1.0", "@testing-library/vue": "^8.1.0",
"@vitejs/plugin-vue": "^6.0.1", "@vitejs/plugin-vue": "^6.0.1",

View File

@@ -2,16 +2,16 @@
<a <a
href="#" href="#"
:key="category.id" :key="category.id"
class="flex items-center" class="flex items-center gap-3"
@click.prevent="$emit('onSelect', category)" @click.prevent="$emit('onSelect', category)"
> >
<div class="avatar"> <div class="avatar shrink-0">
<div class="w-8 h-8 rounded"> <div class="w-10 h-10 rounded">
<img :src="category.image" :alt="category.name" loading="lazy" width="30" height="30" class="bg-base-400"/> <img :src="category.image" :alt="category.name" loading="lazy" width="40" height="40" class="bg-base-400"/>
</div> </div>
</div> </div>
<h3 class="ml-4 text-lg line-clamp-2">{{ category.name }}</h3> <h3 class="text-base font-medium leading-snug line-clamp-2">{{ category.name }}</h3>
</a> </a>
</template> </template>

View File

@@ -1,13 +1,13 @@
<template> <template>
<section class="px-4"> <section class="px-4">
<header class="flex justify-between items-end mb-4"> <header class="flex justify-between items-end mb-4 gap-2">
<div> <div class="min-w-0">
<div v-if="title" class="font-bold uppercase">{{ title }}</div> <div v-if="title" class="font-bold uppercase">{{ title }}</div>
<div v-if="description" class="text-sm text-base-content/50">{{ description }}</div> <div v-if="description" class="text-sm text-base-content/50">{{ description }}</div>
</div> </div>
<div v-if="moreLink"> <div v-if="moreLink" class="shrink-0">
<RouterLink :to="moreLink" class="btn btn-soft btn-xs" @click="haptic.selectionChanged"> <RouterLink :to="moreLink" class="btn btn-soft btn-xs whitespace-nowrap" @click="haptic.selectionChanged">
{{ moreText || 'Смотреть всё' }} {{ moreText || 'Смотреть всё' }}
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" /> <path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />

View File

@@ -2,10 +2,19 @@
@custom-variant dark (&:where(.dark, .dark *)); @custom-variant dark (&:where(.dark, .dark *));
@plugin "@tailwindcss/typography";
@plugin "daisyui" { @plugin "daisyui" {
themes: all; themes: all;
} }
.prose :where(table):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
display: block;
width: 100%;
max-width: 100%;
overflow-x: auto;
}
/** /**
--color-base-100 - DaisyUI background --color-base-100 - DaisyUI background
*/ */

View File

@@ -9,31 +9,22 @@
</div> </div>
<template v-else> <template v-else>
<button v-if="parentId && parentCategory" class="py-1 px-4 flex items-center mb-3 cursor-pointer" @click="goBack">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6 min-w-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18" />
</svg>
<span class="ml-2 line-clamp-2">Назад к "{{ parentCategory.name }}"</span>
</button>
<button <button
v-if="parentCategory && parentCategory.children?.length && settings.show_category_products_button" v-if="parentCategory && parentCategory.children?.length && settings.show_category_products_button"
class="py-2 px-4 flex items-center mb-3 cursor-pointer border-b w-full pb-2 border-base-200" class="btn btn-soft btn-block btn-sm mb-3 justify-start"
@click.prevent="showProductsInParentCategory" @click.prevent="showProductsInParentCategory"
> >
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5 shrink-0">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6A2.25 2.25 0 0 1 6 3.75h2.25A2.25 2.25 0 0 1 10.5 6v2.25a2.25 2.25 0 0 1-2.25 2.25H6a2.25 2.25 0 0 1-2.25-2.25V6ZM3.75 15.75A2.25 2.25 0 0 1 6 13.5h2.25a2.25 2.25 0 0 1 2.25 2.25V18a2.25 2.25 0 0 1-2.25 2.25H6A2.25 2.25 0 0 1 3.75 18v-2.25ZM13.5 6a2.25 2.25 0 0 1 2.25-2.25H18A2.25 2.25 0 0 1 20.25 6v2.25A2.25 2.25 0 0 1 18 10.5h-2.25a2.25 2.25 0 0 1-2.25-2.25V6ZM13.5 15.75a2.25 2.25 0 0 1 2.25-2.25H18a2.25 2.25 0 0 1 2.25 2.25V18A2.25 2.25 0 0 1 18 20.25h-2.25A2.25 2.25 0 0 1 13.5 18v-2.25Z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6A2.25 2.25 0 0 1 6 3.75h2.25A2.25 2.25 0 0 1 10.5 6v2.25a2.25 2.25 0 0 1-2.25 2.25H6a2.25 2.25 0 0 1-2.25-2.25V6ZM3.75 15.75A2.25 2.25 0 0 1 6 13.5h2.25a2.25 2.25 0 0 1 2.25 2.25V18a2.25 2.25 0 0 1-2.25 2.25H6A2.25 2.25 0 0 1 3.75 18v-2.25ZM13.5 6a2.25 2.25 0 0 1 2.25-2.25H18A2.25 2.25 0 0 1 20.25 6v2.25A2.25 2.25 0 0 1 18 10.5h-2.25a2.25 2.25 0 0 1-2.25-2.25V6ZM13.5 15.75a2.25 2.25 0 0 1 2.25-2.25H18a2.25 2.25 0 0 1 2.25 2.25V18A2.25 2.25 0 0 1 18 20.25h-2.25A2.25 2.25 0 0 1 13.5 18v-2.25Z" />
</svg> </svg>
<span>Показать все товары категории</span>
<span class="ml-2">Показать товары из "{{ parentCategory.name }}"</span>
</button> </button>
<TransitionGroup <TransitionGroup
name="stagger" name="stagger"
tag="ul" tag="ul"
appear appear
class="space-y-4" class="space-y-3"
> >
<li <li
v-for="(category, i) in categories" v-for="(category, i) in categories"
@@ -99,10 +90,6 @@ function onSelect(category) {
} }
} }
function goBack() {
router.back();
}
function showProductsInParentCategory() { function showProductsInParentCategory() {
if (parentId.value != null) { if (parentId.value != null) {
router.push({name: "product.categories.show", params: {category_id: parentId.value}}); router.push({name: "product.categories.show", params: {category_id: parentId.value}});

View File

@@ -87,7 +87,11 @@
</svg> </svg>
Описание Описание
</h3> </h3>
<div class="prose prose-sm max-w-none text-base-content/80" v-html="product.description"></div> <div
class="prose prose-sm max-w-none text-base-content/80"
v-html="product.description"
@click="handleDescriptionClick"
></div>
</div> </div>
<!-- Attributes --> <!-- Attributes -->
@@ -306,6 +310,29 @@ async function onCartBtnClick() {
} }
} }
function handleDescriptionClick(event) {
const link = event.target.closest('a');
if (!link) return;
const href = link.getAttribute('href');
if (!href || href.startsWith('#') || href.startsWith('javascript:')) return;
event.preventDefault();
const telegramWebApp = window.Telegram?.WebApp;
if (!telegramWebApp) {
window.open(href, '_blank', 'noopener,noreferrer');
return;
}
if (/^https?:\/\/(t|telegram)\.me\//i.test(href) || href.startsWith('tg://')) {
telegramWebApp.openTelegramLink(href);
return;
}
telegramWebApp.openLink(href, {try_instant_view: false});
}
function openProductInMarketplace() { function openProductInMarketplace() {
if (!product.value.share) { if (!product.value.share) {
return; return;