@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,309 @@
|
|
|
1
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { Component, createRef } from '@wordpress/element';
|
|
4
|
+
import type { ReactNode } from 'react';
|
|
5
|
+
import * as Dialog from '../index';
|
|
6
|
+
|
|
7
|
+
class TestErrorBoundary extends Component<
|
|
8
|
+
{ children: ReactNode; onError: ( error: Error ) => void },
|
|
9
|
+
{ hasError: boolean }
|
|
10
|
+
> {
|
|
11
|
+
constructor( props: {
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
onError: ( error: Error ) => void;
|
|
14
|
+
} ) {
|
|
15
|
+
super( props );
|
|
16
|
+
this.state = { hasError: false };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static getDerivedStateFromError() {
|
|
20
|
+
return { hasError: true };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
componentDidCatch( error: Error ) {
|
|
24
|
+
this.props.onError( error );
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
render() {
|
|
28
|
+
if ( this.state.hasError ) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return this.props.children;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe( 'Dialog', () => {
|
|
37
|
+
it( 'forwards ref', async () => {
|
|
38
|
+
const user = userEvent.setup();
|
|
39
|
+
const triggerRef = createRef< HTMLButtonElement >();
|
|
40
|
+
const popupRef = createRef< HTMLDivElement >();
|
|
41
|
+
const actionRef = createRef< HTMLButtonElement >();
|
|
42
|
+
const headerRef = createRef< HTMLDivElement >();
|
|
43
|
+
const titleRef = createRef< HTMLHeadingElement >();
|
|
44
|
+
const closeIconRef = createRef< HTMLButtonElement >();
|
|
45
|
+
const footerRef = createRef< HTMLDivElement >();
|
|
46
|
+
|
|
47
|
+
render(
|
|
48
|
+
<Dialog.Root>
|
|
49
|
+
<Dialog.Trigger ref={ triggerRef }>Open Dialog</Dialog.Trigger>
|
|
50
|
+
<Dialog.Popup ref={ popupRef }>
|
|
51
|
+
<Dialog.Header ref={ headerRef }>
|
|
52
|
+
<Dialog.Title ref={ titleRef }>
|
|
53
|
+
Test Dialog
|
|
54
|
+
</Dialog.Title>
|
|
55
|
+
<Dialog.CloseIcon ref={ closeIconRef } />
|
|
56
|
+
</Dialog.Header>
|
|
57
|
+
<Dialog.Footer ref={ footerRef }>
|
|
58
|
+
<Dialog.Action ref={ actionRef }>Close</Dialog.Action>
|
|
59
|
+
</Dialog.Footer>
|
|
60
|
+
</Dialog.Popup>
|
|
61
|
+
</Dialog.Root>
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// Test trigger ref before interaction
|
|
65
|
+
expect( triggerRef.current ).toBeInstanceOf( HTMLButtonElement );
|
|
66
|
+
|
|
67
|
+
// Click trigger to open dialog
|
|
68
|
+
await user.click( triggerRef.current! );
|
|
69
|
+
|
|
70
|
+
// Wait for the dialog to appear
|
|
71
|
+
await waitFor( () => {
|
|
72
|
+
expect( popupRef.current ).toBeInstanceOf( HTMLDivElement );
|
|
73
|
+
} );
|
|
74
|
+
|
|
75
|
+
// Now that the dialog is open, verify all inner refs
|
|
76
|
+
expect( headerRef.current ).toBeInstanceOf( HTMLDivElement );
|
|
77
|
+
expect( titleRef.current ).toBeInstanceOf( HTMLHeadingElement );
|
|
78
|
+
expect( closeIconRef.current ).toBeInstanceOf( HTMLButtonElement );
|
|
79
|
+
expect( actionRef.current ).toBeInstanceOf( HTMLButtonElement );
|
|
80
|
+
expect( footerRef.current ).toBeInstanceOf( HTMLDivElement );
|
|
81
|
+
} );
|
|
82
|
+
|
|
83
|
+
describe( 'Development mode validation', () => {
|
|
84
|
+
// Suppress React's error boundary logging for these tests.
|
|
85
|
+
let originalConsoleError: typeof console.error;
|
|
86
|
+
|
|
87
|
+
beforeEach( () => {
|
|
88
|
+
// eslint-disable-next-line no-console
|
|
89
|
+
originalConsoleError = console.error;
|
|
90
|
+
// eslint-disable-next-line no-console
|
|
91
|
+
console.error = jest.fn();
|
|
92
|
+
} );
|
|
93
|
+
|
|
94
|
+
afterEach( () => {
|
|
95
|
+
// eslint-disable-next-line no-console
|
|
96
|
+
console.error = originalConsoleError;
|
|
97
|
+
} );
|
|
98
|
+
|
|
99
|
+
it( 'should throw when Dialog.Title is missing', async () => {
|
|
100
|
+
const user = userEvent.setup();
|
|
101
|
+
const onError = jest.fn();
|
|
102
|
+
|
|
103
|
+
render(
|
|
104
|
+
<TestErrorBoundary onError={ onError }>
|
|
105
|
+
<Dialog.Root>
|
|
106
|
+
<Dialog.Trigger>Open Dialog</Dialog.Trigger>
|
|
107
|
+
<Dialog.Popup>
|
|
108
|
+
<Dialog.Header>
|
|
109
|
+
{ /* Missing Dialog.Title */ }
|
|
110
|
+
</Dialog.Header>
|
|
111
|
+
<p>Content without a title</p>
|
|
112
|
+
<Dialog.Footer>
|
|
113
|
+
<Dialog.Action>Close</Dialog.Action>
|
|
114
|
+
</Dialog.Footer>
|
|
115
|
+
</Dialog.Popup>
|
|
116
|
+
</Dialog.Root>
|
|
117
|
+
</TestErrorBoundary>
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
// Open the dialog - this will trigger the error in useEffect
|
|
121
|
+
await user.click(
|
|
122
|
+
screen.getByRole( 'button', { name: 'Open Dialog' } )
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
await waitFor( () => {
|
|
126
|
+
expect( onError ).toHaveBeenCalled();
|
|
127
|
+
} );
|
|
128
|
+
|
|
129
|
+
expect( onError.mock.calls[ 0 ][ 0 ] ).toBeInstanceOf( Error );
|
|
130
|
+
expect( ( onError.mock.calls[ 0 ][ 0 ] as Error ).message ).toBe(
|
|
131
|
+
'Dialog: Missing <Dialog.Title>. ' +
|
|
132
|
+
'For accessibility, every dialog requires a title. ' +
|
|
133
|
+
'If needed, the title can be visually hidden but must not be omitted.'
|
|
134
|
+
);
|
|
135
|
+
} );
|
|
136
|
+
|
|
137
|
+
it( 'should not throw before opening the dialog', async () => {
|
|
138
|
+
const onError = jest.fn();
|
|
139
|
+
|
|
140
|
+
render(
|
|
141
|
+
<TestErrorBoundary onError={ onError }>
|
|
142
|
+
<Dialog.Root>
|
|
143
|
+
<Dialog.Trigger>Open Dialog</Dialog.Trigger>
|
|
144
|
+
<Dialog.Popup>
|
|
145
|
+
<Dialog.Header>
|
|
146
|
+
<Dialog.Title>My Title</Dialog.Title>
|
|
147
|
+
</Dialog.Header>
|
|
148
|
+
<p>Content with a title</p>
|
|
149
|
+
<Dialog.Footer>
|
|
150
|
+
<Dialog.Action>Close</Dialog.Action>
|
|
151
|
+
</Dialog.Footer>
|
|
152
|
+
</Dialog.Popup>
|
|
153
|
+
</Dialog.Root>
|
|
154
|
+
</TestErrorBoundary>
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
// Check that the dialog itself hasn't been rendered in the DOM.
|
|
158
|
+
await expect( screen.findByRole( 'dialog' ) ).rejects.toThrow();
|
|
159
|
+
|
|
160
|
+
expect( onError ).not.toHaveBeenCalled();
|
|
161
|
+
} );
|
|
162
|
+
|
|
163
|
+
it( 'should not throw when Dialog.Title is present', async () => {
|
|
164
|
+
const user = userEvent.setup();
|
|
165
|
+
const onError = jest.fn();
|
|
166
|
+
|
|
167
|
+
render(
|
|
168
|
+
<TestErrorBoundary onError={ onError }>
|
|
169
|
+
<Dialog.Root>
|
|
170
|
+
<Dialog.Trigger>Open Dialog</Dialog.Trigger>
|
|
171
|
+
<Dialog.Popup>
|
|
172
|
+
<Dialog.Header>
|
|
173
|
+
<Dialog.Title>My Title</Dialog.Title>
|
|
174
|
+
</Dialog.Header>
|
|
175
|
+
<p>Content with a title</p>
|
|
176
|
+
<Dialog.Footer>
|
|
177
|
+
<Dialog.Action>Close</Dialog.Action>
|
|
178
|
+
</Dialog.Footer>
|
|
179
|
+
</Dialog.Popup>
|
|
180
|
+
</Dialog.Root>
|
|
181
|
+
</TestErrorBoundary>
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
// Open the dialog - should not throw
|
|
185
|
+
await user.click(
|
|
186
|
+
screen.getByRole( 'button', { name: 'Open Dialog' } )
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
// Wait for the dialog to appear and ensure validation does not trigger errors
|
|
190
|
+
await waitFor( () => {
|
|
191
|
+
expect( screen.getByRole( 'dialog' ) ).toBeInTheDocument();
|
|
192
|
+
} );
|
|
193
|
+
expect( onError ).not.toHaveBeenCalled();
|
|
194
|
+
} );
|
|
195
|
+
|
|
196
|
+
it( 'should throw when Dialog.Title is empty', async () => {
|
|
197
|
+
const user = userEvent.setup();
|
|
198
|
+
const onError = jest.fn();
|
|
199
|
+
|
|
200
|
+
render(
|
|
201
|
+
<TestErrorBoundary onError={ onError }>
|
|
202
|
+
<Dialog.Root>
|
|
203
|
+
<Dialog.Trigger>Open Dialog</Dialog.Trigger>
|
|
204
|
+
<Dialog.Popup>
|
|
205
|
+
<Dialog.Header>
|
|
206
|
+
{ /* @ts-expect-error this is just for test purposes */ }
|
|
207
|
+
<Dialog.Title>
|
|
208
|
+
{ /* Empty title */ }
|
|
209
|
+
</Dialog.Title>
|
|
210
|
+
</Dialog.Header>
|
|
211
|
+
<p>Content with empty title</p>
|
|
212
|
+
<Dialog.Footer>
|
|
213
|
+
<Dialog.Action>Close</Dialog.Action>
|
|
214
|
+
</Dialog.Footer>
|
|
215
|
+
</Dialog.Popup>
|
|
216
|
+
</Dialog.Root>
|
|
217
|
+
</TestErrorBoundary>
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
// Open the dialog - this will trigger the error
|
|
221
|
+
await user.click(
|
|
222
|
+
screen.getByRole( 'button', { name: 'Open Dialog' } )
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
await waitFor( () => {
|
|
226
|
+
expect( onError ).toHaveBeenCalled();
|
|
227
|
+
} );
|
|
228
|
+
|
|
229
|
+
expect( onError.mock.calls[ 0 ][ 0 ] ).toBeInstanceOf( Error );
|
|
230
|
+
expect( ( onError.mock.calls[ 0 ][ 0 ] as Error ).message ).toBe(
|
|
231
|
+
'Dialog: <Dialog.Title> cannot be empty. ' +
|
|
232
|
+
'Provide meaningful text content for the dialog title.'
|
|
233
|
+
);
|
|
234
|
+
} );
|
|
235
|
+
|
|
236
|
+
it( 'should throw when Dialog.Title contains only whitespace', async () => {
|
|
237
|
+
const user = userEvent.setup();
|
|
238
|
+
const onError = jest.fn();
|
|
239
|
+
|
|
240
|
+
render(
|
|
241
|
+
<TestErrorBoundary onError={ onError }>
|
|
242
|
+
<Dialog.Root>
|
|
243
|
+
<Dialog.Trigger>Open Dialog</Dialog.Trigger>
|
|
244
|
+
<Dialog.Popup>
|
|
245
|
+
<Dialog.Header>
|
|
246
|
+
<Dialog.Title> </Dialog.Title>
|
|
247
|
+
</Dialog.Header>
|
|
248
|
+
<p>Content with whitespace-only title</p>
|
|
249
|
+
<Dialog.Footer>
|
|
250
|
+
<Dialog.Action>Close</Dialog.Action>
|
|
251
|
+
</Dialog.Footer>
|
|
252
|
+
</Dialog.Popup>
|
|
253
|
+
</Dialog.Root>
|
|
254
|
+
</TestErrorBoundary>
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
// Open the dialog - this will trigger the error
|
|
258
|
+
await user.click(
|
|
259
|
+
screen.getByRole( 'button', { name: 'Open Dialog' } )
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
await waitFor( () => {
|
|
263
|
+
expect( onError ).toHaveBeenCalled();
|
|
264
|
+
} );
|
|
265
|
+
|
|
266
|
+
expect( onError.mock.calls[ 0 ][ 0 ] ).toBeInstanceOf( Error );
|
|
267
|
+
expect( ( onError.mock.calls[ 0 ][ 0 ] as Error ).message ).toBe(
|
|
268
|
+
'Dialog: <Dialog.Title> cannot be empty. ' +
|
|
269
|
+
'Provide meaningful text content for the dialog title.'
|
|
270
|
+
);
|
|
271
|
+
} );
|
|
272
|
+
|
|
273
|
+
it( 'should not throw when Dialog.Title contains mixed content with text', async () => {
|
|
274
|
+
const user = userEvent.setup();
|
|
275
|
+
const onError = jest.fn();
|
|
276
|
+
|
|
277
|
+
render(
|
|
278
|
+
<TestErrorBoundary onError={ onError }>
|
|
279
|
+
<Dialog.Root>
|
|
280
|
+
<Dialog.Trigger>Open Dialog</Dialog.Trigger>
|
|
281
|
+
<Dialog.Popup>
|
|
282
|
+
<Dialog.Header>
|
|
283
|
+
<Dialog.Title>
|
|
284
|
+
<span aria-hidden="true">🎉</span>
|
|
285
|
+
Settings
|
|
286
|
+
</Dialog.Title>
|
|
287
|
+
</Dialog.Header>
|
|
288
|
+
<p>Content with icon and text title</p>
|
|
289
|
+
<Dialog.Footer>
|
|
290
|
+
<Dialog.Action>Close</Dialog.Action>
|
|
291
|
+
</Dialog.Footer>
|
|
292
|
+
</Dialog.Popup>
|
|
293
|
+
</Dialog.Root>
|
|
294
|
+
</TestErrorBoundary>
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
// Open the dialog - should not throw
|
|
298
|
+
await user.click(
|
|
299
|
+
screen.getByRole( 'button', { name: 'Open Dialog' } )
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
// Wait for the dialog to appear and ensure validation does not trigger errors
|
|
303
|
+
await waitFor( () => {
|
|
304
|
+
expect( screen.getByRole( 'dialog' ) ).toBeInTheDocument();
|
|
305
|
+
} );
|
|
306
|
+
expect( onError ).not.toHaveBeenCalled();
|
|
307
|
+
} );
|
|
308
|
+
} );
|
|
309
|
+
} );
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Dialog as _Dialog } from '@base-ui/react/dialog';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import { useMergeRefs } from '@wordpress/compose';
|
|
4
|
+
import { forwardRef, useLayoutEffect, useRef } from '@wordpress/element';
|
|
5
|
+
import { useDialogValidationContext } from './context';
|
|
6
|
+
import styles from './style.module.css';
|
|
7
|
+
import type { TitleProps } from './types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Renders the dialog title. This component is required for accessibility
|
|
11
|
+
* and serves as both the visible heading and the accessible label for
|
|
12
|
+
* the dialog.
|
|
13
|
+
*
|
|
14
|
+
* Base UI's Dialog.Title renders an `<h2>` by default. Use the `render` prop
|
|
15
|
+
* to customize the element if needed.
|
|
16
|
+
*/
|
|
17
|
+
const Title = forwardRef< HTMLHeadingElement, TitleProps >(
|
|
18
|
+
function DialogTitle( { className, render, ...props }, forwardedRef ) {
|
|
19
|
+
const validationContext = useDialogValidationContext();
|
|
20
|
+
const internalRef = useRef< HTMLHeadingElement >( null );
|
|
21
|
+
const mergedRef = useMergeRefs( [ internalRef, forwardedRef ] );
|
|
22
|
+
|
|
23
|
+
// Register this title with the parent Popup for validation (dev only)
|
|
24
|
+
useLayoutEffect( () => {
|
|
25
|
+
validationContext?.registerTitle( internalRef.current );
|
|
26
|
+
}, [ validationContext ] );
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<_Dialog.Title
|
|
30
|
+
ref={ mergedRef }
|
|
31
|
+
className={ clsx( styles.title, className ) }
|
|
32
|
+
render={ render }
|
|
33
|
+
{ ...props }
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
export { Title };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Dialog as _Dialog } from '@base-ui/react/dialog';
|
|
2
|
+
import { forwardRef } from '@wordpress/element';
|
|
3
|
+
import type { TriggerProps } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Renders a button that opens the dialog popup when clicked.
|
|
7
|
+
*/
|
|
8
|
+
const Trigger = forwardRef< HTMLButtonElement, TriggerProps >(
|
|
9
|
+
function DialogTrigger( props, ref ) {
|
|
10
|
+
return <_Dialog.Trigger ref={ ref } { ...props } />;
|
|
11
|
+
}
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
export { Trigger };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { Dialog as _Dialog } from '@base-ui/react/dialog';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
import type { Button } from '../button';
|
|
4
|
+
import type { IconButton } from '../icon-button';
|
|
5
|
+
import type { ComponentProps } from '../utils/types';
|
|
6
|
+
|
|
7
|
+
export interface RootProps
|
|
8
|
+
extends Pick<
|
|
9
|
+
_Dialog.Root.Props,
|
|
10
|
+
'open' | 'onOpenChange' | 'defaultOpen' | 'modal'
|
|
11
|
+
> {
|
|
12
|
+
/**
|
|
13
|
+
* The content to be rendered inside the component.
|
|
14
|
+
*/
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface TriggerProps extends ComponentProps< 'button' > {
|
|
19
|
+
/**
|
|
20
|
+
* The content to be rendered inside the component.
|
|
21
|
+
*/
|
|
22
|
+
children?: ReactNode;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface PopupProps extends ComponentProps< 'div' > {
|
|
26
|
+
/**
|
|
27
|
+
* The content to be rendered inside the component.
|
|
28
|
+
*/
|
|
29
|
+
children?: ReactNode;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Renders the dialog at a preset width (excluding additional padding from
|
|
33
|
+
* the viewport edges).
|
|
34
|
+
*
|
|
35
|
+
* - `'small'` — max-width of 384px.
|
|
36
|
+
* - `'medium'` — max-width of 512px.
|
|
37
|
+
* - `'large'` — max-width of 840px.
|
|
38
|
+
* - `'stretch'` — no max-width, stretches to fill available space.
|
|
39
|
+
* - `'full'` — stretches to fill available width and height.
|
|
40
|
+
*
|
|
41
|
+
* @default 'medium'
|
|
42
|
+
*/
|
|
43
|
+
size?: 'small' | 'medium' | 'large' | 'stretch' | 'full';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ActionProps extends ComponentProps< typeof Button > {
|
|
47
|
+
/**
|
|
48
|
+
* The content to be rendered inside the component.
|
|
49
|
+
*/
|
|
50
|
+
children?: ReactNode;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface FooterProps extends ComponentProps< 'div' > {
|
|
54
|
+
/**
|
|
55
|
+
* The content to be rendered inside the component.
|
|
56
|
+
*/
|
|
57
|
+
children?: ReactNode;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface HeaderProps extends ComponentProps< 'div' > {
|
|
61
|
+
/**
|
|
62
|
+
* The content to be rendered inside the component.
|
|
63
|
+
*/
|
|
64
|
+
children?: ReactNode;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface TitleProps extends ComponentProps< 'h2' > {
|
|
68
|
+
/**
|
|
69
|
+
* The title content to be rendered. This serves as both the visible
|
|
70
|
+
* heading and the accessible label for the dialog.
|
|
71
|
+
*/
|
|
72
|
+
children: ReactNode;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface CloseIconProps
|
|
76
|
+
extends Omit<
|
|
77
|
+
ComponentProps< typeof IconButton >,
|
|
78
|
+
'label' | 'icon' | 'loading' | 'loadingAnnouncement'
|
|
79
|
+
> {
|
|
80
|
+
/**
|
|
81
|
+
* A label describing the button's action, shown as a tooltip and to
|
|
82
|
+
* assistive technology.
|
|
83
|
+
*
|
|
84
|
+
* @default __( 'Close' )
|
|
85
|
+
*/
|
|
86
|
+
label?: ComponentProps< typeof IconButton >[ 'label' ];
|
|
87
|
+
/**
|
|
88
|
+
* The icon to display in the button.
|
|
89
|
+
*
|
|
90
|
+
* @default the `close` icon from `@wordpress/icons`
|
|
91
|
+
*/
|
|
92
|
+
icon?: ComponentProps< typeof IconButton >[ 'icon' ];
|
|
93
|
+
}
|
|
@@ -6,7 +6,7 @@ import type { FieldRootProps } from './types';
|
|
|
6
6
|
import { Stack } from '../../../stack';
|
|
7
7
|
|
|
8
8
|
const DEFAULT_RENDER = ( props: React.ComponentProps< typeof Stack > ) => (
|
|
9
|
-
<Stack { ...props } direction="column" gap="
|
|
9
|
+
<Stack { ...props } direction="column" gap="sm" />
|
|
10
10
|
);
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
@layer wp-ui-components {
|
|
4
4
|
.input-layout {
|
|
5
|
-
|
|
6
|
-
--wp-ui-input-layout-padding-inline: calc(var(--wpds-dimension-base) * 3);
|
|
5
|
+
--wp-ui-input-layout-padding-inline: var(--wpds-dimension-padding-md);
|
|
7
6
|
|
|
8
7
|
display: flex;
|
|
9
8
|
height: 40px;
|
|
@@ -22,14 +21,12 @@
|
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
&.is-size-compact {
|
|
25
|
-
|
|
26
|
-
--wp-ui-input-layout-padding-inline: calc(var(--wpds-dimension-base) * 2);
|
|
24
|
+
--wp-ui-input-layout-padding-inline: var(--wpds-dimension-padding-sm);
|
|
27
25
|
height: 32px;
|
|
28
26
|
}
|
|
29
27
|
|
|
30
28
|
&.is-size-small {
|
|
31
|
-
|
|
32
|
-
--wp-ui-input-layout-padding-inline: calc(var(--wpds-dimension-base) * 2);
|
|
29
|
+
--wp-ui-input-layout-padding-inline: var(--wpds-dimension-padding-sm);
|
|
33
30
|
height: 24px;
|
|
34
31
|
}
|
|
35
32
|
|
|
@@ -66,10 +63,10 @@
|
|
|
66
63
|
&.is-padding-minimal {
|
|
67
64
|
--wp-ui-input-layout-prefix-padding-start:
|
|
68
65
|
calc(var(--wp-ui-input-layout-padding-inline) -
|
|
69
|
-
var(--wpds-dimension-
|
|
66
|
+
var(--wpds-dimension-padding-xs));
|
|
70
67
|
--wp-ui-input-layout-suffix-padding-end:
|
|
71
68
|
calc(var(--wp-ui-input-layout-padding-inline) -
|
|
72
|
-
var(--wpds-dimension-
|
|
69
|
+
var(--wpds-dimension-padding-xs));
|
|
73
70
|
}
|
|
74
71
|
|
|
75
72
|
&.is-prefix {
|
|
@@ -3,8 +3,6 @@ import userEvent from '@testing-library/user-event';
|
|
|
3
3
|
import { createRef } from '@wordpress/element';
|
|
4
4
|
import * as Select from '../index';
|
|
5
5
|
|
|
6
|
-
jest.setTimeout( 10000 );
|
|
7
|
-
|
|
8
6
|
describe( 'Select', () => {
|
|
9
7
|
it( 'forwards ref', async () => {
|
|
10
8
|
const user = userEvent.setup();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Textarea } from './textarea';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { Textarea } from '../index';
|
|
3
|
+
|
|
4
|
+
const meta: Meta< typeof Textarea > = {
|
|
5
|
+
title: 'Design System/Components/Form/Primitives/Textarea',
|
|
6
|
+
component: Textarea,
|
|
7
|
+
};
|
|
8
|
+
export default meta;
|
|
9
|
+
|
|
10
|
+
type Story = StoryObj< typeof Textarea >;
|
|
11
|
+
|
|
12
|
+
export const Default: Story = {
|
|
13
|
+
args: {
|
|
14
|
+
placeholder: 'Placeholder',
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const Disabled: Story = {
|
|
19
|
+
args: {
|
|
20
|
+
...Default.args,
|
|
21
|
+
disabled: true,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const WithOverflow: Story = {
|
|
26
|
+
args: {
|
|
27
|
+
...Default.args,
|
|
28
|
+
defaultValue: `Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`,
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* When `rows` is set to `1`, the textarea will have the same footprint as a default `Input`.
|
|
34
|
+
*/
|
|
35
|
+
export const WithOneRow: Story = {
|
|
36
|
+
args: {
|
|
37
|
+
...Default.args,
|
|
38
|
+
rows: 1,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
@layer wp-ui-utilities, wp-ui-components, wp-ui-compositions, wp-ui-overrides;
|
|
2
|
+
|
|
3
|
+
@layer wp-ui-components {
|
|
4
|
+
.wrapper {
|
|
5
|
+
--wp-ui-textarea-min-height: 40px;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.textarea {
|
|
9
|
+
/* Prevents users from resizing the textarea below this height. */
|
|
10
|
+
min-height: calc(var(--wp-ui-textarea-min-height) - 2px);
|
|
11
|
+
resize: block;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@layer wp-ui-compositions {
|
|
16
|
+
.wrapper {
|
|
17
|
+
--wp-ui-input-padding-block: 9.9px;
|
|
18
|
+
|
|
19
|
+
height: auto;
|
|
20
|
+
line-height: 1.4; /* TODO: Use variable */
|
|
21
|
+
}
|
|
22
|
+
}
|