@wordpress/ui 0.6.1-next.v.0 → 0.7.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/AGENTS.md +9 -0
- package/CHANGELOG.md +32 -1
- package/CLAUDE.md +1 -0
- package/README.md +13 -12
- package/build/badge/badge.cjs +37 -62
- package/build/badge/badge.cjs.map +4 -4
- package/build/button/button.cjs +3 -3
- package/build/button/button.cjs.map +2 -2
- package/build/dialog/action.cjs +46 -0
- package/build/dialog/action.cjs.map +7 -0
- package/build/dialog/close-icon.cjs +57 -0
- package/build/dialog/close-icon.cjs.map +7 -0
- package/build/dialog/context.cjs +76 -0
- package/build/dialog/context.cjs.map +7 -0
- package/build/dialog/footer.cjs +64 -0
- package/build/dialog/footer.cjs.map +7 -0
- package/build/dialog/header.cjs +64 -0
- package/build/dialog/header.cjs.map +7 -0
- package/build/dialog/index.cjs +52 -0
- package/build/dialog/index.cjs.map +7 -0
- package/build/dialog/popup.cjs +77 -0
- package/build/dialog/popup.cjs.map +7 -0
- package/build/dialog/root.cjs +35 -0
- package/build/dialog/root.cjs.map +7 -0
- package/build/dialog/title.cjs +76 -0
- package/build/dialog/title.cjs.map +7 -0
- package/build/dialog/trigger.cjs +38 -0
- package/build/dialog/trigger.cjs.map +7 -0
- package/build/dialog/types.cjs +19 -0
- package/build/dialog/types.cjs.map +7 -0
- package/build/form/primitives/field/root.cjs +1 -1
- package/build/form/primitives/field/root.cjs.map +1 -1
- package/build/form/primitives/fieldset/root.cjs +3 -3
- package/build/form/primitives/fieldset/root.cjs.map +2 -2
- package/build/form/primitives/index.cjs +5 -2
- package/build/form/primitives/index.cjs.map +2 -2
- package/build/form/primitives/input-layout/input-layout.cjs +3 -3
- package/build/form/primitives/input-layout/input-layout.cjs.map +2 -2
- package/build/form/primitives/input-layout/slot.cjs +3 -3
- package/build/form/primitives/input-layout/slot.cjs.map +2 -2
- package/build/form/primitives/select/item.cjs +3 -3
- package/build/form/primitives/select/item.cjs.map +2 -2
- package/build/form/primitives/select/popup.cjs +3 -3
- package/build/form/primitives/select/popup.cjs.map +2 -2
- package/build/form/primitives/select/trigger.cjs +3 -3
- package/build/form/primitives/select/trigger.cjs.map +2 -2
- package/build/{box → form/primitives/textarea}/index.cjs +7 -7
- package/build/form/primitives/textarea/index.cjs.map +7 -0
- package/build/form/primitives/textarea/textarea.cjs +90 -0
- package/build/form/primitives/textarea/textarea.cjs.map +7 -0
- package/build/form/primitives/textarea/types.cjs +19 -0
- package/build/form/primitives/textarea/types.cjs.map +7 -0
- package/build/icon-button/icon-button.cjs +104 -0
- package/build/icon-button/icon-button.cjs.map +7 -0
- package/build/icon-button/index.cjs +31 -0
- package/build/icon-button/index.cjs.map +7 -0
- package/build/icon-button/types.cjs +19 -0
- package/build/icon-button/types.cjs.map +7 -0
- package/build/index.cjs +8 -2
- package/build/index.cjs.map +2 -2
- package/build/tabs/index.cjs +40 -0
- package/build/tabs/index.cjs.map +7 -0
- package/build/tabs/list.cjs +145 -0
- package/build/tabs/list.cjs.map +7 -0
- package/build/tabs/panel.cjs +67 -0
- package/build/tabs/panel.cjs.map +7 -0
- package/build/tabs/root.cjs +38 -0
- package/build/tabs/root.cjs.map +7 -0
- package/build/tabs/tab.cjs +71 -0
- package/build/tabs/tab.cjs.map +7 -0
- package/build/{box → tabs}/types.cjs +1 -1
- package/build/tabs/types.cjs.map +7 -0
- package/build/tooltip/popup.cjs +3 -3
- package/build/tooltip/popup.cjs.map +2 -2
- package/build-module/badge/badge.mjs +27 -62
- package/build-module/badge/badge.mjs.map +3 -3
- package/build-module/button/button.mjs +3 -3
- package/build-module/button/button.mjs.map +2 -2
- package/build-module/dialog/action.mjs +21 -0
- package/build-module/dialog/action.mjs.map +7 -0
- package/build-module/dialog/close-icon.mjs +32 -0
- package/build-module/dialog/close-icon.mjs.map +7 -0
- package/build-module/dialog/context.mjs +57 -0
- package/build-module/dialog/context.mjs.map +7 -0
- package/build-module/dialog/footer.mjs +29 -0
- package/build-module/dialog/footer.mjs.map +7 -0
- package/build-module/dialog/header.mjs +29 -0
- package/build-module/dialog/header.mjs.map +7 -0
- package/build-module/dialog/index.mjs +20 -0
- package/build-module/dialog/index.mjs.map +7 -0
- package/build-module/dialog/popup.mjs +44 -0
- package/build-module/dialog/popup.mjs.map +7 -0
- package/build-module/dialog/root.mjs +10 -0
- package/build-module/dialog/root.mjs.map +7 -0
- package/build-module/dialog/title.mjs +41 -0
- package/build-module/dialog/title.mjs.map +7 -0
- package/build-module/dialog/trigger.mjs +13 -0
- package/build-module/dialog/trigger.mjs.map +7 -0
- package/build-module/form/primitives/field/root.mjs +1 -1
- package/build-module/form/primitives/field/root.mjs.map +1 -1
- package/build-module/form/primitives/fieldset/root.mjs +3 -3
- package/build-module/form/primitives/fieldset/root.mjs.map +2 -2
- package/build-module/form/primitives/index.mjs +3 -1
- package/build-module/form/primitives/index.mjs.map +2 -2
- package/build-module/form/primitives/input-layout/input-layout.mjs +3 -3
- package/build-module/form/primitives/input-layout/input-layout.mjs.map +2 -2
- package/build-module/form/primitives/input-layout/slot.mjs +3 -3
- package/build-module/form/primitives/input-layout/slot.mjs.map +2 -2
- package/build-module/form/primitives/select/item.mjs +3 -3
- package/build-module/form/primitives/select/item.mjs.map +2 -2
- package/build-module/form/primitives/select/popup.mjs +3 -3
- package/build-module/form/primitives/select/popup.mjs.map +2 -2
- package/build-module/form/primitives/select/trigger.mjs +3 -3
- package/build-module/form/primitives/select/trigger.mjs.map +2 -2
- package/build-module/form/primitives/textarea/index.mjs +6 -0
- package/build-module/form/primitives/textarea/index.mjs.map +7 -0
- package/build-module/form/primitives/textarea/textarea.mjs +55 -0
- package/build-module/form/primitives/textarea/textarea.mjs.map +7 -0
- package/build-module/form/primitives/textarea/types.mjs +1 -0
- package/build-module/form/primitives/textarea/types.mjs.map +7 -0
- package/build-module/icon-button/icon-button.mjs +69 -0
- package/build-module/icon-button/icon-button.mjs.map +7 -0
- package/build-module/icon-button/index.mjs +6 -0
- package/build-module/icon-button/index.mjs.map +7 -0
- package/build-module/icon-button/types.mjs +1 -0
- package/build-module/icon-button/types.mjs.map +7 -0
- package/build-module/index.mjs +5 -1
- package/build-module/index.mjs.map +2 -2
- package/build-module/tabs/index.mjs +12 -0
- package/build-module/tabs/index.mjs.map +7 -0
- package/build-module/tabs/list.mjs +110 -0
- package/build-module/tabs/list.mjs.map +7 -0
- package/build-module/tabs/panel.mjs +32 -0
- package/build-module/tabs/panel.mjs.map +7 -0
- package/build-module/tabs/root.mjs +13 -0
- package/build-module/tabs/root.mjs.map +7 -0
- package/build-module/tabs/tab.mjs +36 -0
- package/build-module/tabs/tab.mjs.map +7 -0
- package/build-module/tabs/types.mjs +1 -0
- package/build-module/tabs/types.mjs.map +7 -0
- package/build-module/tooltip/popup.mjs +3 -3
- package/build-module/tooltip/popup.mjs.map +2 -2
- package/build-types/badge/badge.d.ts +1 -2
- package/build-types/badge/badge.d.ts.map +1 -1
- package/build-types/button/stories/index.story.d.ts +1 -2
- package/build-types/button/stories/index.story.d.ts.map +1 -1
- package/build-types/dialog/action.d.ts +8 -0
- package/build-types/dialog/action.d.ts.map +1 -0
- package/build-types/dialog/close-icon.d.ts +8 -0
- package/build-types/dialog/close-icon.d.ts.map +1 -0
- package/build-types/dialog/context.d.ts +25 -0
- package/build-types/dialog/context.d.ts.map +1 -0
- package/build-types/dialog/footer.d.ts +8 -0
- package/build-types/dialog/footer.d.ts.map +1 -0
- package/build-types/dialog/header.d.ts +8 -0
- package/build-types/dialog/header.d.ts.map +1 -0
- package/build-types/dialog/index.d.ts +10 -0
- package/build-types/dialog/index.d.ts.map +1 -0
- package/build-types/dialog/popup.d.ts +8 -0
- package/build-types/dialog/popup.d.ts.map +1 -0
- package/build-types/dialog/root.d.ts +10 -0
- package/build-types/dialog/root.d.ts.map +1 -0
- package/build-types/dialog/stories/index.story.d.ts +18 -0
- package/build-types/dialog/stories/index.story.d.ts.map +1 -0
- package/build-types/dialog/test/index.test.d.ts +2 -0
- package/build-types/dialog/test/index.test.d.ts.map +1 -0
- package/build-types/dialog/title.d.ts +12 -0
- package/build-types/dialog/title.d.ts.map +1 -0
- package/build-types/dialog/trigger.d.ts +7 -0
- package/build-types/dialog/trigger.d.ts.map +1 -0
- package/build-types/dialog/types.d.ts +77 -0
- package/build-types/dialog/types.d.ts.map +1 -0
- package/build-types/form/primitives/field/stories/index.story.d.ts +0 -1
- package/build-types/form/primitives/field/stories/index.story.d.ts.map +1 -1
- package/build-types/form/primitives/index.d.ts +1 -0
- package/build-types/form/primitives/index.d.ts.map +1 -1
- package/build-types/form/primitives/input/input.d.ts +1 -1
- package/build-types/form/primitives/select/stories/index.story.d.ts +0 -1
- package/build-types/form/primitives/select/stories/index.story.d.ts.map +1 -1
- package/build-types/form/primitives/textarea/index.d.ts +2 -0
- package/build-types/form/primitives/textarea/index.d.ts.map +1 -0
- package/build-types/form/primitives/textarea/stories/index.story.d.ts +13 -0
- package/build-types/form/primitives/textarea/stories/index.story.d.ts.map +1 -0
- package/build-types/form/primitives/textarea/test/index.test.d.ts +2 -0
- package/build-types/form/primitives/textarea/test/index.test.d.ts.map +1 -0
- package/build-types/form/primitives/textarea/textarea.d.ts +4 -0
- package/build-types/form/primitives/textarea/textarea.d.ts.map +1 -0
- package/build-types/form/primitives/textarea/types.d.ts +11 -0
- package/build-types/form/primitives/textarea/types.d.ts.map +1 -0
- package/build-types/icon-button/icon-button.d.ts +13 -0
- package/build-types/icon-button/icon-button.d.ts.map +1 -0
- package/build-types/icon-button/index.d.ts +2 -0
- package/build-types/icon-button/index.d.ts.map +1 -0
- package/build-types/icon-button/stories/index.story.d.ts +19 -0
- package/build-types/icon-button/stories/index.story.d.ts.map +1 -0
- package/build-types/icon-button/test/index.test.d.ts +2 -0
- package/build-types/icon-button/test/index.test.d.ts.map +1 -0
- package/build-types/icon-button/types.d.ts +36 -0
- package/build-types/icon-button/types.d.ts.map +1 -0
- package/build-types/index.d.ts +3 -1
- package/build-types/index.d.ts.map +1 -1
- package/build-types/stack/stories/index.story.d.ts.map +1 -1
- package/build-types/tabs/index.d.ts +6 -0
- package/build-types/tabs/index.d.ts.map +1 -0
- package/build-types/tabs/list.d.ts +16 -0
- package/build-types/tabs/list.d.ts.map +1 -0
- package/build-types/tabs/panel.d.ts +15 -0
- package/build-types/tabs/panel.d.ts.map +1 -0
- package/build-types/tabs/root.d.ts +15 -0
- package/build-types/tabs/root.d.ts.map +1 -0
- package/build-types/tabs/stories/index.story.d.ts +13 -0
- package/build-types/tabs/stories/index.story.d.ts.map +1 -0
- package/build-types/tabs/tab.d.ts +15 -0
- package/build-types/tabs/tab.d.ts.map +1 -0
- package/build-types/tabs/test/index.test.d.ts +2 -0
- package/build-types/tabs/test/index.test.d.ts.map +1 -0
- package/build-types/tabs/types.d.ts +33 -0
- package/build-types/tabs/types.d.ts.map +1 -0
- package/package.json +12 -10
- package/src/badge/badge.tsx +19 -78
- package/src/badge/stories/choosing-intent.story.tsx +1 -1
- package/src/badge/style.module.css +48 -0
- package/src/button/stories/index.story.tsx +3 -16
- package/src/button/style.module.css +23 -12
- package/src/dialog/action.tsx +22 -0
- package/src/dialog/close-icon.tsx +32 -0
- package/src/dialog/context.tsx +113 -0
- package/src/dialog/footer.tsx +26 -0
- package/src/dialog/header.tsx +26 -0
- package/src/dialog/index.ts +10 -0
- package/src/dialog/popup.tsx +46 -0
- package/src/dialog/root.tsx +14 -0
- package/src/dialog/stories/index.story.tsx +177 -0
- package/src/dialog/style.module.css +114 -0
- package/src/dialog/test/index.test.tsx +309 -0
- package/src/dialog/title.tsx +39 -0
- package/src/dialog/trigger.tsx +14 -0
- package/src/dialog/types.ts +93 -0
- package/src/form/primitives/field/root.tsx +1 -1
- package/src/form/primitives/field/stories/index.story.tsx +0 -1
- package/src/form/primitives/fieldset/style.module.css +1 -1
- package/src/form/primitives/index.ts +1 -0
- package/src/form/primitives/input-layout/style.module.css +5 -8
- package/src/form/primitives/select/stories/index.story.tsx +0 -1
- package/src/form/primitives/select/test/index.test.tsx +0 -2
- package/src/form/primitives/textarea/index.ts +1 -0
- package/src/form/primitives/textarea/stories/index.story.tsx +40 -0
- package/src/form/primitives/textarea/style.module.css +22 -0
- package/src/form/primitives/textarea/test/index.test.tsx +143 -0
- package/src/form/primitives/textarea/textarea.tsx +51 -0
- package/src/form/primitives/textarea/types.ts +18 -0
- package/src/icon-button/icon-button.tsx +65 -0
- package/src/icon-button/index.ts +1 -0
- package/src/icon-button/stories/index.story.tsx +128 -0
- package/src/icon-button/style.module.css +16 -0
- package/src/icon-button/test/index.test.tsx +86 -0
- package/src/icon-button/types.ts +38 -0
- package/src/index.ts +3 -1
- package/src/stack/stories/index.story.tsx +4 -5
- package/src/tabs/index.ts +6 -0
- package/src/tabs/list.tsx +130 -0
- package/src/tabs/panel.tsx +23 -0
- package/src/tabs/root.tsx +15 -0
- package/src/tabs/stories/best-practices.mdx +85 -0
- package/src/tabs/stories/index.story.tsx +363 -0
- package/src/tabs/style.module.css +269 -0
- package/src/tabs/tab.tsx +29 -0
- package/src/tabs/test/index.test.tsx +2260 -0
- package/src/tabs/types.ts +36 -0
- package/src/tooltip/style.module.css +3 -3
- package/src/utils/css/item-popup.module.css +2 -2
- package/src/utils/css/select-trigger.module.css +1 -1
- package/build/box/box.cjs +0 -88
- package/build/box/box.cjs.map +0 -7
- package/build/box/index.cjs.map +0 -7
- package/build/box/types.cjs.map +0 -7
- package/build-module/box/box.mjs +0 -63
- package/build-module/box/box.mjs.map +0 -7
- package/build-module/box/index.mjs +0 -6
- package/build-module/box/index.mjs.map +0 -7
- package/build-types/box/box.d.ts +0 -7
- package/build-types/box/box.d.ts.map +0 -1
- package/build-types/box/index.d.ts +0 -2
- package/build-types/box/index.d.ts.map +0 -1
- package/build-types/box/stories/index.story.d.ts +0 -8
- package/build-types/box/stories/index.story.d.ts.map +0 -1
- package/build-types/box/test/box.test.d.ts +0 -2
- package/build-types/box/test/box.test.d.ts.map +0 -1
- package/build-types/box/types.d.ts +0 -46
- package/build-types/box/types.d.ts.map +0 -1
- package/src/box/box.tsx +0 -118
- package/src/box/index.ts +0 -1
- package/src/box/stories/index.story.tsx +0 -41
- package/src/box/test/box.test.tsx +0 -29
- package/src/box/types.ts +0 -61
- /package/build-module/{box → dialog}/types.mjs +0 -0
- /package/build-module/{box → dialog}/types.mjs.map +0 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { createRef, useState } from '@wordpress/element';
|
|
4
|
+
import { Textarea } from '../index';
|
|
5
|
+
|
|
6
|
+
describe( 'Textarea', () => {
|
|
7
|
+
it( 'forwards ref', () => {
|
|
8
|
+
const ref = createRef< HTMLTextAreaElement >();
|
|
9
|
+
|
|
10
|
+
render( <Textarea ref={ ref } /> );
|
|
11
|
+
|
|
12
|
+
expect( ref.current ).toBeInstanceOf( HTMLTextAreaElement );
|
|
13
|
+
} );
|
|
14
|
+
|
|
15
|
+
describe( 'value prop', () => {
|
|
16
|
+
it( 'renders with controlled value', () => {
|
|
17
|
+
render( <Textarea value="Hello, world!" /> );
|
|
18
|
+
|
|
19
|
+
const textarea = screen.getByRole( 'textbox' );
|
|
20
|
+
expect( textarea ).toHaveValue( 'Hello, world!' );
|
|
21
|
+
} );
|
|
22
|
+
} );
|
|
23
|
+
|
|
24
|
+
describe( 'defaultValue prop', () => {
|
|
25
|
+
it( 'renders with default value', () => {
|
|
26
|
+
render( <Textarea defaultValue="Default content" /> );
|
|
27
|
+
|
|
28
|
+
const textarea = screen.getByRole( 'textbox' );
|
|
29
|
+
expect( textarea ).toHaveValue( 'Default content' );
|
|
30
|
+
} );
|
|
31
|
+
|
|
32
|
+
it( 'allows user to modify uncontrolled value', async () => {
|
|
33
|
+
const user = userEvent.setup();
|
|
34
|
+
render( <Textarea defaultValue="Default content" /> );
|
|
35
|
+
|
|
36
|
+
const textarea = screen.getByRole( 'textbox' );
|
|
37
|
+
expect( textarea ).toHaveValue( 'Default content' );
|
|
38
|
+
|
|
39
|
+
// Clear and type new content
|
|
40
|
+
await user.clear( textarea );
|
|
41
|
+
await user.type( textarea, 'New content' );
|
|
42
|
+
|
|
43
|
+
expect( textarea ).toHaveValue( 'New content' );
|
|
44
|
+
} );
|
|
45
|
+
} );
|
|
46
|
+
|
|
47
|
+
describe( 'onValueChange prop', () => {
|
|
48
|
+
it( 'calls onValueChange when user types', async () => {
|
|
49
|
+
const user = userEvent.setup();
|
|
50
|
+
const handleValueChange = jest.fn();
|
|
51
|
+
|
|
52
|
+
render(
|
|
53
|
+
<Textarea defaultValue="" onValueChange={ handleValueChange } />
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const textarea = screen.getByRole( 'textbox' );
|
|
57
|
+
|
|
58
|
+
await user.type( textarea, 'Hello' );
|
|
59
|
+
|
|
60
|
+
expect( handleValueChange ).toHaveBeenLastCalledWith(
|
|
61
|
+
'Hello',
|
|
62
|
+
expect.any( Object )
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
await user.clear( textarea );
|
|
66
|
+
|
|
67
|
+
expect( handleValueChange ).toHaveBeenLastCalledWith(
|
|
68
|
+
'',
|
|
69
|
+
expect.any( Object )
|
|
70
|
+
);
|
|
71
|
+
} );
|
|
72
|
+
|
|
73
|
+
it( 'works with controlled component pattern', async () => {
|
|
74
|
+
const user = userEvent.setup();
|
|
75
|
+
const handleValueChange = jest.fn();
|
|
76
|
+
|
|
77
|
+
const ControlledTextarea = () => {
|
|
78
|
+
const [ value, setValue ] = useState( 'Initial' );
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<Textarea
|
|
82
|
+
value={ value }
|
|
83
|
+
onValueChange={ ( newValue ) => {
|
|
84
|
+
handleValueChange( newValue );
|
|
85
|
+
setValue( newValue );
|
|
86
|
+
} }
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
render( <ControlledTextarea /> );
|
|
92
|
+
|
|
93
|
+
const textarea = screen.getByRole( 'textbox' );
|
|
94
|
+
|
|
95
|
+
await user.clear( textarea );
|
|
96
|
+
await user.type( textarea, 'Updated' );
|
|
97
|
+
|
|
98
|
+
expect( handleValueChange ).toHaveBeenLastCalledWith( 'Updated' );
|
|
99
|
+
} );
|
|
100
|
+
} );
|
|
101
|
+
|
|
102
|
+
describe( 'render prop', () => {
|
|
103
|
+
it( 'correctly merges props with custom render function', () => {
|
|
104
|
+
render(
|
|
105
|
+
<Textarea
|
|
106
|
+
render={ ( props ) => (
|
|
107
|
+
<div data-testid="my-render" { ...props } />
|
|
108
|
+
) }
|
|
109
|
+
data-my-attribute
|
|
110
|
+
/>
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
expect( screen.getByTestId( 'my-render' ) ).toHaveAttribute(
|
|
114
|
+
'data-my-attribute'
|
|
115
|
+
);
|
|
116
|
+
} );
|
|
117
|
+
|
|
118
|
+
it( 'correctly merges props with custom render element', () => {
|
|
119
|
+
render(
|
|
120
|
+
<Textarea
|
|
121
|
+
render={ <div data-testid="my-render" /> }
|
|
122
|
+
data-my-attribute
|
|
123
|
+
/>
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
expect( screen.getByTestId( 'my-render' ) ).toHaveAttribute(
|
|
127
|
+
'data-my-attribute'
|
|
128
|
+
);
|
|
129
|
+
} );
|
|
130
|
+
} );
|
|
131
|
+
|
|
132
|
+
it( 'disables the textarea when disabled prop is true', () => {
|
|
133
|
+
render( <Textarea disabled /> );
|
|
134
|
+
|
|
135
|
+
expect( screen.getByRole( 'textbox' ) ).toBeDisabled();
|
|
136
|
+
} );
|
|
137
|
+
|
|
138
|
+
it( 'applies custom rows value', () => {
|
|
139
|
+
render( <Textarea rows={ 10 } /> );
|
|
140
|
+
|
|
141
|
+
expect( screen.getByRole( 'textbox' ) ).toHaveAttribute( 'rows', '10' );
|
|
142
|
+
} );
|
|
143
|
+
} );
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { mergeProps } from '@base-ui/react';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import { cloneElement, forwardRef } from '@wordpress/element';
|
|
4
|
+
import styles from './style.module.css';
|
|
5
|
+
import type { TextareaProps } from './types';
|
|
6
|
+
import { Input } from '../input';
|
|
7
|
+
|
|
8
|
+
const wrappedRender = (
|
|
9
|
+
render: NonNullable< TextareaProps[ 'render' ] >,
|
|
10
|
+
restProps: TextareaProps & { ref: React.Ref< HTMLTextAreaElement > }
|
|
11
|
+
) => {
|
|
12
|
+
return function Render(
|
|
13
|
+
props: React.HTMLAttributes< HTMLTextAreaElement >
|
|
14
|
+
) {
|
|
15
|
+
return typeof render === 'function'
|
|
16
|
+
? render( mergeProps( props, restProps ) )
|
|
17
|
+
: cloneElement( render, mergeProps( props, restProps ) );
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const Textarea = forwardRef< HTMLTextAreaElement, TextareaProps >(
|
|
22
|
+
function Textarea(
|
|
23
|
+
{
|
|
24
|
+
className,
|
|
25
|
+
defaultValue,
|
|
26
|
+
disabled,
|
|
27
|
+
onValueChange,
|
|
28
|
+
render,
|
|
29
|
+
rows = 4,
|
|
30
|
+
style,
|
|
31
|
+
value,
|
|
32
|
+
...restProps
|
|
33
|
+
},
|
|
34
|
+
ref
|
|
35
|
+
) {
|
|
36
|
+
return (
|
|
37
|
+
<Input
|
|
38
|
+
className={ clsx( styles.wrapper, className ) }
|
|
39
|
+
style={ style }
|
|
40
|
+
render={ wrappedRender(
|
|
41
|
+
render || ( ( props ) => <textarea { ...props } /> ),
|
|
42
|
+
{ className: styles.textarea, ref, rows, ...restProps }
|
|
43
|
+
) }
|
|
44
|
+
value={ value }
|
|
45
|
+
defaultValue={ defaultValue }
|
|
46
|
+
onValueChange={ onValueChange }
|
|
47
|
+
disabled={ disabled }
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { InputProps } from '../input/types';
|
|
2
|
+
import type { ComponentProps } from '../../../utils/types';
|
|
3
|
+
|
|
4
|
+
export type TextareaProps = Omit<
|
|
5
|
+
ComponentProps< 'textarea' >,
|
|
6
|
+
'disabled' | 'rows' | 'value' | 'defaultValue'
|
|
7
|
+
> &
|
|
8
|
+
Pick<
|
|
9
|
+
InputProps,
|
|
10
|
+
'value' | 'defaultValue' | 'onValueChange' | 'disabled'
|
|
11
|
+
> & {
|
|
12
|
+
/**
|
|
13
|
+
* The number of rows the textarea should contain.
|
|
14
|
+
*
|
|
15
|
+
* @default 4
|
|
16
|
+
*/
|
|
17
|
+
rows?: React.ComponentProps< 'textarea' >[ 'rows' ];
|
|
18
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import { forwardRef } from '@wordpress/element';
|
|
3
|
+
import { Button } from '../button';
|
|
4
|
+
import { Icon } from '../icon';
|
|
5
|
+
import * as Tooltip from '../tooltip';
|
|
6
|
+
import styles from './style.module.css';
|
|
7
|
+
import { type IconButtonProps } from './types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* An icon-only button with automatic tooltip and optimized styling.
|
|
11
|
+
* Inherits all Button props while providing icon-specific enhancements.
|
|
12
|
+
*/
|
|
13
|
+
export const IconButton = forwardRef< HTMLButtonElement, IconButtonProps >(
|
|
14
|
+
function IconButton(
|
|
15
|
+
{
|
|
16
|
+
label,
|
|
17
|
+
className,
|
|
18
|
+
// Prevent accidental forwarding of `children`
|
|
19
|
+
children: _children,
|
|
20
|
+
icon,
|
|
21
|
+
size,
|
|
22
|
+
shortcut,
|
|
23
|
+
...restProps
|
|
24
|
+
}: IconButtonProps & { children?: unknown },
|
|
25
|
+
ref
|
|
26
|
+
) {
|
|
27
|
+
const classes = clsx( styles[ 'icon-button' ], className );
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<Tooltip.Provider delay={ 0 }>
|
|
31
|
+
<Tooltip.Root>
|
|
32
|
+
<Tooltip.Trigger
|
|
33
|
+
ref={ ref }
|
|
34
|
+
render={
|
|
35
|
+
<Button
|
|
36
|
+
{ ...restProps }
|
|
37
|
+
size={ size }
|
|
38
|
+
aria-label={ label }
|
|
39
|
+
aria-keyshortcuts={ shortcut?.ariaKeyShortcut }
|
|
40
|
+
/>
|
|
41
|
+
}
|
|
42
|
+
className={ classes }
|
|
43
|
+
>
|
|
44
|
+
<Icon
|
|
45
|
+
icon={ icon }
|
|
46
|
+
size={ 24 }
|
|
47
|
+
className={ styles.icon }
|
|
48
|
+
/>
|
|
49
|
+
</Tooltip.Trigger>
|
|
50
|
+
<Tooltip.Popup>
|
|
51
|
+
{ label }
|
|
52
|
+
{ shortcut && (
|
|
53
|
+
<>
|
|
54
|
+
{ ' ' }
|
|
55
|
+
<span aria-hidden="true">
|
|
56
|
+
{ shortcut.displayShortcut }
|
|
57
|
+
</span>
|
|
58
|
+
</>
|
|
59
|
+
) }
|
|
60
|
+
</Tooltip.Popup>
|
|
61
|
+
</Tooltip.Root>
|
|
62
|
+
</Tooltip.Provider>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { IconButton } from './icon-button';
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import {
|
|
3
|
+
cog,
|
|
4
|
+
copy,
|
|
5
|
+
download,
|
|
6
|
+
pencil,
|
|
7
|
+
plus,
|
|
8
|
+
trash,
|
|
9
|
+
upload,
|
|
10
|
+
wordpress,
|
|
11
|
+
} from '@wordpress/icons';
|
|
12
|
+
import { displayShortcut, ariaKeyShortcut } from '@wordpress/keycodes';
|
|
13
|
+
import { IconButton } from '../index';
|
|
14
|
+
|
|
15
|
+
const meta: Meta< typeof IconButton > = {
|
|
16
|
+
title: 'Design System/Components/IconButton',
|
|
17
|
+
component: IconButton,
|
|
18
|
+
argTypes: {
|
|
19
|
+
'aria-pressed': {
|
|
20
|
+
control: { type: 'boolean' },
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
export default meta;
|
|
25
|
+
|
|
26
|
+
type Story = StoryObj< typeof IconButton >;
|
|
27
|
+
|
|
28
|
+
export const Default: Story = {
|
|
29
|
+
args: {
|
|
30
|
+
icon: cog,
|
|
31
|
+
label: 'Settings',
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const Outline: Story = {
|
|
36
|
+
...Default,
|
|
37
|
+
args: {
|
|
38
|
+
...Default.args,
|
|
39
|
+
variant: 'outline',
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const Minimal: Story = {
|
|
44
|
+
...Default,
|
|
45
|
+
args: {
|
|
46
|
+
...Default.args,
|
|
47
|
+
variant: 'minimal',
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const Neutral: Story = {
|
|
52
|
+
...Default,
|
|
53
|
+
args: {
|
|
54
|
+
...Default.args,
|
|
55
|
+
tone: 'neutral',
|
|
56
|
+
label: 'Settings',
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const NeutralOutline: Story = {
|
|
61
|
+
...Default,
|
|
62
|
+
args: {
|
|
63
|
+
...Default.args,
|
|
64
|
+
tone: 'neutral',
|
|
65
|
+
variant: 'outline',
|
|
66
|
+
label: 'Settings',
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const Disabled: Story = {
|
|
71
|
+
...Default,
|
|
72
|
+
args: {
|
|
73
|
+
...Default.args,
|
|
74
|
+
disabled: true,
|
|
75
|
+
label: 'Settings',
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const WithDifferentIcons: Story = {
|
|
80
|
+
...Default,
|
|
81
|
+
render: ( args ) => (
|
|
82
|
+
<div
|
|
83
|
+
style={ {
|
|
84
|
+
display: 'flex',
|
|
85
|
+
gap: '1rem',
|
|
86
|
+
alignItems: 'center',
|
|
87
|
+
flexWrap: 'wrap',
|
|
88
|
+
} }
|
|
89
|
+
>
|
|
90
|
+
<IconButton { ...args } icon={ wordpress } label="WordPress" />
|
|
91
|
+
<IconButton { ...args } icon={ plus } label="Add" />
|
|
92
|
+
<IconButton { ...args } icon={ pencil } label="Edit" />
|
|
93
|
+
<IconButton { ...args } icon={ trash } label="Delete" />
|
|
94
|
+
<IconButton { ...args } icon={ download } label="Download" />
|
|
95
|
+
<IconButton { ...args } icon={ upload } label="Upload" />
|
|
96
|
+
</div>
|
|
97
|
+
),
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The pressed state is only available for buttons with `tone="neutral"` and
|
|
102
|
+
* `variant="minimal"` and can be toggled via the `aria-pressed` HTML attribute.
|
|
103
|
+
*/
|
|
104
|
+
export const Pressed: Story = {
|
|
105
|
+
...Default,
|
|
106
|
+
args: {
|
|
107
|
+
...Default.args,
|
|
108
|
+
tone: 'neutral',
|
|
109
|
+
variant: 'minimal',
|
|
110
|
+
label: 'Toggle Settings',
|
|
111
|
+
'aria-pressed': true,
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const EXAMPLE_SHORTCUT_OBJECT = {
|
|
116
|
+
displayShortcut: displayShortcut.primary( 'c' ),
|
|
117
|
+
ariaKeyShortcut: ariaKeyShortcut.primary( 'c' ),
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export const WithShortcut: Story = {
|
|
121
|
+
...Default,
|
|
122
|
+
args: {
|
|
123
|
+
...Default.args,
|
|
124
|
+
icon: copy,
|
|
125
|
+
label: 'Copy',
|
|
126
|
+
shortcut: EXAMPLE_SHORTCUT_OBJECT,
|
|
127
|
+
},
|
|
128
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
@layer wp-ui-utilities, wp-ui-components, wp-ui-compositions, wp-ui-overrides;
|
|
2
|
+
|
|
3
|
+
@layer wp-ui-compositions {
|
|
4
|
+
.icon-button {
|
|
5
|
+
--wp-ui-button-aspect-ratio: 1;
|
|
6
|
+
--wp-ui-button-padding-inline: 0;
|
|
7
|
+
--wp-ui-button-min-width: unset;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.icon {
|
|
11
|
+
/* Compensate for the button's 1px border so the icon extends
|
|
12
|
+
edge-to-edge and doesn't inflate the button's dimensions
|
|
13
|
+
(e.g. 24px icon inside a 24px-tall small button). */
|
|
14
|
+
margin: -1px;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { render, waitFor, screen } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { createRef } from '@wordpress/element';
|
|
4
|
+
import { IconButton } from '../index';
|
|
5
|
+
|
|
6
|
+
describe( 'IconButton', () => {
|
|
7
|
+
it( 'forwards ref', () => {
|
|
8
|
+
const ref = createRef< HTMLButtonElement >();
|
|
9
|
+
|
|
10
|
+
render( <IconButton ref={ ref } label="Click me" icon={ <svg /> } /> );
|
|
11
|
+
|
|
12
|
+
expect( ref.current ).toBeInstanceOf( HTMLButtonElement );
|
|
13
|
+
} );
|
|
14
|
+
|
|
15
|
+
it( 'respects custom render prop as handled by Button', () => {
|
|
16
|
+
render(
|
|
17
|
+
<IconButton
|
|
18
|
+
label="Click me"
|
|
19
|
+
icon={ <svg /> }
|
|
20
|
+
variant="outline"
|
|
21
|
+
disabled
|
|
22
|
+
focusableWhenDisabled
|
|
23
|
+
render={ <button data-testid="button" /> }
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
// Should render as a button from `render` prop...
|
|
28
|
+
const button = screen.getByRole( 'button', { name: 'Click me' } );
|
|
29
|
+
expect( button ).toHaveAttribute( 'data-testid', 'button' );
|
|
30
|
+
|
|
31
|
+
// ...and still inherit the behavior of Button
|
|
32
|
+
expect( button ).toBeEnabled();
|
|
33
|
+
expect( button ).toHaveAttribute( 'aria-disabled', 'true' );
|
|
34
|
+
} );
|
|
35
|
+
|
|
36
|
+
describe( 'shortcut', () => {
|
|
37
|
+
it( 'sets aria-keyshortcuts attribute on the button', () => {
|
|
38
|
+
const { rerender } = render(
|
|
39
|
+
<IconButton
|
|
40
|
+
label="Save"
|
|
41
|
+
icon={ <svg /> }
|
|
42
|
+
shortcut={ {
|
|
43
|
+
displayShortcut: '⌘S',
|
|
44
|
+
ariaKeyShortcut: 'Meta+S',
|
|
45
|
+
} }
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const button = screen.getByRole( 'button', { name: 'Save' } );
|
|
50
|
+
expect( button ).toHaveAttribute( 'aria-keyshortcuts', 'Meta+S' );
|
|
51
|
+
|
|
52
|
+
// The aria-keyshortcuts attribute is removed when there is no
|
|
53
|
+
// `shortcut` prop.
|
|
54
|
+
rerender( <IconButton label="Save" icon={ <svg /> } /> );
|
|
55
|
+
expect( button ).not.toHaveAttribute( 'aria-keyshortcuts' );
|
|
56
|
+
} );
|
|
57
|
+
|
|
58
|
+
it( 'displays the shortcut in the tooltip but hides it from assistive technology', async () => {
|
|
59
|
+
const user = userEvent.setup();
|
|
60
|
+
|
|
61
|
+
render(
|
|
62
|
+
<IconButton
|
|
63
|
+
label="Save"
|
|
64
|
+
icon={ <svg /> }
|
|
65
|
+
shortcut={ {
|
|
66
|
+
displayShortcut: '⌘S',
|
|
67
|
+
ariaKeyShortcut: 'Meta+S',
|
|
68
|
+
} }
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const button = screen.getByRole( 'button', { name: 'Save' } );
|
|
73
|
+
await user.hover( button );
|
|
74
|
+
|
|
75
|
+
await waitFor( () => {
|
|
76
|
+
const shortcutElement = screen.getByText( '⌘S' );
|
|
77
|
+
expect( shortcutElement ).toBeVisible();
|
|
78
|
+
} );
|
|
79
|
+
|
|
80
|
+
expect( screen.getByText( '⌘S' ) ).toHaveAttribute(
|
|
81
|
+
'aria-hidden',
|
|
82
|
+
'true'
|
|
83
|
+
);
|
|
84
|
+
} );
|
|
85
|
+
} );
|
|
86
|
+
} );
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type ButtonProps } from '../button/types';
|
|
2
|
+
import { type IconProps } from '../icon/types';
|
|
3
|
+
|
|
4
|
+
export type IconButtonProps = Omit< ButtonProps, 'children' > & {
|
|
5
|
+
/**
|
|
6
|
+
* A label describing the button's action, shown as a tooltip and to
|
|
7
|
+
* assistive technology.
|
|
8
|
+
*/
|
|
9
|
+
label: string;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The icon to display in the button.
|
|
13
|
+
*/
|
|
14
|
+
icon: IconProps[ 'icon' ];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The keyboard shortcut associated with this button. When provided, the
|
|
18
|
+
* shortcut is displayed in the tooltip and announced to assistive technology.
|
|
19
|
+
*
|
|
20
|
+
* **Note**: This prop is for display and accessibility purposes only — the
|
|
21
|
+
* consumer is responsible for implementing the actual keyboard event handler.
|
|
22
|
+
*/
|
|
23
|
+
shortcut?: {
|
|
24
|
+
/**
|
|
25
|
+
* The human-readable representation of the shortcut, displayed in the
|
|
26
|
+
* tooltip. Use platform-appropriate symbols (e.g., "⌘S" on macOS,
|
|
27
|
+
* "Ctrl+S" on Windows).
|
|
28
|
+
*/
|
|
29
|
+
displayShortcut: string;
|
|
30
|
+
/**
|
|
31
|
+
* The shortcut in a format compatible with the
|
|
32
|
+
* [aria-keyshortcuts](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-keyshortcuts)
|
|
33
|
+
* attribute. Use "+" to separate keys and standard key names
|
|
34
|
+
* (e.g., "Meta+S", "Control+Shift+P").
|
|
35
|
+
*/
|
|
36
|
+
ariaKeyShortcut: string;
|
|
37
|
+
};
|
|
38
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export * from './badge';
|
|
2
|
-
export * from './box';
|
|
3
2
|
export * from './button';
|
|
3
|
+
export * as Dialog from './dialog';
|
|
4
4
|
export * from './form/primitives';
|
|
5
5
|
export * from './icon';
|
|
6
|
+
export * from './icon-button';
|
|
6
7
|
export * from './stack';
|
|
8
|
+
export * as Tabs from './tabs';
|
|
7
9
|
export * as Tooltip from './tooltip';
|
|
8
10
|
export * from './visually-hidden';
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
2
|
import { Stack } from '../index';
|
|
3
|
-
import { Box } from '../../box';
|
|
4
3
|
|
|
5
4
|
const meta: Meta< typeof Stack > = {
|
|
6
5
|
title: 'Design System/Components/Stack',
|
|
@@ -9,9 +8,9 @@ const meta: Meta< typeof Stack > = {
|
|
|
9
8
|
export default meta;
|
|
10
9
|
|
|
11
10
|
const DemoBox = ( { variant }: { variant?: 'lg' } ) => (
|
|
12
|
-
<
|
|
13
|
-
backgroundColor="brand"
|
|
11
|
+
<div
|
|
14
12
|
style={ {
|
|
13
|
+
backgroundColor: 'var(--wpds-color-bg-surface-brand)',
|
|
15
14
|
width: variant === 'lg' ? '150px' : '100px',
|
|
16
15
|
height: variant === 'lg' ? '150px' : '100px',
|
|
17
16
|
} }
|
|
@@ -22,7 +21,7 @@ type Story = StoryObj< typeof Stack >;
|
|
|
22
21
|
|
|
23
22
|
export const Default: Story = {
|
|
24
23
|
args: {
|
|
25
|
-
gap: '
|
|
24
|
+
gap: 'md',
|
|
26
25
|
children: (
|
|
27
26
|
<>
|
|
28
27
|
<DemoBox />
|
|
@@ -91,7 +90,7 @@ export const Nested: Story = {
|
|
|
91
90
|
children: (
|
|
92
91
|
<>
|
|
93
92
|
<DemoBox variant="lg" />
|
|
94
|
-
<Stack gap="
|
|
93
|
+
<Stack gap="lg">
|
|
95
94
|
<DemoBox />
|
|
96
95
|
<DemoBox />
|
|
97
96
|
</Stack>
|