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