@brumaombra/ui-vintage 0.9.7 → 0.9.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COMPONENTS.md +4 -1
- package/package.json +1 -1
- package/src/components/blog/BlogContentRenderer.vue +0 -11
- package/src/components/blog/BlogFAQSection.vue +1 -1
- package/src/components/blog/BlogIndexPage.vue +1 -1
- package/src/components/blog/BlogInfoSection.vue +4 -4
- package/src/components/blog/BlogPostHeader.vue +4 -5
- package/src/components/blog/BlogPostPage.vue +2 -2
- package/src/components/blog/BlogTagsSection.vue +1 -1
- package/src/components/blog/CategoryCard.vue +1 -1
- package/src/components/blog/PostCard.vue +1 -1
- package/src/components/ui/badge/Badge.vue +16 -3
- package/src/components/ui/select/SelectItemContent.vue +5 -3
package/COMPONENTS.md
CHANGED
|
@@ -964,7 +964,7 @@ import { RadioGroup, RadioGroupItem, RadioGroupCard } from '@brumaombra/ui-vinta
|
|
|
964
964
|
|
|
965
965
|
### `@brumaombra/ui-vintage/select`
|
|
966
966
|
|
|
967
|
-
Styled Reka select. `SelectValueContent` and `SelectItemContent` render an icon, label, and description
|
|
967
|
+
Styled Reka select. `SelectValueContent` and `SelectItemContent` render an icon, label, and description; the default slot of `SelectItemContent` replaces the icon with any leading visual, such as a `LanguageFlag`.
|
|
968
968
|
|
|
969
969
|
```ts
|
|
970
970
|
import { Select, SelectContent, SelectGroup, SelectItem, SelectItemContent, SelectItemText, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SelectValueContent } from '@brumaombra/ui-vintage/select';
|
|
@@ -1019,6 +1019,8 @@ import { Select, SelectContent, SelectGroup, SelectItem, SelectItemContent, Sele
|
|
|
1019
1019
|
| `textClass` | `HTMLAttributes['class']` | |
|
|
1020
1020
|
| `descriptionClass` | `HTMLAttributes['class']` | |
|
|
1021
1021
|
|
|
1022
|
+
- **Slots:** `default`.
|
|
1023
|
+
|
|
1022
1024
|
#### `SelectItemText`
|
|
1023
1025
|
|
|
1024
1026
|
- **Also accepts** all props of Reka `SelectItemTextProps`.
|
|
@@ -1373,6 +1375,7 @@ import { Badge } from '@brumaombra/ui-vintage/badge';
|
|
|
1373
1375
|
| `color` | `ToneColor \| 'primary'` | `'gray'` |
|
|
1374
1376
|
| `dot` | `boolean` | `false` |
|
|
1375
1377
|
| `pulse` | `boolean` | `false` |
|
|
1378
|
+
| `interactive` | `boolean` | `false` |
|
|
1376
1379
|
| `class` | `HTMLAttributes['class']` | |
|
|
1377
1380
|
|
|
1378
1381
|
---
|
package/package.json
CHANGED
|
@@ -91,13 +91,6 @@ const props = defineProps<{
|
|
|
91
91
|
line-height: 1.85;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
/* Lede: the opening paragraph reads a little larger */
|
|
95
|
-
.blog-content :deep(p:not(.not-prose):not(.not-prose *):first-of-type) {
|
|
96
|
-
font-size: 17px;
|
|
97
|
-
line-height: 1.8;
|
|
98
|
-
color: var(--muted-foreground);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
94
|
.blog-content :deep(:is(p, blockquote, li):not(.not-prose):not(.not-prose *) strong) {
|
|
102
95
|
font-weight: 700;
|
|
103
96
|
color: var(--foreground);
|
|
@@ -215,10 +208,6 @@ const props = defineProps<{
|
|
|
215
208
|
line-height: 1.8;
|
|
216
209
|
}
|
|
217
210
|
|
|
218
|
-
.blog-content :deep(p:not(.not-prose):not(.not-prose *):first-of-type) {
|
|
219
|
-
font-size: 15px;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
211
|
.blog-content :deep(h2:not(.not-prose):not(.not-prose *)) {
|
|
223
212
|
margin-top: 2.25rem;
|
|
224
213
|
margin-bottom: 1rem;
|
|
@@ -20,7 +20,7 @@ const hasValidFAQs = computed(() => props.faqs && props.faqs.length > 0);
|
|
|
20
20
|
</script>
|
|
21
21
|
|
|
22
22
|
<template>
|
|
23
|
-
<div v-if="hasValidFAQs" class="mt-
|
|
23
|
+
<div v-if="hasValidFAQs" class="mt-10 lg:mt-12">
|
|
24
24
|
<!-- Title and description -->
|
|
25
25
|
<BlogSectionTitle :title="t('uiVintage.blog.faq.title')" :description="t('uiVintage.blog.faq.description')" />
|
|
26
26
|
|
|
@@ -34,12 +34,12 @@ const showModifiedDate = computed(() => Boolean(props.dateModified) && formatted
|
|
|
34
34
|
|
|
35
35
|
<template>
|
|
36
36
|
<!-- Author and date information -->
|
|
37
|
-
<Card data-aos="blur-up"
|
|
37
|
+
<Card data-aos="blur-up" padding="none">
|
|
38
38
|
<!-- Author -->
|
|
39
|
-
<div :class="['flex gap-3 px-5 py-4', props.authorBio ? 'items-start' : 'items-center']">
|
|
40
|
-
<!-- Author image -->
|
|
39
|
+
<div :class="['group/author flex gap-3 px-5 py-4', props.authorBio ? 'items-start' : 'items-center']">
|
|
40
|
+
<!-- Author image (grows softly while the author row is hovered) -->
|
|
41
41
|
<NuxtLinkLocale :to="props.authorUrl" target="_blank" rel="noopener noreferrer" class="shrink-0 rounded outline-none focus-visible:ring-[3px] focus-visible:ring-ring/45">
|
|
42
|
-
<NuxtImg :src="props.authorImageUrl" :alt="props.author" height="44" width="44" format="avif" quality="40" :sizes="{ 480: '44px', 1280: '44px' }" loading="lazy" decoding="async" class="size-11 rounded border border-border object-cover" />
|
|
42
|
+
<NuxtImg :src="props.authorImageUrl" :alt="props.author" height="44" width="44" format="avif" quality="40" :sizes="{ 480: '44px', 1280: '44px' }" loading="lazy" decoding="async" class="size-11 rounded border border-border object-cover transition-[scale] duration-500 ease-spring group-hover/author:scale-110" />
|
|
43
43
|
</NuxtLinkLocale>
|
|
44
44
|
|
|
45
45
|
<!-- Author name and bio -->
|
|
@@ -67,7 +67,7 @@ const hasCategory = computed(() => Boolean(props.categoryText && props.categoryS
|
|
|
67
67
|
// Whether the details row has any column to show
|
|
68
68
|
const hasDetails = computed(() => Boolean(props.author || formattedDate.value || resolvedReadingMinutes.value || hasCategory.value));
|
|
69
69
|
|
|
70
|
-
const badgeLinkClasses = 'rounded outline-none
|
|
70
|
+
const badgeLinkClasses = 'rounded outline-none focus-visible:ring-[3px] focus-visible:ring-ring/45';
|
|
71
71
|
|
|
72
72
|
// Shared classes for the details cells (exposed to the meta slot)
|
|
73
73
|
const detailCellClasses = 'min-w-0 border-t border-l border-border py-4 pl-4 pr-4';
|
|
@@ -153,7 +153,7 @@ const detailLabelClasses = 'text-[11px] font-semibold tracking-wider text-muted-
|
|
|
153
153
|
<dt :class="detailLabelClasses">{{ t('uiVintage.blog.category') }}</dt>
|
|
154
154
|
<dd class="mt-1.5">
|
|
155
155
|
<NuxtLinkLocale :to="categoryPath" :class="['inline-flex', badgeLinkClasses]">
|
|
156
|
-
<Badge color="yellow" :text="props.categoryText" pulse />
|
|
156
|
+
<Badge color="yellow" :text="props.categoryText" pulse interactive />
|
|
157
157
|
</NuxtLinkLocale>
|
|
158
158
|
</dd>
|
|
159
159
|
</div>
|
|
@@ -166,15 +166,14 @@ const detailLabelClasses = 'text-[11px] font-semibold tracking-wider text-muted-
|
|
|
166
166
|
<!-- Tags -->
|
|
167
167
|
<div v-if="props.tags.length > 0" :class="['mt-4 flex flex-wrap items-center gap-2', enter('animate-uv-fade-up [animation-delay:600ms]')]">
|
|
168
168
|
<NuxtLinkLocale v-for="tag in props.tags" :key="tag.slug" :to="tagPath(tag.slug)" :class="badgeLinkClasses">
|
|
169
|
-
<Badge :text="`#${tag.name}`" />
|
|
169
|
+
<Badge :text="`#${tag.name}`" interactive />
|
|
170
170
|
</NuxtLinkLocale>
|
|
171
171
|
</div>
|
|
172
172
|
|
|
173
173
|
<!-- Featured image (replaceable through the media slot) -->
|
|
174
174
|
<slot name="media">
|
|
175
|
-
<div v-if="props.image" :class="['relative mt-8 aspect-video w-full overflow-hidden rounded border border-border shadow-elevated-
|
|
175
|
+
<div v-if="props.image" :class="['relative mt-8 aspect-video w-full overflow-hidden rounded border border-border shadow-elevated-lg', enter('animate-uv-fade-up [animation-delay:650ms]')]">
|
|
176
176
|
<NuxtImg :src="props.image" :alt="props.title" format="avif" quality="50" :sizes="{ 480: '480px', 1536: '896px' }" loading="eager" fetchpriority="high" preload :class="['size-full object-cover', enter('animate-uv-ken-burns')]" />
|
|
177
|
-
<div aria-hidden="true" class="pointer-events-none absolute inset-0 bg-linear-to-t from-black/20 to-transparent" />
|
|
178
177
|
</div>
|
|
179
178
|
</slot>
|
|
180
179
|
</header>
|
|
@@ -88,7 +88,7 @@ const postTags = computed(() => ((props.post.tags as string[] | undefined) || []
|
|
|
88
88
|
</article>
|
|
89
89
|
|
|
90
90
|
<!-- Related posts -->
|
|
91
|
-
<section v-if="props.relatedPosts.length" class="mt-
|
|
91
|
+
<section v-if="props.relatedPosts.length" class="mt-12 md:mt-16">
|
|
92
92
|
<BlogSectionTitle :title="t('uiVintage.blog.readAlso')" />
|
|
93
93
|
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
|
94
94
|
<NuxtLinkLocale v-for="(relatedPost, index) in props.relatedPosts" :key="relatedPost.path" :to="relatedPost.path" data-aos="blur-up" :data-aos-delay="(index % 2) * 90" class="block h-full rounded outline-none focus-visible:ring-[3px] focus-visible:ring-ring/45">
|
|
@@ -102,7 +102,7 @@ const postTags = computed(() => ((props.post.tags as string[] | undefined) || []
|
|
|
102
102
|
</section>
|
|
103
103
|
|
|
104
104
|
<!-- Related categories -->
|
|
105
|
-
<section v-if="props.relatedCategories.length" class="mt-
|
|
105
|
+
<section v-if="props.relatedCategories.length" class="mt-12 md:mt-16">
|
|
106
106
|
<BlogSectionTitle :title="t('uiVintage.blog.exploreCategories')">
|
|
107
107
|
<template #action>
|
|
108
108
|
<BlogViewAllLink to="/blog/categories" :label="t('uiVintage.blog.exploreAllCategories')" />
|
|
@@ -17,7 +17,7 @@ const props = withDefaults(defineProps<{
|
|
|
17
17
|
<div v-if="props.tags.length" class="flex flex-wrap gap-2">
|
|
18
18
|
<!-- Tag chip -->
|
|
19
19
|
<span v-for="(tag, index) in props.tags" :key="tag.slug" data-aos="blur-up" :data-aos-delay="Math.min(index * 40, 400)" class="inline-flex">
|
|
20
|
-
<NuxtLinkLocale :to="`${props.blogPath}/tags/${tag.slug}`" class="group/tag inline-flex items-stretch overflow-hidden rounded border border-border text-xs shadow-elevated-sm outline-none transition-
|
|
20
|
+
<NuxtLinkLocale :to="`${props.blogPath}/tags/${tag.slug}`" class="group/tag inline-flex items-stretch overflow-hidden rounded border border-border text-xs shadow-elevated-sm outline-none transition-[translate,border-color,box-shadow] duration-300 ease-out-expo hover:-translate-y-0.5 hover:border-primary/40 hover:shadow-elevated-md focus-visible:ring-[3px] focus-visible:ring-ring/45 sm:text-sm">
|
|
21
21
|
<!-- Name -->
|
|
22
22
|
<span class="inline-flex items-center gap-1.5 bg-card px-3 py-1.5 font-medium text-foreground transition-colors duration-150 group-hover/tag:bg-primary/10 group-hover/tag:text-primary">
|
|
23
23
|
<span aria-hidden="true" class="text-primary">#</span>
|
|
@@ -16,7 +16,7 @@ const props = withDefaults(defineProps<{
|
|
|
16
16
|
</script>
|
|
17
17
|
|
|
18
18
|
<template>
|
|
19
|
-
<Card class="group/category h-full
|
|
19
|
+
<Card padding="none" interactive class="group/category h-full">
|
|
20
20
|
<!-- Image -->
|
|
21
21
|
<div class="h-32 w-full overflow-hidden border-b border-border bg-surface">
|
|
22
22
|
<NuxtImg v-if="props.image" :src="props.image" :alt="props.name" height="225" width="400" format="avif" quality="35" :sizes="{ 480: '480px', 1280: '400px' }" loading="lazy" decoding="async" class="size-full object-cover transition-[scale] duration-700 ease-out-expo group-hover/category:scale-[1.03]" />
|
|
@@ -22,7 +22,7 @@ const props = withDefaults(defineProps<{
|
|
|
22
22
|
</script>
|
|
23
23
|
|
|
24
24
|
<template>
|
|
25
|
-
<Card class="group/post h-full
|
|
25
|
+
<Card padding="none" interactive class="group/post h-full">
|
|
26
26
|
<!-- Featured image -->
|
|
27
27
|
<div v-if="props.image" class="aspect-video w-full overflow-hidden border-b border-border bg-surface">
|
|
28
28
|
<NuxtImg :src="props.image" :alt="props.title" height="225" width="400" format="avif" quality="35" :sizes="{ 480: '480px', 1280: '400px' }" loading="lazy" decoding="async" class="size-full object-cover transition-[scale] duration-700 ease-out-expo group-hover/post:scale-[1.03]" />
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { HTMLAttributes } from 'vue';
|
|
3
3
|
import { computed } from 'vue';
|
|
4
4
|
import { HugeiconsIcon } from '@hugeicons/vue';
|
|
5
|
-
import { getSurfaceToneClasses, type ToneColor } from '../../../lib/color-tokens';
|
|
5
|
+
import { getInteractiveSurfaceToneClasses, getSurfaceToneClasses, type ToneColor } from '../../../lib/color-tokens';
|
|
6
6
|
import { cn } from '../../../lib/utils';
|
|
7
7
|
import type { HugeiconsIconDefinition } from '../../../lib/common-types';
|
|
8
8
|
|
|
@@ -13,20 +13,33 @@ const props = withDefaults(defineProps<{
|
|
|
13
13
|
color?: ToneColor | 'primary';
|
|
14
14
|
dot?: boolean;
|
|
15
15
|
pulse?: boolean;
|
|
16
|
+
interactive?: boolean;
|
|
16
17
|
class?: HTMLAttributes['class'];
|
|
17
18
|
}>(), {
|
|
18
19
|
icon: null,
|
|
19
20
|
color: 'gray',
|
|
20
21
|
dot: false,
|
|
21
|
-
pulse: false
|
|
22
|
+
pulse: false,
|
|
23
|
+
interactive: false
|
|
22
24
|
});
|
|
23
25
|
|
|
24
26
|
// Surface of the badge: a tone, or a soft primary tint (for highlights like a category)
|
|
25
27
|
const colorClasses = computed(() => props.color === 'primary' ? 'border border-primary/30 bg-primary/10 text-primary' : getSurfaceToneClasses(props.color));
|
|
28
|
+
|
|
29
|
+
// Hover of a badge used as a link: it lifts and tints like the cards (a gray badge takes the primary tint, a tone badge its stronger tone)
|
|
30
|
+
const interactiveClasses = computed(() => {
|
|
31
|
+
if (!props.interactive) return '';
|
|
32
|
+
const tint = props.color === 'gray'
|
|
33
|
+
? 'hover:border-primary/40 hover:bg-primary/10 hover:text-primary'
|
|
34
|
+
: props.color === 'primary'
|
|
35
|
+
? 'hover:border-primary/50 hover:bg-primary/15'
|
|
36
|
+
: getInteractiveSurfaceToneClasses(props.color);
|
|
37
|
+
return `cursor-pointer transition-[translate,color,background-color,border-color] duration-200 ease-out-expo hover:-translate-y-0.5 ${tint}`;
|
|
38
|
+
});
|
|
26
39
|
</script>
|
|
27
40
|
|
|
28
41
|
<template>
|
|
29
|
-
<span data-slot="badge" :class="cn('inline-flex w-fit items-center justify-center gap-1.5 whitespace-nowrap rounded border px-2 py-0.5 text-xs font-semibold transition-colors duration-150', colorClasses, props.class)">
|
|
42
|
+
<span data-slot="badge" :class="cn('inline-flex w-fit items-center justify-center gap-1.5 whitespace-nowrap rounded border px-2 py-0.5 text-xs font-semibold transition-colors duration-150', colorClasses, interactiveClasses, props.class)">
|
|
30
43
|
<!-- Status dot -->
|
|
31
44
|
<span v-if="props.dot || props.pulse" class="relative flex size-1.5 shrink-0">
|
|
32
45
|
<span v-if="props.pulse" class="absolute inset-0 animate-uv-ping-soft rounded-full bg-current" />
|
|
@@ -18,9 +18,11 @@ const props = defineProps<{
|
|
|
18
18
|
|
|
19
19
|
<template>
|
|
20
20
|
<div data-slot="select-item-content" :class="cn('flex min-w-0 flex-1 items-center gap-2', props.class)">
|
|
21
|
-
<!--
|
|
22
|
-
<div v-if="props.icon" data-slot="select-item-content-icon" :class="cn('shrink-0 self-center', props.iconClass)">
|
|
23
|
-
<
|
|
21
|
+
<!-- Leading visual: the default slot (for example a flag), or the icon -->
|
|
22
|
+
<div v-if="$slots.default || props.icon" data-slot="select-item-content-icon" :class="cn('shrink-0 self-center', props.iconClass)">
|
|
23
|
+
<slot>
|
|
24
|
+
<HugeiconsIcon :icon="props.icon" class="block size-3 sm:size-4" />
|
|
25
|
+
</slot>
|
|
24
26
|
</div>
|
|
25
27
|
|
|
26
28
|
<!-- Text and description -->
|