feat(gui-v2): PageHead (Tailwind flex title/sub/#actions) + story
This commit is contained in:
24
apps/app/src/components-v2/shared/PageHead.stories.ts
Normal file
24
apps/app/src/components-v2/shared/PageHead.stories.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
||||||
|
import Button from 'primevue/button'
|
||||||
|
import PageHead from '@/components-v2/shared/PageHead.vue'
|
||||||
|
|
||||||
|
const meta: Meta<typeof PageHead> = {
|
||||||
|
title: 'Shared/PageHead',
|
||||||
|
component: PageHead,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: { title: { control: 'text' }, sub: { control: 'text' } },
|
||||||
|
}
|
||||||
|
|
||||||
|
export default meta
|
||||||
|
type Story = StoryObj<typeof PageHead>
|
||||||
|
|
||||||
|
export const TitleOnly: Story = { args: { title: 'Evenementen' } }
|
||||||
|
export const WithSub: Story = { args: { title: 'Evenementen', sub: '3 actieve evenementen' } }
|
||||||
|
export const WithActions: Story = {
|
||||||
|
args: { title: 'Evenementen', sub: '3 actief' },
|
||||||
|
render: args => ({
|
||||||
|
components: { PageHead, Button },
|
||||||
|
setup: () => ({ args }),
|
||||||
|
template: '<PageHead :title="args.title" :sub="args.sub"><template #actions><Button label="Nieuw evenement" /></template></PageHead>',
|
||||||
|
}),
|
||||||
|
}
|
||||||
27
apps/app/src/components-v2/shared/PageHead.vue
Normal file
27
apps/app/src/components-v2/shared/PageHead.vue
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* PageHead — title/sub/#actions. Pure layout (spec §8). CSS translation
|
||||||
|
* of main.css .page-head 466–478 (+ <=640px stack 1355–1360) to Tailwind.
|
||||||
|
*/
|
||||||
|
defineProps<{ title: string; sub?: string }>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-wrap items-end justify-between gap-6 mb-5 max-[640px]:flex-col max-[640px]:items-stretch max-[640px]:gap-3.5">
|
||||||
|
<div class="min-w-0 flex-[1_1_240px] max-[640px]:flex-[0_0_auto]">
|
||||||
|
<h1 class="m-0 text-2xl font-bold tracking-[-0.01em] text-[var(--p-text-color)] text-balance max-[640px]:text-[22px]">
|
||||||
|
{{ title }}
|
||||||
|
</h1>
|
||||||
|
<div
|
||||||
|
v-if="sub"
|
||||||
|
data-sub
|
||||||
|
class="mt-1 text-[13.5px] text-[var(--p-text-muted-color)]"
|
||||||
|
>
|
||||||
|
{{ sub }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap items-center gap-2 max-[640px]:w-full max-[640px]:justify-start">
|
||||||
|
<slot name="actions" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
24
apps/app/src/components-v2/shared/__tests__/PageHead.spec.ts
Normal file
24
apps/app/src/components-v2/shared/__tests__/PageHead.spec.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import PageHead from '@/components-v2/shared/PageHead.vue'
|
||||||
|
|
||||||
|
describe('PageHead', () => {
|
||||||
|
it('renders the title and optional sub', () => {
|
||||||
|
const w = mount(PageHead, { props: { title: 'Evenementen', sub: '3 actief' } })
|
||||||
|
|
||||||
|
expect(w.get('h1').text()).toBe('Evenementen')
|
||||||
|
expect(w.text()).toContain('3 actief')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('omits the sub element when not provided', () => {
|
||||||
|
const w = mount(PageHead, { props: { title: 'Evenementen' } })
|
||||||
|
|
||||||
|
expect(w.find('[data-sub]').exists()).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the #actions slot', () => {
|
||||||
|
const w = mount(PageHead, { props: { title: 'X' }, slots: { actions: '<button>New</button>' } })
|
||||||
|
|
||||||
|
expect(w.get('button').text()).toBe('New')
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user