@wordpress/ui 0.8.0 → 0.9.1-next.v.202603102151.0
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/CHANGELOG.md +19 -0
- package/build/card/content.cjs +54 -0
- package/build/card/content.cjs.map +7 -0
- package/build/card/full-bleed.cjs +57 -0
- package/build/card/full-bleed.cjs.map +7 -0
- package/build/card/header.cjs +54 -0
- package/build/card/header.cjs.map +7 -0
- package/build/card/index.cjs +43 -0
- package/build/card/index.cjs.map +7 -0
- package/build/card/root.cjs +73 -0
- package/build/card/root.cjs.map +7 -0
- package/build/card/title.cjs +55 -0
- package/build/card/title.cjs.map +7 -0
- package/build/card/types.cjs +19 -0
- package/build/card/types.cjs.map +7 -0
- package/build/collapsible-card/content.cjs +56 -0
- package/build/collapsible-card/content.cjs.map +7 -0
- package/build/collapsible-card/header.cjs +104 -0
- package/build/collapsible-card/header.cjs.map +7 -0
- package/build/collapsible-card/index.cjs +37 -0
- package/build/collapsible-card/index.cjs.map +7 -0
- package/build/collapsible-card/root.cjs +56 -0
- package/build/collapsible-card/root.cjs.map +7 -0
- package/build/collapsible-card/types.cjs +19 -0
- package/build/collapsible-card/types.cjs.map +7 -0
- package/build/form/primitives/field/label.cjs +6 -1
- package/build/form/primitives/field/label.cjs.map +2 -2
- package/build/form/primitives/field/types.cjs.map +1 -1
- package/build/form/primitives/fieldset/legend.cjs +5 -1
- package/build/form/primitives/fieldset/legend.cjs.map +2 -2
- package/build/form/primitives/fieldset/types.cjs.map +1 -1
- package/build/index.cjs +10 -0
- package/build/index.cjs.map +2 -2
- package/build/link/index.cjs +31 -0
- package/build/link/index.cjs.map +7 -0
- package/build/link/link.cjs +125 -0
- package/build/link/link.cjs.map +7 -0
- package/build/link/types.cjs +19 -0
- package/build/link/types.cjs.map +7 -0
- package/build/notice/action-button.cjs +3 -3
- package/build/notice/action-button.cjs.map +2 -2
- package/build/notice/action-link.cjs +17 -18
- package/build/notice/action-link.cjs.map +2 -2
- package/build/notice/actions.cjs +3 -3
- package/build/notice/actions.cjs.map +2 -2
- package/build/notice/close-icon.cjs +3 -3
- package/build/notice/close-icon.cjs.map +2 -2
- package/build/notice/description.cjs +26 -15
- package/build/notice/description.cjs.map +3 -3
- package/build/notice/root.cjs +3 -3
- package/build/notice/root.cjs.map +2 -2
- package/build/notice/title.cjs +26 -12
- package/build/notice/title.cjs.map +3 -3
- package/build/notice/types.cjs.map +1 -1
- package/build/text/index.cjs +31 -0
- package/build/text/index.cjs.map +7 -0
- package/build/text/text.cjs +65 -0
- package/build/text/text.cjs.map +7 -0
- package/build/text/types.cjs +19 -0
- package/build/text/types.cjs.map +7 -0
- package/build/tooltip/popup.cjs +1 -1
- package/build/tooltip/popup.cjs.map +1 -1
- package/build/visually-hidden/visually-hidden.cjs +3 -3
- package/build/visually-hidden/visually-hidden.cjs.map +2 -2
- package/build-module/card/content.mjs +29 -0
- package/build-module/card/content.mjs.map +7 -0
- package/build-module/card/full-bleed.mjs +32 -0
- package/build-module/card/full-bleed.mjs.map +7 -0
- package/build-module/card/header.mjs +29 -0
- package/build-module/card/header.mjs.map +7 -0
- package/build-module/card/index.mjs +14 -0
- package/build-module/card/index.mjs.map +7 -0
- package/build-module/card/root.mjs +38 -0
- package/build-module/card/root.mjs.map +7 -0
- package/build-module/card/title.mjs +30 -0
- package/build-module/card/title.mjs.map +7 -0
- package/build-module/card/types.mjs +1 -0
- package/build-module/card/types.mjs.map +7 -0
- package/build-module/collapsible-card/content.mjs +21 -0
- package/build-module/collapsible-card/content.mjs.map +7 -0
- package/build-module/collapsible-card/header.mjs +69 -0
- package/build-module/collapsible-card/header.mjs.map +7 -0
- package/build-module/collapsible-card/index.mjs +10 -0
- package/build-module/collapsible-card/index.mjs.map +7 -0
- package/build-module/collapsible-card/root.mjs +21 -0
- package/build-module/collapsible-card/root.mjs.map +7 -0
- package/build-module/collapsible-card/types.mjs +1 -0
- package/build-module/collapsible-card/types.mjs.map +7 -0
- package/build-module/form/primitives/field/label.mjs +6 -1
- package/build-module/form/primitives/field/label.mjs.map +2 -2
- package/build-module/form/primitives/fieldset/legend.mjs +5 -1
- package/build-module/form/primitives/fieldset/legend.mjs.map +2 -2
- package/build-module/index.mjs +6 -0
- package/build-module/index.mjs.map +2 -2
- package/build-module/link/index.mjs +6 -0
- package/build-module/link/index.mjs.map +7 -0
- package/build-module/link/link.mjs +90 -0
- package/build-module/link/link.mjs.map +7 -0
- package/build-module/link/types.mjs +1 -0
- package/build-module/link/types.mjs.map +7 -0
- package/build-module/notice/action-button.mjs +3 -3
- package/build-module/notice/action-button.mjs.map +2 -2
- package/build-module/notice/action-link.mjs +17 -18
- package/build-module/notice/action-link.mjs.map +2 -2
- package/build-module/notice/actions.mjs +3 -3
- package/build-module/notice/actions.mjs.map +2 -2
- package/build-module/notice/close-icon.mjs +3 -3
- package/build-module/notice/close-icon.mjs.map +2 -2
- package/build-module/notice/description.mjs +16 -15
- package/build-module/notice/description.mjs.map +2 -2
- package/build-module/notice/root.mjs +3 -3
- package/build-module/notice/root.mjs.map +2 -2
- package/build-module/notice/title.mjs +16 -12
- package/build-module/notice/title.mjs.map +2 -2
- package/build-module/text/index.mjs +6 -0
- package/build-module/text/index.mjs.map +7 -0
- package/build-module/text/text.mjs +30 -0
- package/build-module/text/text.mjs.map +7 -0
- package/build-module/text/types.mjs +1 -0
- package/build-module/text/types.mjs.map +7 -0
- package/build-module/tooltip/popup.mjs +1 -1
- package/build-module/tooltip/popup.mjs.map +1 -1
- package/build-module/visually-hidden/visually-hidden.mjs +3 -3
- package/build-module/visually-hidden/visually-hidden.mjs.map +2 -2
- package/build-types/card/content.d.ts +6 -0
- package/build-types/card/content.d.ts.map +1 -0
- package/build-types/card/full-bleed.d.ts +9 -0
- package/build-types/card/full-bleed.d.ts.map +1 -0
- package/build-types/card/header.d.ts +7 -0
- package/build-types/card/header.d.ts.map +1 -0
- package/build-types/card/index.d.ts +7 -0
- package/build-types/card/index.d.ts.map +1 -0
- package/build-types/card/root.d.ts +23 -0
- package/build-types/card/root.d.ts.map +1 -0
- package/build-types/card/stories/index.story.d.ts +22 -0
- package/build-types/card/stories/index.story.d.ts.map +1 -0
- package/build-types/card/test/index.test.d.ts +2 -0
- package/build-types/card/test/index.test.d.ts.map +1 -0
- package/build-types/card/title.d.ts +7 -0
- package/build-types/card/title.d.ts.map +1 -0
- package/build-types/card/types.d.ts +34 -0
- package/build-types/card/types.d.ts.map +1 -0
- package/build-types/collapsible-card/content.d.ts +7 -0
- package/build-types/collapsible-card/content.d.ts.map +1 -0
- package/build-types/collapsible-card/header.d.ts +12 -0
- package/build-types/collapsible-card/header.d.ts.map +1 -0
- package/build-types/collapsible-card/index.d.ts +5 -0
- package/build-types/collapsible-card/index.d.ts.map +1 -0
- package/build-types/collapsible-card/root.d.ts +24 -0
- package/build-types/collapsible-card/root.d.ts.map +1 -0
- package/build-types/collapsible-card/stories/index.story.d.ts +23 -0
- package/build-types/collapsible-card/stories/index.story.d.ts.map +1 -0
- package/build-types/collapsible-card/test/index.test.d.ts +2 -0
- package/build-types/collapsible-card/test/index.test.d.ts.map +1 -0
- package/build-types/collapsible-card/types.d.ts +42 -0
- package/build-types/collapsible-card/types.d.ts.map +1 -0
- package/build-types/form/primitives/field/label.d.ts +1 -0
- package/build-types/form/primitives/field/label.d.ts.map +1 -1
- package/build-types/form/primitives/field/stories/index.story.d.ts +5 -0
- package/build-types/form/primitives/field/stories/index.story.d.ts.map +1 -1
- package/build-types/form/primitives/field/types.d.ts +7 -0
- package/build-types/form/primitives/field/types.d.ts.map +1 -1
- package/build-types/form/primitives/fieldset/legend.d.ts +1 -0
- package/build-types/form/primitives/fieldset/legend.d.ts.map +1 -1
- package/build-types/form/primitives/fieldset/stories/index.story.d.ts +5 -0
- package/build-types/form/primitives/fieldset/stories/index.story.d.ts.map +1 -1
- package/build-types/form/primitives/fieldset/types.d.ts +7 -0
- package/build-types/form/primitives/fieldset/types.d.ts.map +1 -1
- package/build-types/index.d.ts +4 -0
- package/build-types/index.d.ts.map +1 -1
- package/build-types/link/index.d.ts +2 -0
- package/build-types/link/index.d.ts.map +1 -0
- package/build-types/link/link.d.ts +7 -0
- package/build-types/link/link.d.ts.map +1 -0
- package/build-types/link/stories/index.story.d.ts +18 -0
- package/build-types/link/stories/index.story.d.ts.map +1 -0
- package/build-types/link/test/index.test.d.ts +2 -0
- package/build-types/link/test/index.test.d.ts.map +1 -0
- package/build-types/link/types.d.ts +33 -0
- package/build-types/link/types.d.ts.map +1 -0
- package/build-types/notice/action-link.d.ts +0 -2
- package/build-types/notice/action-link.d.ts.map +1 -1
- package/build-types/notice/description.d.ts +1 -1
- package/build-types/notice/description.d.ts.map +1 -1
- package/build-types/notice/title.d.ts.map +1 -1
- package/build-types/notice/types.d.ts +3 -2
- package/build-types/notice/types.d.ts.map +1 -1
- package/build-types/text/index.d.ts +2 -0
- package/build-types/text/index.d.ts.map +1 -0
- package/build-types/text/stories/index.story.d.ts +9 -0
- package/build-types/text/stories/index.story.d.ts.map +1 -0
- package/build-types/text/test/index.test.d.ts +2 -0
- package/build-types/text/test/index.test.d.ts.map +1 -0
- package/build-types/text/text.d.ts +7 -0
- package/build-types/text/text.d.ts.map +1 -0
- package/build-types/text/types.d.ts +15 -0
- package/build-types/text/types.d.ts.map +1 -0
- package/package.json +11 -11
- package/src/card/content.tsx +20 -0
- package/src/card/full-bleed.tsx +26 -0
- package/src/card/header.tsx +21 -0
- package/src/card/index.ts +7 -0
- package/src/card/root.tsx +42 -0
- package/src/card/stories/index.story.tsx +128 -0
- package/src/card/style.module.css +47 -0
- package/src/card/test/index.test.tsx +96 -0
- package/src/card/title.tsx +22 -0
- package/src/card/types.ts +38 -0
- package/src/collapsible-card/content.tsx +20 -0
- package/src/collapsible-card/header.tsx +71 -0
- package/src/collapsible-card/index.ts +5 -0
- package/src/collapsible-card/root.tsx +37 -0
- package/src/collapsible-card/stories/index.story.tsx +156 -0
- package/src/collapsible-card/style.module.css +37 -0
- package/src/collapsible-card/test/index.test.tsx +207 -0
- package/src/collapsible-card/types.ts +44 -0
- package/src/form/primitives/field/label.tsx +9 -1
- package/src/form/primitives/field/stories/index.story.tsx +17 -0
- package/src/form/primitives/field/test/index.test.tsx +13 -0
- package/src/form/primitives/field/types.ts +7 -0
- package/src/form/primitives/fieldset/legend.tsx +8 -1
- package/src/form/primitives/fieldset/stories/index.story.tsx +20 -0
- package/src/form/primitives/fieldset/test/index.test.tsx +14 -0
- package/src/form/primitives/fieldset/types.ts +7 -0
- package/src/index.ts +4 -0
- package/src/link/index.ts +1 -0
- package/src/link/link.tsx +73 -0
- package/src/link/stories/index.story.tsx +92 -0
- package/src/link/style.module.css +68 -0
- package/src/link/test/index.test.tsx +93 -0
- package/src/link/types.ts +36 -0
- package/src/notice/action-link.tsx +12 -18
- package/src/notice/description.tsx +12 -14
- package/src/notice/style.module.css +9 -22
- package/src/notice/test/index.test.tsx +2 -2
- package/src/notice/title.tsx +11 -10
- package/src/notice/types.ts +3 -2
- package/src/text/index.ts +1 -0
- package/src/text/stories/index.story.tsx +68 -0
- package/src/text/style.module.css +67 -0
- package/src/text/test/index.test.tsx +46 -0
- package/src/text/text.tsx +25 -0
- package/src/text/types.ts +25 -0
- package/src/tooltip/popup.tsx +1 -1
- package/src/visually-hidden/style.module.css +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { mergeProps, useRender } from '@base-ui/react';
|
|
2
|
+
import { forwardRef } from '@wordpress/element';
|
|
3
|
+
import styles from './style.module.css';
|
|
4
|
+
import type { HeaderProps } from './types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A structural container for the card's heading area, typically containing
|
|
8
|
+
* `Card.Title`.
|
|
9
|
+
*/
|
|
10
|
+
export const Header = forwardRef< HTMLDivElement, HeaderProps >(
|
|
11
|
+
function CardHeader( { render, ...props }, ref ) {
|
|
12
|
+
const element = useRender( {
|
|
13
|
+
defaultTagName: 'div',
|
|
14
|
+
render,
|
|
15
|
+
ref,
|
|
16
|
+
props: mergeProps< 'div' >( { className: styles.header }, props ),
|
|
17
|
+
} );
|
|
18
|
+
|
|
19
|
+
return element;
|
|
20
|
+
}
|
|
21
|
+
);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { mergeProps, useRender } from '@base-ui/react';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import { forwardRef } from '@wordpress/element';
|
|
4
|
+
import resetStyles from '../utils/css/resets.module.css';
|
|
5
|
+
import styles from './style.module.css';
|
|
6
|
+
import type { RootProps } from './types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A visually contained surface that groups related content and actions.
|
|
10
|
+
*
|
|
11
|
+
* ```jsx
|
|
12
|
+
* import { Card } from '@wordpress/ui';
|
|
13
|
+
*
|
|
14
|
+
* function MyComponent() {
|
|
15
|
+
* return (
|
|
16
|
+
* <Card.Root>
|
|
17
|
+
* <Card.Header>
|
|
18
|
+
* <Card.Title>Heading</Card.Title>
|
|
19
|
+
* </Card.Header>
|
|
20
|
+
* <Card.Content>
|
|
21
|
+
* <p>Main content here.</p>
|
|
22
|
+
* </Card.Content>
|
|
23
|
+
* </Card.Root>
|
|
24
|
+
* );
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export const Root = forwardRef< HTMLDivElement, RootProps >( function Card(
|
|
29
|
+
{ render, ...restProps },
|
|
30
|
+
ref
|
|
31
|
+
) {
|
|
32
|
+
const mergedClassName = clsx( styles.root, resetStyles[ 'box-sizing' ] );
|
|
33
|
+
|
|
34
|
+
const element = useRender( {
|
|
35
|
+
defaultTagName: 'div',
|
|
36
|
+
render,
|
|
37
|
+
ref,
|
|
38
|
+
props: mergeProps< 'div' >( { className: mergedClassName }, restProps ),
|
|
39
|
+
} );
|
|
40
|
+
|
|
41
|
+
return element;
|
|
42
|
+
} );
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import * as Card from '../index';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Temporary text component for story examples. This will be replaced by an
|
|
6
|
+
* official DS `<Text />` component once it's available.
|
|
7
|
+
*/
|
|
8
|
+
function Text( { children }: { children: React.ReactNode } ) {
|
|
9
|
+
return (
|
|
10
|
+
<p
|
|
11
|
+
style={ {
|
|
12
|
+
margin: 0,
|
|
13
|
+
fontFamily: [ 'var(--wp', 'ds-font-family-body)' ].join( '' ),
|
|
14
|
+
fontSize: 'var(--wpds-font-size-md)',
|
|
15
|
+
fontWeight: 'var(--wpds-font-weight-regular)',
|
|
16
|
+
lineHeight: 'var(--wpds-font-line-height-sm)',
|
|
17
|
+
textWrap: 'pretty',
|
|
18
|
+
color: 'var(--wpds-color-fg-content-neutral-weak)',
|
|
19
|
+
} }
|
|
20
|
+
>
|
|
21
|
+
{ children }
|
|
22
|
+
</p>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const meta: Meta< typeof Card.Root > = {
|
|
27
|
+
title: 'Design System/Components/Card',
|
|
28
|
+
component: Card.Root,
|
|
29
|
+
subcomponents: {
|
|
30
|
+
'Card.Header': Card.Header,
|
|
31
|
+
'Card.Content': Card.Content,
|
|
32
|
+
'Card.FullBleed': Card.FullBleed,
|
|
33
|
+
'Card.Title': Card.Title,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
export default meta;
|
|
37
|
+
|
|
38
|
+
type Story = StoryObj< typeof Card.Root >;
|
|
39
|
+
|
|
40
|
+
export const Default: Story = {
|
|
41
|
+
args: {
|
|
42
|
+
children: (
|
|
43
|
+
<>
|
|
44
|
+
<Card.Header>
|
|
45
|
+
<Card.Title>Card title</Card.Title>
|
|
46
|
+
</Card.Header>
|
|
47
|
+
<Card.Content>
|
|
48
|
+
<Text>
|
|
49
|
+
This is the main content area. It can contain any
|
|
50
|
+
elements. This is the main content area. It can contain
|
|
51
|
+
any elements. This is the main content area. It can
|
|
52
|
+
contain any elements. This is the main content area. It
|
|
53
|
+
can contain any elements. This is the main content area.
|
|
54
|
+
It can contain any elements. This is the main content
|
|
55
|
+
area. It can contain any elements.
|
|
56
|
+
</Text>
|
|
57
|
+
<Text>
|
|
58
|
+
This is the main content area. It can contain any
|
|
59
|
+
elements.
|
|
60
|
+
</Text>
|
|
61
|
+
</Card.Content>
|
|
62
|
+
</>
|
|
63
|
+
),
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* `Card.FullBleed` breaks out of the card's padding to span
|
|
69
|
+
* edge-to-edge. Useful for images, dividers, or embedded content.
|
|
70
|
+
*/
|
|
71
|
+
export const WithFullBleed: Story = {
|
|
72
|
+
args: {
|
|
73
|
+
children: (
|
|
74
|
+
<>
|
|
75
|
+
<Card.Header>
|
|
76
|
+
<Card.Title>Featured image</Card.Title>
|
|
77
|
+
</Card.Header>
|
|
78
|
+
<Card.Content>
|
|
79
|
+
<Card.FullBleed>
|
|
80
|
+
<div
|
|
81
|
+
style={ {
|
|
82
|
+
height: 160,
|
|
83
|
+
background:
|
|
84
|
+
'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
|
85
|
+
} }
|
|
86
|
+
/>
|
|
87
|
+
</Card.FullBleed>
|
|
88
|
+
<Text>Content below the full-bleed area.</Text>
|
|
89
|
+
</Card.Content>
|
|
90
|
+
</>
|
|
91
|
+
),
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* A minimal card with only a header.
|
|
97
|
+
*/
|
|
98
|
+
export const HeaderOnly: Story = {
|
|
99
|
+
args: {
|
|
100
|
+
children: (
|
|
101
|
+
<Card.Header>
|
|
102
|
+
<Card.Title>Simple card</Card.Title>
|
|
103
|
+
</Card.Header>
|
|
104
|
+
),
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Use the `render` prop to change the underlying HTML elements for
|
|
110
|
+
* better semantics. Here, `Card.Root` renders as a `<section>` and
|
|
111
|
+
* `Card.Title` renders as an `<h2>`.
|
|
112
|
+
*/
|
|
113
|
+
export const CustomSemantics: Story = {
|
|
114
|
+
args: {
|
|
115
|
+
render: <section />,
|
|
116
|
+
children: (
|
|
117
|
+
<>
|
|
118
|
+
<Card.Header>
|
|
119
|
+
{ /* eslint-disable-next-line jsx-a11y/heading-has-content -- content provided via render prop */ }
|
|
120
|
+
<Card.Title render={ <h2 /> }>Section heading</Card.Title>
|
|
121
|
+
</Card.Header>
|
|
122
|
+
<Card.Content>
|
|
123
|
+
<Text>Semantically meaningful card content.</Text>
|
|
124
|
+
</Card.Content>
|
|
125
|
+
</>
|
|
126
|
+
),
|
|
127
|
+
},
|
|
128
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
@layer wp-ui-utilities, wp-ui-components, wp-ui-compositions, wp-ui-overrides;
|
|
2
|
+
|
|
3
|
+
@layer wp-ui-components {
|
|
4
|
+
.root {
|
|
5
|
+
--wp-ui-card-padding: var(--wpds-dimension-padding-xl);
|
|
6
|
+
--wp-ui-card-header-content-gap: var(--wpds-dimension-gap-lg);
|
|
7
|
+
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
border: 1px solid var(--wpds-color-stroke-surface-neutral-weak);
|
|
11
|
+
border-radius: var(--wpds-border-radius-lg);
|
|
12
|
+
background-color: var(--wpds-color-bg-surface-neutral-strong);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* Padding is applied to the individual header/content elements to enable
|
|
16
|
+
* the higher-level `CollapsibleCard` component to be fully clickable
|
|
17
|
+
* to expand/collapse the card.
|
|
18
|
+
*/
|
|
19
|
+
.header,
|
|
20
|
+
.content {
|
|
21
|
+
padding: var(--wp-ui-card-padding);
|
|
22
|
+
|
|
23
|
+
&:not(:first-child):not(:last-child) {
|
|
24
|
+
padding-block-end: 0;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Custom vertical gap between header and content */
|
|
29
|
+
.header + .content {
|
|
30
|
+
padding-block-start: 0;
|
|
31
|
+
margin-block-start: calc(var(--wp-ui-card-header-content-gap) - var(--wp-ui-card-padding));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.fullbleed {
|
|
35
|
+
margin-inline: calc(-1 * var(--wp-ui-card-padding));
|
|
36
|
+
width: calc(100% + 2 * var(--wp-ui-card-padding));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.title {
|
|
40
|
+
margin: 0;
|
|
41
|
+
font-family: var(--wpds-font-family-heading);
|
|
42
|
+
font-size: var(--wpds-font-size-lg);
|
|
43
|
+
font-weight: var(--wpds-font-weight-medium);
|
|
44
|
+
line-height: var(--wpds-font-line-height-sm);
|
|
45
|
+
color: var(--wpds-color-fg-content-neutral);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { createRef } from '@wordpress/element';
|
|
3
|
+
import * as Card from '../index';
|
|
4
|
+
|
|
5
|
+
describe( 'Card', () => {
|
|
6
|
+
describe( 'basic behaviour', () => {
|
|
7
|
+
it( 'forwards ref', () => {
|
|
8
|
+
const rootRef = createRef< HTMLDivElement >();
|
|
9
|
+
const headerRef = createRef< HTMLDivElement >();
|
|
10
|
+
const contentRef = createRef< HTMLDivElement >();
|
|
11
|
+
const fullBleedRef = createRef< HTMLDivElement >();
|
|
12
|
+
const titleRef = createRef< HTMLDivElement >();
|
|
13
|
+
|
|
14
|
+
render(
|
|
15
|
+
<Card.Root ref={ rootRef }>
|
|
16
|
+
<Card.Header ref={ headerRef }>
|
|
17
|
+
<Card.Title ref={ titleRef }>Title</Card.Title>
|
|
18
|
+
</Card.Header>
|
|
19
|
+
<Card.Content ref={ contentRef }>
|
|
20
|
+
<Card.FullBleed ref={ fullBleedRef }>
|
|
21
|
+
Full width
|
|
22
|
+
</Card.FullBleed>
|
|
23
|
+
</Card.Content>
|
|
24
|
+
</Card.Root>
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
expect( rootRef.current ).toBeInstanceOf( HTMLDivElement );
|
|
28
|
+
expect( headerRef.current ).toBeInstanceOf( HTMLDivElement );
|
|
29
|
+
expect( contentRef.current ).toBeInstanceOf( HTMLDivElement );
|
|
30
|
+
expect( fullBleedRef.current ).toBeInstanceOf( HTMLDivElement );
|
|
31
|
+
expect( titleRef.current ).toBeInstanceOf( HTMLDivElement );
|
|
32
|
+
} );
|
|
33
|
+
|
|
34
|
+
it( 'renders content', () => {
|
|
35
|
+
render(
|
|
36
|
+
<Card.Root>
|
|
37
|
+
<Card.Header>
|
|
38
|
+
<Card.Title>Card heading</Card.Title>
|
|
39
|
+
</Card.Header>
|
|
40
|
+
<Card.Content>
|
|
41
|
+
<p>Main content</p>
|
|
42
|
+
</Card.Content>
|
|
43
|
+
</Card.Root>
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
expect( screen.getByText( 'Card heading' ) ).toBeVisible();
|
|
47
|
+
expect( screen.getByText( 'Main content' ) ).toBeVisible();
|
|
48
|
+
} );
|
|
49
|
+
} );
|
|
50
|
+
|
|
51
|
+
describe( 'fullbleed', () => {
|
|
52
|
+
it( 'renders children', () => {
|
|
53
|
+
render(
|
|
54
|
+
<Card.Root>
|
|
55
|
+
<Card.Content>
|
|
56
|
+
<Card.FullBleed>
|
|
57
|
+
<img
|
|
58
|
+
src="https://example.com/image.jpg"
|
|
59
|
+
alt="test"
|
|
60
|
+
/>
|
|
61
|
+
</Card.FullBleed>
|
|
62
|
+
</Card.Content>
|
|
63
|
+
</Card.Root>
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
expect( screen.getByRole( 'img', { name: 'test' } ) ).toBeVisible();
|
|
67
|
+
} );
|
|
68
|
+
} );
|
|
69
|
+
|
|
70
|
+
describe( 'render prop', () => {
|
|
71
|
+
it( 'allows Root to render as a different element', () => {
|
|
72
|
+
render(
|
|
73
|
+
<Card.Root render={ <section /> } data-testid="card">
|
|
74
|
+
<Card.Content>Content</Card.Content>
|
|
75
|
+
</Card.Root>
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
expect( screen.getByTestId( 'card' ).tagName ).toBe( 'SECTION' );
|
|
79
|
+
} );
|
|
80
|
+
|
|
81
|
+
it( 'allows Title to render as a heading element', () => {
|
|
82
|
+
render(
|
|
83
|
+
<Card.Root>
|
|
84
|
+
<Card.Header>
|
|
85
|
+
{ /* eslint-disable-next-line jsx-a11y/heading-has-content -- content provided via render prop */ }
|
|
86
|
+
<Card.Title render={ <h2 /> }>Heading</Card.Title>
|
|
87
|
+
</Card.Header>
|
|
88
|
+
</Card.Root>
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
expect(
|
|
92
|
+
screen.getByRole( 'heading', { level: 2, name: 'Heading' } )
|
|
93
|
+
).toBeVisible();
|
|
94
|
+
} );
|
|
95
|
+
} );
|
|
96
|
+
} );
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { mergeProps, useRender } from '@base-ui/react';
|
|
2
|
+
import { forwardRef } from '@wordpress/element';
|
|
3
|
+
import styles from './style.module.css';
|
|
4
|
+
import type { TitleProps } from './types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The title for a card. Renders as a `<div>` by default — use the `render`
|
|
8
|
+
* prop to swap in a semantic heading element when appropriate.
|
|
9
|
+
*/
|
|
10
|
+
export const Title = forwardRef< HTMLDivElement, TitleProps >(
|
|
11
|
+
function CardTitle( { render, ...props }, ref ) {
|
|
12
|
+
const element = useRender( {
|
|
13
|
+
defaultTagName: 'div',
|
|
14
|
+
render,
|
|
15
|
+
ref,
|
|
16
|
+
// TODO: use `Text` component instead, when ready
|
|
17
|
+
props: mergeProps< 'div' >( { className: styles.title }, props ),
|
|
18
|
+
} );
|
|
19
|
+
|
|
20
|
+
return element;
|
|
21
|
+
}
|
|
22
|
+
);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { ComponentProps } from '../utils/types';
|
|
3
|
+
|
|
4
|
+
export interface RootProps extends ComponentProps< 'div' > {
|
|
5
|
+
/**
|
|
6
|
+
* The content to be rendered inside the card.
|
|
7
|
+
*/
|
|
8
|
+
children?: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface HeaderProps extends ComponentProps< 'div' > {
|
|
12
|
+
/**
|
|
13
|
+
* The content to be rendered inside the header.
|
|
14
|
+
*/
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ContentProps extends ComponentProps< 'div' > {
|
|
19
|
+
/**
|
|
20
|
+
* The content to be rendered inside the content area.
|
|
21
|
+
*/
|
|
22
|
+
children?: ReactNode;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface FullBleedProps extends ComponentProps< 'div' > {
|
|
26
|
+
/**
|
|
27
|
+
* The content to be rendered edge-to-edge, breaking out of the
|
|
28
|
+
* card's padding.
|
|
29
|
+
*/
|
|
30
|
+
children?: ReactNode;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface TitleProps extends ComponentProps< 'div' > {
|
|
34
|
+
/**
|
|
35
|
+
* The title text for the card.
|
|
36
|
+
*/
|
|
37
|
+
children?: ReactNode;
|
|
38
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Collapsible } from '@base-ui/react/collapsible';
|
|
2
|
+
import { forwardRef } from '@wordpress/element';
|
|
3
|
+
import * as Card from '../card';
|
|
4
|
+
import type { ContentProps } from './types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The collapsible content area of the card. Hidden when collapsed,
|
|
8
|
+
* visible when expanded.
|
|
9
|
+
*/
|
|
10
|
+
export const Content = forwardRef< HTMLDivElement, ContentProps >(
|
|
11
|
+
function CollapsibleCardContent( { render, ...restProps }, ref ) {
|
|
12
|
+
return (
|
|
13
|
+
<Collapsible.Panel
|
|
14
|
+
ref={ ref }
|
|
15
|
+
render={ <Card.Content render={ render } /> }
|
|
16
|
+
{ ...restProps }
|
|
17
|
+
/>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Collapsible } from '@base-ui/react/collapsible';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import type { MouseEvent } from 'react';
|
|
4
|
+
import { forwardRef, useCallback, useRef } from '@wordpress/element';
|
|
5
|
+
import { __ } from '@wordpress/i18n';
|
|
6
|
+
import { chevronDown, chevronUp } from '@wordpress/icons';
|
|
7
|
+
import * as Card from '../card';
|
|
8
|
+
import { IconButton } from '../icon-button';
|
|
9
|
+
import styles from './style.module.css';
|
|
10
|
+
import type { HeaderProps } from './types';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The header of a collapsible card. Always visible, and acts as the
|
|
14
|
+
* toggle trigger — clicking anywhere on it expands or collapses the
|
|
15
|
+
* card's content.
|
|
16
|
+
*
|
|
17
|
+
* Avoid placing interactive elements (buttons, links, inputs) inside the
|
|
18
|
+
* header, since the entire area is clickable and their events will bubble
|
|
19
|
+
* to trigger the collapse toggle.
|
|
20
|
+
*/
|
|
21
|
+
export const Header = forwardRef< HTMLDivElement, HeaderProps >(
|
|
22
|
+
function CollapsibleCardHeader(
|
|
23
|
+
{ children, className, onClick, ...restProps },
|
|
24
|
+
ref
|
|
25
|
+
) {
|
|
26
|
+
const triggerRef = useRef< HTMLButtonElement >( null );
|
|
27
|
+
|
|
28
|
+
const handleHeaderClick = useCallback(
|
|
29
|
+
( event: MouseEvent< HTMLDivElement > ) => {
|
|
30
|
+
const trigger = triggerRef.current;
|
|
31
|
+
if (
|
|
32
|
+
trigger &&
|
|
33
|
+
event.target instanceof Node &&
|
|
34
|
+
! trigger.contains( event.target )
|
|
35
|
+
) {
|
|
36
|
+
trigger.click();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
onClick?.( event );
|
|
40
|
+
},
|
|
41
|
+
[ onClick ]
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Card.Header
|
|
46
|
+
ref={ ref }
|
|
47
|
+
className={ clsx( styles.header, className ) }
|
|
48
|
+
onClick={ handleHeaderClick }
|
|
49
|
+
{ ...restProps }
|
|
50
|
+
>
|
|
51
|
+
<div className={ styles[ 'header-content' ] }>{ children }</div>
|
|
52
|
+
<div className={ styles[ 'header-trigger-wrapper' ] }>
|
|
53
|
+
<Collapsible.Trigger
|
|
54
|
+
ref={ triggerRef }
|
|
55
|
+
render={ ( props, state ) => (
|
|
56
|
+
<IconButton
|
|
57
|
+
{ ...props }
|
|
58
|
+
label={ __( 'Expand or collapse card' ) }
|
|
59
|
+
icon={ state.open ? chevronUp : chevronDown }
|
|
60
|
+
variant="minimal"
|
|
61
|
+
tone="neutral"
|
|
62
|
+
size="compact"
|
|
63
|
+
/>
|
|
64
|
+
) }
|
|
65
|
+
className={ styles[ 'header-trigger' ] }
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
</Card.Header>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Collapsible } from '@base-ui/react/collapsible';
|
|
2
|
+
import { forwardRef } from '@wordpress/element';
|
|
3
|
+
import * as Card from '../card';
|
|
4
|
+
import type { RootProps } from './types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A card that can be expanded and collapsed. When collapsed, only the
|
|
8
|
+
* header is visible.
|
|
9
|
+
*
|
|
10
|
+
* ```jsx
|
|
11
|
+
* import { CollapsibleCard, Card } from '@wordpress/ui';
|
|
12
|
+
*
|
|
13
|
+
* function MyComponent() {
|
|
14
|
+
* return (
|
|
15
|
+
* <CollapsibleCard.Root defaultOpen>
|
|
16
|
+
* <CollapsibleCard.Header>
|
|
17
|
+
* <Card.Title>Heading</Card.Title>
|
|
18
|
+
* </CollapsibleCard.Header>
|
|
19
|
+
* <CollapsibleCard.Content>
|
|
20
|
+
* <p>Collapsible content here.</p>
|
|
21
|
+
* </CollapsibleCard.Content>
|
|
22
|
+
* </CollapsibleCard.Root>
|
|
23
|
+
* );
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export const Root = forwardRef< HTMLDivElement, RootProps >(
|
|
28
|
+
function CollapsibleCardRoot( { render, ...restProps }, ref ) {
|
|
29
|
+
return (
|
|
30
|
+
<Collapsible.Root
|
|
31
|
+
ref={ ref }
|
|
32
|
+
render={ <Card.Root render={ render } /> }
|
|
33
|
+
{ ...restProps }
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
);
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import * as Card from '../../card';
|
|
3
|
+
import * as CollapsibleCard from '../index';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Temporary text component for story examples. This will be replaced by an
|
|
7
|
+
* official DS `<Text />` component once it's available.
|
|
8
|
+
*/
|
|
9
|
+
function Text( { children }: { children: React.ReactNode } ) {
|
|
10
|
+
return (
|
|
11
|
+
<p
|
|
12
|
+
style={ {
|
|
13
|
+
margin: 0,
|
|
14
|
+
fontFamily: [ 'var(--wp', 'ds-font-family-body)' ].join( '' ),
|
|
15
|
+
fontSize: 'var(--wpds-font-size-md)',
|
|
16
|
+
fontWeight: 'var(--wpds-font-weight-regular)',
|
|
17
|
+
lineHeight: 'var(--wpds-font-line-height-sm)',
|
|
18
|
+
textWrap: 'pretty',
|
|
19
|
+
color: 'var(--wpds-color-fg-content-neutral-weak)',
|
|
20
|
+
} }
|
|
21
|
+
>
|
|
22
|
+
{ children }
|
|
23
|
+
</p>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const meta: Meta< typeof CollapsibleCard.Root > = {
|
|
28
|
+
title: 'Design System/Components/CollapsibleCard',
|
|
29
|
+
component: CollapsibleCard.Root,
|
|
30
|
+
subcomponents: {
|
|
31
|
+
'CollapsibleCard.Header': CollapsibleCard.Header,
|
|
32
|
+
'CollapsibleCard.Content': CollapsibleCard.Content,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
export default meta;
|
|
36
|
+
|
|
37
|
+
type Story = StoryObj< typeof CollapsibleCard.Root >;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A collapsible card that is open by default.
|
|
41
|
+
*/
|
|
42
|
+
export const Default: Story = {
|
|
43
|
+
args: {
|
|
44
|
+
children: (
|
|
45
|
+
<>
|
|
46
|
+
<CollapsibleCard.Header>
|
|
47
|
+
<Card.Title>
|
|
48
|
+
Collapsible card (closed by default)
|
|
49
|
+
</Card.Title>
|
|
50
|
+
</CollapsibleCard.Header>
|
|
51
|
+
<CollapsibleCard.Content>
|
|
52
|
+
<Text>
|
|
53
|
+
This is the collapsible content area. It can contain any
|
|
54
|
+
elements, just like a regular Card.Content.
|
|
55
|
+
</Text>
|
|
56
|
+
<Text>
|
|
57
|
+
When collapsed, only the header and chevron are visible.
|
|
58
|
+
</Text>
|
|
59
|
+
</CollapsibleCard.Content>
|
|
60
|
+
</>
|
|
61
|
+
),
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* A collapsible card that starts collapsed.
|
|
67
|
+
*/
|
|
68
|
+
export const InitiallyOpened: Story = {
|
|
69
|
+
// `defaultOpen` (uncontrolled) and `open` (controlled) should not be
|
|
70
|
+
// used together — disable the `open` control to avoid confusion.
|
|
71
|
+
argTypes: { open: { control: false } },
|
|
72
|
+
args: {
|
|
73
|
+
...Default.args,
|
|
74
|
+
defaultOpen: true,
|
|
75
|
+
children: (
|
|
76
|
+
<>
|
|
77
|
+
<CollapsibleCard.Header>
|
|
78
|
+
<Card.Title>Collapsed by default</Card.Title>
|
|
79
|
+
</CollapsibleCard.Header>
|
|
80
|
+
<CollapsibleCard.Content>
|
|
81
|
+
<Text>This content was hidden until you expanded it.</Text>
|
|
82
|
+
</CollapsibleCard.Content>
|
|
83
|
+
</>
|
|
84
|
+
),
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* A disabled collapsible card cannot be toggled by the user.
|
|
90
|
+
*/
|
|
91
|
+
export const Disabled: Story = {
|
|
92
|
+
args: {
|
|
93
|
+
...Default.args,
|
|
94
|
+
disabled: true,
|
|
95
|
+
children: (
|
|
96
|
+
<>
|
|
97
|
+
<CollapsibleCard.Header>
|
|
98
|
+
<Card.Title>Disabled card</Card.Title>
|
|
99
|
+
</CollapsibleCard.Header>
|
|
100
|
+
<CollapsibleCard.Content>
|
|
101
|
+
<Text>The header is not interactive when disabled.</Text>
|
|
102
|
+
</CollapsibleCard.Content>
|
|
103
|
+
</>
|
|
104
|
+
),
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Visual comparison: a `CollapsibleCard` (open) next to a regular `Card`
|
|
110
|
+
* to verify identical spacing and layout.
|
|
111
|
+
*/
|
|
112
|
+
export const ComparedToCard: Story = {
|
|
113
|
+
// `defaultOpen` (uncontrolled) and `open` (controlled) should not be
|
|
114
|
+
// used together — disable the `open` control to avoid confusion.
|
|
115
|
+
argTypes: { open: { control: false } },
|
|
116
|
+
args: {
|
|
117
|
+
...Default.args,
|
|
118
|
+
defaultOpen: true,
|
|
119
|
+
},
|
|
120
|
+
render: ( { open, defaultOpen, onOpenChange, disabled, ...restArgs } ) => (
|
|
121
|
+
<div
|
|
122
|
+
style={ {
|
|
123
|
+
display: 'flex',
|
|
124
|
+
flexDirection: 'column',
|
|
125
|
+
gap: 'var( --wpds-dimension-gap-lg )',
|
|
126
|
+
} }
|
|
127
|
+
>
|
|
128
|
+
<CollapsibleCard.Root
|
|
129
|
+
open={ open }
|
|
130
|
+
defaultOpen={ defaultOpen }
|
|
131
|
+
onOpenChange={ onOpenChange }
|
|
132
|
+
disabled={ disabled }
|
|
133
|
+
{ ...restArgs }
|
|
134
|
+
>
|
|
135
|
+
<CollapsibleCard.Header>
|
|
136
|
+
<Card.Title>CollapsibleCard (open)</Card.Title>
|
|
137
|
+
</CollapsibleCard.Header>
|
|
138
|
+
<CollapsibleCard.Content>
|
|
139
|
+
<Text>
|
|
140
|
+
Content should align with the regular card below.
|
|
141
|
+
</Text>
|
|
142
|
+
</CollapsibleCard.Content>
|
|
143
|
+
</CollapsibleCard.Root>
|
|
144
|
+
<Card.Root { ...restArgs }>
|
|
145
|
+
<Card.Header>
|
|
146
|
+
<Card.Title>Regular Card</Card.Title>
|
|
147
|
+
</Card.Header>
|
|
148
|
+
<Card.Content>
|
|
149
|
+
<Text>
|
|
150
|
+
Content should align with the collapsible card above.
|
|
151
|
+
</Text>
|
|
152
|
+
</Card.Content>
|
|
153
|
+
</Card.Root>
|
|
154
|
+
</div>
|
|
155
|
+
),
|
|
156
|
+
};
|