From fed19667f74301f2283b2103c9057379ed13dea0 Mon Sep 17 00:00:00 2001 From: Matthew L McPeak Date: Sat, 11 Jul 2026 14:41:58 -0400 Subject: [PATCH] feat: migrate Card to shadcn-vue compound API Co-Authored-By: Claude Sonnet 5 --- packages/library/src/components/index.ts | 1 + .../library/src/components/ui/card/Card.vue | 21 ++++++ .../src/components/ui/card/CardAction.vue | 17 +++++ .../src/components/ui/card/CardContent.vue | 17 +++++ .../components/ui/card/CardDescription.vue | 17 +++++ .../src/components/ui/card/CardFooter.vue | 17 +++++ .../src/components/ui/card/CardHeader.vue | 17 +++++ .../src/components/ui/card/CardTitle.vue | 17 +++++ .../library/src/components/ui/card/index.ts | 7 ++ packages/library/src/index.ts | 6 ++ packages/library/src/lib/index.ts | 45 ++++++++---- packages/library/tests/Card.spec.ts | 32 +++++++++ packages/library/tests/lib.spec.ts | 2 - packages/playground/stories/Card.stories.ts | 69 ++++++++++++------- 14 files changed, 246 insertions(+), 39 deletions(-) create mode 100644 packages/library/src/components/ui/card/Card.vue create mode 100644 packages/library/src/components/ui/card/CardAction.vue create mode 100644 packages/library/src/components/ui/card/CardContent.vue create mode 100644 packages/library/src/components/ui/card/CardDescription.vue create mode 100644 packages/library/src/components/ui/card/CardFooter.vue create mode 100644 packages/library/src/components/ui/card/CardHeader.vue create mode 100644 packages/library/src/components/ui/card/CardTitle.vue create mode 100644 packages/library/src/components/ui/card/index.ts create mode 100644 packages/library/tests/Card.spec.ts diff --git a/packages/library/src/components/index.ts b/packages/library/src/components/index.ts index d75a3c1..fb22734 100644 --- a/packages/library/src/components/index.ts +++ b/packages/library/src/components/index.ts @@ -9,3 +9,4 @@ export { Badge, badgeVariants } from './ui/badge' export { Checkbox } from './ui/checkbox' export { CheckboxGroup } from './ui/checkbox-group' export { RadioGroup, RadioGroupItem } from './ui/radio-group' +export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, CardAction } from './ui/card' diff --git a/packages/library/src/components/ui/card/Card.vue b/packages/library/src/components/ui/card/Card.vue new file mode 100644 index 0000000..a5be5a5 --- /dev/null +++ b/packages/library/src/components/ui/card/Card.vue @@ -0,0 +1,21 @@ + + + diff --git a/packages/library/src/components/ui/card/CardAction.vue b/packages/library/src/components/ui/card/CardAction.vue new file mode 100644 index 0000000..c91638b --- /dev/null +++ b/packages/library/src/components/ui/card/CardAction.vue @@ -0,0 +1,17 @@ + + + diff --git a/packages/library/src/components/ui/card/CardContent.vue b/packages/library/src/components/ui/card/CardContent.vue new file mode 100644 index 0000000..9421851 --- /dev/null +++ b/packages/library/src/components/ui/card/CardContent.vue @@ -0,0 +1,17 @@ + + + diff --git a/packages/library/src/components/ui/card/CardDescription.vue b/packages/library/src/components/ui/card/CardDescription.vue new file mode 100644 index 0000000..1641f9e --- /dev/null +++ b/packages/library/src/components/ui/card/CardDescription.vue @@ -0,0 +1,17 @@ + + + diff --git a/packages/library/src/components/ui/card/CardFooter.vue b/packages/library/src/components/ui/card/CardFooter.vue new file mode 100644 index 0000000..46bf012 --- /dev/null +++ b/packages/library/src/components/ui/card/CardFooter.vue @@ -0,0 +1,17 @@ + + + diff --git a/packages/library/src/components/ui/card/CardHeader.vue b/packages/library/src/components/ui/card/CardHeader.vue new file mode 100644 index 0000000..ef3f9a0 --- /dev/null +++ b/packages/library/src/components/ui/card/CardHeader.vue @@ -0,0 +1,17 @@ + + + diff --git a/packages/library/src/components/ui/card/CardTitle.vue b/packages/library/src/components/ui/card/CardTitle.vue new file mode 100644 index 0000000..eb55649 --- /dev/null +++ b/packages/library/src/components/ui/card/CardTitle.vue @@ -0,0 +1,17 @@ + + + diff --git a/packages/library/src/components/ui/card/index.ts b/packages/library/src/components/ui/card/index.ts new file mode 100644 index 0000000..1627758 --- /dev/null +++ b/packages/library/src/components/ui/card/index.ts @@ -0,0 +1,7 @@ +export { default as Card } from "./Card.vue" +export { default as CardAction } from "./CardAction.vue" +export { default as CardContent } from "./CardContent.vue" +export { default as CardDescription } from "./CardDescription.vue" +export { default as CardFooter } from "./CardFooter.vue" +export { default as CardHeader } from "./CardHeader.vue" +export { default as CardTitle } from "./CardTitle.vue" diff --git a/packages/library/src/index.ts b/packages/library/src/index.ts index 0a51792..ad818d1 100644 --- a/packages/library/src/index.ts +++ b/packages/library/src/index.ts @@ -19,6 +19,12 @@ export { Switch, Select, Card, + CardHeader, + CardTitle, + CardDescription, + CardContent, + CardFooter, + CardAction, Dialog, Message, Badge, diff --git a/packages/library/src/lib/index.ts b/packages/library/src/lib/index.ts index 4efbb97..78bb977 100644 --- a/packages/library/src/lib/index.ts +++ b/packages/library/src/lib/index.ts @@ -1,7 +1,6 @@ import type { App } from 'vue' import { Select as PrimeSelect, - Card as PrimeCard, Dialog as PrimeDialog, Message as PrimeMessage, } from 'primevue' @@ -19,6 +18,13 @@ import { CheckboxGroup, RadioGroup, RadioGroupItem, + Card, + CardHeader, + CardTitle, + CardDescription, + CardContent, + CardFooter, + CardAction, } from '../components' export { @@ -33,6 +39,13 @@ export { CheckboxGroup, RadioGroup, RadioGroupItem, + Card, + CardHeader, + CardTitle, + CardDescription, + CardContent, + CardFooter, + CardAction, } /** @@ -89,15 +102,6 @@ const nychthemeron: ThemeLibrary = { 'optionLabel', 'emptyMessage', ]), - Card: ptThemeable(PrimeCard, 'card', [ - 'header', - 'body', - 'caption', - 'title', - 'subtitle', - 'content', - 'footer', - ]), Dialog: ptThemeable(PrimeDialog, 'dialog', [ 'mask', 'header', @@ -121,7 +125,6 @@ const _engine = new ThemeEngine(nychthemeron) export const LoadingIcon = _engine.getComponent('LoadingIcon') export const Select: typeof PrimeSelect = _engine.getComponent('Select') -export const Card: typeof PrimeCard = _engine.getComponent('Card') export const Dialog: typeof PrimeDialog = _engine.getComponent('Dialog') export const Message: typeof PrimeMessage = _engine.getComponent('Message') @@ -136,7 +139,13 @@ export const createNychthemeron = (): { RadioGroupItem: typeof RadioGroupItem Switch: typeof Switch Select: typeof PrimeSelect - Card: typeof PrimeCard + Card: typeof Card + CardHeader: typeof CardHeader + CardTitle: typeof CardTitle + CardDescription: typeof CardDescription + CardContent: typeof CardContent + CardFooter: typeof CardFooter + CardAction: typeof CardAction Dialog: typeof PrimeDialog Message: typeof PrimeMessage Badge: typeof Badge @@ -153,6 +162,12 @@ export const createNychthemeron = (): { Switch, Select, Card, + CardHeader, + CardTitle, + CardDescription, + CardContent, + CardFooter, + CardAction, Dialog, Message, Badge, @@ -168,6 +183,12 @@ export const createNychthemeron = (): { app.component('NychSwitch', Switch) app.component('NychSelect', Select) app.component('NychCard', Card) + app.component('NychCardHeader', CardHeader) + app.component('NychCardTitle', CardTitle) + app.component('NychCardDescription', CardDescription) + app.component('NychCardContent', CardContent) + app.component('NychCardFooter', CardFooter) + app.component('NychCardAction', CardAction) app.component('NychDialog', Dialog) app.component('NychMessage', Message) app.component('NychBadge', Badge) diff --git a/packages/library/tests/Card.spec.ts b/packages/library/tests/Card.spec.ts new file mode 100644 index 0000000..c51fb16 --- /dev/null +++ b/packages/library/tests/Card.spec.ts @@ -0,0 +1,32 @@ +import { describe, it, expect } from 'vitest' +import { h } from 'vue' +import { mount } from '@vue/test-utils' +import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from '../src/lib' + +describe('Card', () => { + it('renders header, title, description, content, and footer together', () => { + const wrapper = mount(Card, { + slots: { + default: () => [ + h(CardHeader, undefined, { + default: () => [ + h(CardTitle, undefined, { default: () => 'Elysium' }), + h(CardDescription, undefined, { default: () => 'The blessed fields' }), + ], + }), + h(CardContent, undefined, { default: () => 'A resting place for the virtuous.' }), + h(CardFooter, undefined, { default: () => 'Enter' }), + ], + }, + }) + expect(wrapper.text()).toContain('Elysium') + expect(wrapper.text()).toContain('The blessed fields') + expect(wrapper.text()).toContain('A resting place for the virtuous.') + expect(wrapper.text()).toContain('Enter') + }) + + it('exposes a data-slot attribute for styling hooks', () => { + const wrapper = mount(Card) + expect(wrapper.attributes('data-slot')).toBe('card') + }) +}) diff --git a/packages/library/tests/lib.spec.ts b/packages/library/tests/lib.spec.ts index 4a523c5..6080c56 100644 --- a/packages/library/tests/lib.spec.ts +++ b/packages/library/tests/lib.spec.ts @@ -4,7 +4,6 @@ import { createNychthemeron, LoadingIcon, Select, - Card, Dialog, Message, } from '../src/index' @@ -64,7 +63,6 @@ describe('LoadingIcon', () => { describe('themed pt components', () => { const cases = [ { name: 'Select', component: Select, base: 'nych-select' }, - { name: 'Card', component: Card, base: 'nych-card' }, { name: 'Dialog', component: Dialog, base: 'nych-dialog' }, { name: 'Message', component: Message, base: 'nych-message' }, ] diff --git a/packages/playground/stories/Card.stories.ts b/packages/playground/stories/Card.stories.ts index e005029..21d9c02 100644 --- a/packages/playground/stories/Card.stories.ts +++ b/packages/playground/stories/Card.stories.ts @@ -1,6 +1,15 @@ import type { Meta, StoryObj } from '@storybook/vue3-vite' -import { Card as NychCard, Button as NychButton, Badge as NychBadge } from '@nychthemeron/library' +import { + Card as NychCard, + CardHeader as NychCardHeader, + CardTitle as NychCardTitle, + CardDescription as NychCardDescription, + CardContent as NychCardContent, + CardFooter as NychCardFooter, + Button as NychButton, + Badge as NychBadge, +} from '@nychthemeron/library' const meta = { title: 'Components/Card', @@ -16,15 +25,17 @@ type Story = StoryObj export const Default: Story = { render: () => ({ - components: { NychCard }, + components: { NychCard, NychCardHeader, NychCardTitle, NychCardDescription, NychCardContent }, template: ` - - - + `, }), @@ -32,19 +43,29 @@ export const Default: Story = { export const WithFooter: Story = { render: () => ({ - components: { NychCard, NychButton }, + components: { + NychCard, + NychCardHeader, + NychCardTitle, + NychCardDescription, + NychCardContent, + NychCardFooter, + NychButton, + }, template: ` - - - - + + + Pay the toll + Turn back + `, }), @@ -52,21 +73,19 @@ export const WithFooter: Story = { export const WithHeaderAndTag: Story = { render: () => ({ - components: { NychCard, NychBadge }, + components: { NychCard, NychCardHeader, NychCardTitle, NychCardContent, NychBadge }, template: ` - - - + `, }),