Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/kit/json-render.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ Container with an optional title and collapsible behavior.

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `title` | `string` | — | Header title |
| `title` | `string` | — | Header title. A collapsible card still shows its (empty) header without one — otherwise there'd be nothing to click |
| `collapsible` | `boolean` | `false` | Whether the card can be collapsed |
| `defaultCollapsed` | `boolean` | `false` | Start collapsed (when `collapsible`) |
| `loading` | `boolean` | `false` | Show a loading state |
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/client/webcomponents/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export * from './components/DockStandalone'
export type {
BadgeProps,
ButtonProps,
CardProps,
CodeBlockProps,
DataTableColumn,
DataTableProps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,29 +82,35 @@ export const Gallery: Story = {
}

/**
* A `Card` grouping content under a titled, bordered surface. Toggle the
* Controls below to compare today's default (`primary`, non-`interactive` —
* unchanged from before this fix) against the new opt-in look: `variant`
* A `Card` grouping content under a titled, bordered surface. `variant`
* tints the background (`secondary`/`danger`) or leaves it untouched
* (`primary`/`ghost`); `interactive` strengthens the Card's border on hover
* and tints each row's (`Stack`) background on hover.
* (`primary`/`ghost`); `interactive` strengthens the border on hover and
* tints each row's (`Stack`) background. `collapsible` + `defaultCollapsed`
* control the initial collapse state; clear `title` to confirm the header
* (and its chevron) still renders without one.
*/
interface CardArgs {
variant: 'primary' | 'secondary' | 'ghost' | 'danger'
interactive: boolean
title: string
collapsible: boolean
defaultCollapsed: boolean
}

export const Card: StoryObj<Meta<CardArgs>> = {
argTypes: {
variant: { control: 'select', options: ['primary', 'secondary', 'ghost', 'danger'] },
interactive: { control: 'boolean' },
title: { control: 'text' },
collapsible: { control: 'boolean' },
defaultCollapsed: { control: 'boolean' },
},
args: { variant: 'primary', interactive: false },
args: { variant: 'primary', interactive: false, title: 'Plugin', collapsible: true, defaultCollapsed: false },
render: args => renderSpec(() => ({
root: 'root',
state: {},
elements: {
root: { type: 'Card', props: { title: 'Plugin', collapsible: false, variant: args.variant, interactive: args.interactive }, children: ['body'] },
root: { type: 'Card', props: { title: args.title, collapsible: args.collapsible, defaultCollapsed: args.defaultCollapsed, variant: args.variant, interactive: args.interactive }, children: ['body'] },
body: { type: 'Stack', props: { direction: 'column', gap: 4, padding: 4 }, children: ['row1', 'row2'] },
row1: { type: 'Stack', props: { direction: 'row', gap: 8, align: 'center', interactive: args.interactive }, children: ['t', 'badge'] },
t: { type: 'Text', props: { text: 'vite-plugin-inspect', variant: 'code' } },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import type { RegistryComponentProps } from './types'
import { defineComponent, h, ref } from 'vue'
import { border, borderSolid, borderStrong, colors, surfaceMuted } from './tokens'
import { registryProps } from './types'

export type CardVariant = 'primary' | 'secondary' | 'ghost' | 'danger'

export interface CardProps {
title?: string
collapsible?: boolean
defaultCollapsed?: boolean
variant?: CardVariant
interactive?: boolean
}

// Mirrors `ContainerCard.vue`'s (packages/ui) opt-in `variant` model: `primary`
// (default) keeps today's fully transparent look, so existing specs render
// unchanged; `ghost` is the same no-fill look under a more intentional name.
const variantBackground: Record<string, string | undefined> = {
const variantBackground: Record<CardVariant, string | undefined> = {
primary: undefined,
secondary: surfaceMuted,
ghost: undefined,
Expand All @@ -14,11 +24,16 @@ const variantBackground: Record<string, string | undefined> = {

export const Card = defineComponent({
name: 'JrCard',
props: ['element', 'emit', 'on', 'bindings', 'loading'],
setup(ctx: RegistryComponentProps, { slots }) {
const collapsed = ref(false)
props: registryProps<'Card', CardProps>(),
setup(ctx, { slots }) {
const collapsed = ref(!!ctx.element.props.defaultCollapsed)
return () => {
const { title, collapsible, variant = 'primary', interactive = false } = ctx.element.props
const toggle = ctx.on('toggle')
const setCollapsed = (next: boolean) => {
collapsed.value = next
toggle.emit()
}
return h('div', {
class: 'jr-card',
style: {
Expand All @@ -34,7 +49,8 @@ export const Card = defineComponent({
onMouseenter: interactive ? (e: MouseEvent) => { (e.currentTarget as HTMLElement).style.borderColor = borderStrong } : undefined,
onMouseleave: interactive ? (e: MouseEvent) => { (e.currentTarget as HTMLElement).style.borderColor = border } : undefined,
}, [
title && h('div', {
// Renders without a `title` too — a collapsible card needs this as its click target.
(title || collapsible) && h('div', {
class: 'jr-card-header',
style: {
padding: '8px 12px',
Expand All @@ -47,7 +63,7 @@ export const Card = defineComponent({
borderBottom: collapsed.value ? 'none' : borderSolid(border),
userSelect: 'none',
},
onClick: collapsible ? () => { collapsed.value = !collapsed.value } : undefined,
onClick: collapsible ? () => setCollapsed(!collapsed.value) : undefined,
}, [
h('span', title),
collapsible && h('span', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export { UnsupportedComponent }
*/
export type { BadgeProps } from './components/Badge'
export type { ButtonProps } from './components/Button'
export type { CardProps } from './components/Card'
export type { CodeBlockProps } from './components/CodeBlock'
export type { DataTableColumn, DataTableProps } from './components/DataTable'
export type { DividerProps } from './components/Divider'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ export interface ButtonProps {
icon?: string;
disabled?: boolean;
}
export interface CardProps {
title?: string;
collapsible?: boolean;
defaultCollapsed?: boolean;
variant?: CardVariant;
interactive?: boolean;
}
export interface CodeBlockProps {
code?: string;
filename?: string;
Expand Down
Loading