@wordpress/ui 0.7.2-next.v.202602241322.0 → 0.8.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 +13 -1
- package/CONTRIBUTING.md +51 -0
- package/build/badge/badge.cjs +2 -2
- package/build/badge/badge.cjs.map +2 -2
- package/build/button/button.cjs +5 -5
- package/build/button/button.cjs.map +2 -2
- package/build/dialog/footer.cjs +3 -3
- package/build/dialog/footer.cjs.map +2 -2
- package/build/dialog/header.cjs +3 -3
- package/build/dialog/header.cjs.map +2 -2
- package/build/dialog/popup.cjs +3 -3
- package/build/dialog/popup.cjs.map +2 -2
- package/build/dialog/title.cjs +3 -3
- package/build/dialog/title.cjs.map +2 -2
- package/build/form/primitives/field/description.cjs +2 -2
- package/build/form/primitives/field/description.cjs.map +2 -2
- package/build/form/primitives/field/details.cjs +2 -2
- package/build/form/primitives/field/details.cjs.map +2 -2
- package/build/form/primitives/field/label.cjs +2 -2
- package/build/form/primitives/field/label.cjs.map +2 -2
- package/build/form/primitives/field/root.cjs +1 -1
- package/build/form/primitives/field/root.cjs.map +2 -2
- package/build/form/primitives/fieldset/description.cjs +2 -2
- package/build/form/primitives/fieldset/description.cjs.map +2 -2
- package/build/form/primitives/fieldset/details.cjs +2 -2
- package/build/form/primitives/fieldset/details.cjs.map +2 -2
- package/build/form/primitives/fieldset/legend.cjs +2 -2
- package/build/form/primitives/fieldset/legend.cjs.map +2 -2
- package/build/form/primitives/fieldset/root.cjs +2 -2
- package/build/form/primitives/fieldset/root.cjs.map +2 -2
- package/build/form/primitives/input/input.cjs +4 -4
- package/build/form/primitives/input/input.cjs.map +2 -2
- package/build/form/primitives/input-layout/input-layout.cjs +21 -8
- package/build/form/primitives/input-layout/input-layout.cjs.map +2 -2
- package/build/form/primitives/input-layout/slot.cjs +5 -14
- package/build/form/primitives/input-layout/slot.cjs.map +2 -2
- package/build/form/primitives/input-layout/types.cjs.map +1 -1
- 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 +4 -4
- package/build/form/primitives/select/popup.cjs.map +2 -2
- package/build/form/primitives/select/trigger.cjs +4 -4
- package/build/form/primitives/select/trigger.cjs.map +2 -2
- package/build/form/primitives/textarea/textarea.cjs +1 -1
- package/build/form/primitives/textarea/textarea.cjs.map +2 -2
- package/build/icon-button/icon-button.cjs +1 -1
- package/build/icon-button/icon-button.cjs.map +2 -2
- package/build/index.cjs +3 -0
- package/build/index.cjs.map +2 -2
- package/build/notice/action-button.cjs +76 -0
- package/build/notice/action-button.cjs.map +7 -0
- package/build/notice/action-link.cjs +70 -0
- package/build/notice/action-link.cjs.map +7 -0
- package/build/notice/actions.cjs +59 -0
- package/build/notice/actions.cjs.map +7 -0
- package/build/notice/close-icon.cjs +74 -0
- package/build/notice/close-icon.cjs.map +7 -0
- package/build/notice/description.cjs +57 -0
- package/build/notice/description.cjs.map +7 -0
- package/build/notice/index.cjs +49 -0
- package/build/notice/index.cjs.map +7 -0
- package/build/notice/root.cjs +137 -0
- package/build/notice/root.cjs.map +7 -0
- package/build/notice/title.cjs +54 -0
- package/build/notice/title.cjs.map +7 -0
- package/build/notice/types.cjs +19 -0
- package/build/notice/types.cjs.map +7 -0
- package/build/stack/stack.cjs +11 -2
- package/build/stack/stack.cjs.map +2 -2
- package/build/tabs/list.cjs +2 -2
- package/build/tabs/list.cjs.map +2 -2
- package/build/tabs/panel.cjs +2 -2
- package/build/tabs/panel.cjs.map +2 -2
- package/build/tabs/tab.cjs +2 -2
- package/build/tabs/tab.cjs.map +2 -2
- package/build/tooltip/popup.cjs +3 -3
- package/build/tooltip/popup.cjs.map +2 -2
- package/build/visually-hidden/visually-hidden.cjs +1 -1
- package/build/visually-hidden/visually-hidden.cjs.map +2 -2
- package/build-module/badge/badge.mjs +2 -2
- package/build-module/badge/badge.mjs.map +2 -2
- package/build-module/button/button.mjs +5 -5
- package/build-module/button/button.mjs.map +2 -2
- package/build-module/dialog/footer.mjs +3 -3
- package/build-module/dialog/footer.mjs.map +2 -2
- package/build-module/dialog/header.mjs +3 -3
- package/build-module/dialog/header.mjs.map +2 -2
- package/build-module/dialog/popup.mjs +3 -3
- package/build-module/dialog/popup.mjs.map +2 -2
- package/build-module/dialog/title.mjs +3 -3
- package/build-module/dialog/title.mjs.map +2 -2
- package/build-module/form/primitives/field/description.mjs +2 -2
- package/build-module/form/primitives/field/description.mjs.map +2 -2
- package/build-module/form/primitives/field/details.mjs +2 -2
- package/build-module/form/primitives/field/details.mjs.map +2 -2
- package/build-module/form/primitives/field/label.mjs +2 -2
- package/build-module/form/primitives/field/label.mjs.map +2 -2
- package/build-module/form/primitives/field/root.mjs +1 -1
- package/build-module/form/primitives/field/root.mjs.map +2 -2
- package/build-module/form/primitives/fieldset/description.mjs +2 -2
- package/build-module/form/primitives/fieldset/description.mjs.map +2 -2
- package/build-module/form/primitives/fieldset/details.mjs +2 -2
- package/build-module/form/primitives/fieldset/details.mjs.map +2 -2
- package/build-module/form/primitives/fieldset/legend.mjs +2 -2
- package/build-module/form/primitives/fieldset/legend.mjs.map +2 -2
- package/build-module/form/primitives/fieldset/root.mjs +2 -2
- package/build-module/form/primitives/fieldset/root.mjs.map +2 -2
- package/build-module/form/primitives/input/input.mjs +4 -4
- package/build-module/form/primitives/input/input.mjs.map +2 -2
- package/build-module/form/primitives/input-layout/input-layout.mjs +22 -9
- package/build-module/form/primitives/input-layout/input-layout.mjs.map +2 -2
- package/build-module/form/primitives/input-layout/slot.mjs +5 -14
- 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 +4 -4
- package/build-module/form/primitives/select/popup.mjs.map +2 -2
- package/build-module/form/primitives/select/trigger.mjs +4 -4
- package/build-module/form/primitives/select/trigger.mjs.map +2 -2
- package/build-module/form/primitives/textarea/textarea.mjs +1 -1
- package/build-module/form/primitives/textarea/textarea.mjs.map +2 -2
- package/build-module/icon-button/icon-button.mjs +1 -1
- package/build-module/icon-button/icon-button.mjs.map +2 -2
- package/build-module/index.mjs +2 -0
- package/build-module/index.mjs.map +2 -2
- package/build-module/notice/action-button.mjs +41 -0
- package/build-module/notice/action-button.mjs.map +7 -0
- package/build-module/notice/action-link.mjs +35 -0
- package/build-module/notice/action-link.mjs.map +7 -0
- package/build-module/notice/actions.mjs +34 -0
- package/build-module/notice/actions.mjs.map +7 -0
- package/build-module/notice/close-icon.mjs +39 -0
- package/build-module/notice/close-icon.mjs.map +7 -0
- package/build-module/notice/description.mjs +32 -0
- package/build-module/notice/description.mjs.map +7 -0
- package/build-module/notice/index.mjs +18 -0
- package/build-module/notice/index.mjs.map +7 -0
- package/build-module/notice/root.mjs +102 -0
- package/build-module/notice/root.mjs.map +7 -0
- package/build-module/notice/title.mjs +29 -0
- package/build-module/notice/title.mjs.map +7 -0
- package/build-module/notice/types.mjs +1 -0
- package/build-module/notice/types.mjs.map +7 -0
- package/build-module/stack/stack.mjs +11 -2
- package/build-module/stack/stack.mjs.map +2 -2
- package/build-module/tabs/list.mjs +2 -2
- package/build-module/tabs/list.mjs.map +2 -2
- package/build-module/tabs/panel.mjs +2 -2
- package/build-module/tabs/panel.mjs.map +2 -2
- package/build-module/tabs/tab.mjs +2 -2
- package/build-module/tabs/tab.mjs.map +2 -2
- package/build-module/tooltip/popup.mjs +3 -3
- package/build-module/tooltip/popup.mjs.map +2 -2
- package/build-module/visually-hidden/visually-hidden.mjs +1 -1
- package/build-module/visually-hidden/visually-hidden.mjs.map +2 -2
- package/build-types/dialog/stories/index.story.d.ts +10 -0
- package/build-types/dialog/stories/index.story.d.ts.map +1 -1
- package/build-types/form/primitives/input-layout/input-layout.d.ts.map +1 -1
- package/build-types/form/primitives/input-layout/slot.d.ts.map +1 -1
- package/build-types/form/primitives/input-layout/types.d.ts +1 -9
- package/build-types/form/primitives/input-layout/types.d.ts.map +1 -1
- package/build-types/index.d.ts +1 -0
- package/build-types/index.d.ts.map +1 -1
- package/build-types/notice/action-button.d.ts +6 -0
- package/build-types/notice/action-button.d.ts.map +1 -0
- package/build-types/notice/action-link.d.ts +8 -0
- package/build-types/notice/action-link.d.ts.map +1 -0
- package/build-types/notice/actions.d.ts +6 -0
- package/build-types/notice/actions.d.ts.map +1 -0
- package/build-types/notice/close-icon.d.ts +6 -0
- package/build-types/notice/close-icon.d.ts.map +1 -0
- package/build-types/notice/description.d.ts +6 -0
- package/build-types/notice/description.d.ts.map +1 -0
- package/build-types/notice/index.d.ts +10 -0
- package/build-types/notice/index.d.ts.map +1 -0
- package/build-types/notice/root.d.ts +23 -0
- package/build-types/notice/root.d.ts.map +1 -0
- package/build-types/notice/stories/index.story.d.ts +28 -0
- package/build-types/notice/stories/index.story.d.ts.map +1 -0
- package/build-types/notice/test/index.test.d.ts +2 -0
- package/build-types/notice/test/index.test.d.ts.map +1 -0
- package/build-types/notice/title.d.ts +6 -0
- package/build-types/notice/title.d.ts.map +1 -0
- package/build-types/notice/types.d.ts +77 -0
- package/build-types/notice/types.d.ts.map +1 -0
- package/build-types/stack/stack.d.ts.map +1 -1
- package/package.json +11 -11
- package/src/dialog/stories/index.story.tsx +14 -0
- package/src/dialog/style.module.css +2 -0
- package/src/form/primitives/input-layout/input-layout.tsx +17 -8
- package/src/form/primitives/input-layout/slot.tsx +1 -15
- package/src/form/primitives/input-layout/style.module.css +6 -2
- package/src/form/primitives/input-layout/test/index.test.tsx +34 -1
- package/src/form/primitives/input-layout/types.ts +1 -10
- package/src/index.ts +1 -0
- package/src/notice/action-button.tsx +36 -0
- package/src/notice/action-link.tsx +28 -0
- package/src/notice/actions.tsx +25 -0
- package/src/notice/close-icon.tsx +30 -0
- package/src/notice/description.tsx +23 -0
- package/src/notice/index.ts +19 -0
- package/src/notice/root.tsx +132 -0
- package/src/notice/stories/index.story.tsx +157 -0
- package/src/notice/style.module.css +147 -0
- package/src/notice/test/index.test.tsx +206 -0
- package/src/notice/title.tsx +20 -0
- package/src/notice/types.ts +100 -0
- package/src/stack/stack.tsx +14 -1
- package/build/form/primitives/input-layout/context.cjs +0 -49
- package/build/form/primitives/input-layout/context.cjs.map +0 -7
- package/build-module/form/primitives/input-layout/context.mjs +0 -22
- package/build-module/form/primitives/input-layout/context.mjs.map +0 -7
- package/build-types/form/primitives/input-layout/context.d.ts +0 -17
- package/build-types/form/primitives/input-layout/context.d.ts.map +0 -1
- package/src/form/primitives/input-layout/context.tsx +0 -36
|
@@ -2,7 +2,6 @@ import clsx from 'clsx';
|
|
|
2
2
|
import { forwardRef } from '@wordpress/element';
|
|
3
3
|
import styles from './style.module.css';
|
|
4
4
|
import type { InputLayoutSlotProps } from './types';
|
|
5
|
-
import { useInputLayoutSlotContext } from './context';
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* A layout helper to add paddings in a prefix or suffix.
|
|
@@ -10,25 +9,12 @@ import { useInputLayoutSlotContext } from './context';
|
|
|
10
9
|
export const InputLayoutSlot = forwardRef<
|
|
11
10
|
HTMLDivElement,
|
|
12
11
|
InputLayoutSlotProps
|
|
13
|
-
>( function InputLayoutSlot(
|
|
14
|
-
{ type: typeProp, padding = 'default', ...restProps },
|
|
15
|
-
ref
|
|
16
|
-
) {
|
|
17
|
-
const typeContext = useInputLayoutSlotContext();
|
|
18
|
-
const type = typeProp ?? typeContext;
|
|
19
|
-
|
|
20
|
-
if ( ! type ) {
|
|
21
|
-
throw new Error(
|
|
22
|
-
'InputLayoutSlot requires a `type` prop or must be used within an InputLayout prefix/suffix slot.'
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
|
|
12
|
+
>( function InputLayoutSlot( { padding = 'default', ...restProps }, ref ) {
|
|
26
13
|
return (
|
|
27
14
|
<div
|
|
28
15
|
ref={ ref }
|
|
29
16
|
className={ clsx(
|
|
30
17
|
styles[ 'input-layout-slot' ],
|
|
31
|
-
styles[ `is-${ type }` ],
|
|
32
18
|
styles[ `is-padding-${ padding }` ]
|
|
33
19
|
) }
|
|
34
20
|
{ ...restProps }
|
|
@@ -56,6 +56,10 @@
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
.slot-wrapper {
|
|
60
|
+
display: contents;
|
|
61
|
+
}
|
|
62
|
+
|
|
59
63
|
.input-layout-slot {
|
|
60
64
|
display: flex;
|
|
61
65
|
align-items: center;
|
|
@@ -69,11 +73,11 @@
|
|
|
69
73
|
var(--wpds-dimension-padding-xs));
|
|
70
74
|
}
|
|
71
75
|
|
|
72
|
-
|
|
76
|
+
[data-slot-type="prefix"] & {
|
|
73
77
|
padding-inline-start: var(--wp-ui-input-layout-prefix-padding-start, var(--wp-ui-input-layout-padding-inline));
|
|
74
78
|
}
|
|
75
79
|
|
|
76
|
-
|
|
80
|
+
[data-slot-type="suffix"] & {
|
|
77
81
|
padding-inline-end: var(--wp-ui-input-layout-suffix-padding-end, var(--wp-ui-input-layout-padding-inline));
|
|
78
82
|
}
|
|
79
83
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { render } from '@testing-library/react';
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
2
|
import { createRef } from '@wordpress/element';
|
|
3
3
|
import { InputLayout } from '../index';
|
|
4
4
|
import { InputLayoutSlot } from '../slot';
|
|
@@ -20,4 +20,37 @@ describe( 'InputLayout', () => {
|
|
|
20
20
|
expect( layoutRef.current ).toBeInstanceOf( HTMLDivElement );
|
|
21
21
|
expect( slotRef.current ).toBeInstanceOf( HTMLDivElement );
|
|
22
22
|
} );
|
|
23
|
+
|
|
24
|
+
// Testing the DOM contract that CSS selectors depend on.
|
|
25
|
+
it( 'wraps prefix and suffix with data-slot-type attributes', () => {
|
|
26
|
+
render(
|
|
27
|
+
<InputLayout
|
|
28
|
+
prefix={ <InputLayout.Slot>Prefix</InputLayout.Slot> }
|
|
29
|
+
suffix={ <InputLayout.Slot>Suffix</InputLayout.Slot> }
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
/* eslint-disable testing-library/no-node-access */
|
|
34
|
+
const prefix = screen
|
|
35
|
+
.getByText( 'Prefix' )
|
|
36
|
+
.closest( '[data-slot-type]' );
|
|
37
|
+
expect( prefix ).toHaveAttribute( 'data-slot-type', 'prefix' );
|
|
38
|
+
|
|
39
|
+
const suffix = screen
|
|
40
|
+
.getByText( 'Suffix' )
|
|
41
|
+
.closest( '[data-slot-type]' );
|
|
42
|
+
expect( suffix ).toHaveAttribute( 'data-slot-type', 'suffix' );
|
|
43
|
+
/* eslint-enable testing-library/no-node-access */
|
|
44
|
+
} );
|
|
45
|
+
|
|
46
|
+
it( 'does not render slot wrappers when prefix/suffix are empty', () => {
|
|
47
|
+
render( <InputLayout data-testid="layout" /> );
|
|
48
|
+
|
|
49
|
+
const layout = screen.getByTestId( 'layout' );
|
|
50
|
+
/* eslint-disable testing-library/no-node-access */
|
|
51
|
+
expect(
|
|
52
|
+
layout.querySelector( '[data-slot-type]' )
|
|
53
|
+
).not.toBeInTheDocument();
|
|
54
|
+
/* eslint-enable testing-library/no-node-access */
|
|
55
|
+
} );
|
|
23
56
|
} );
|
|
@@ -24,17 +24,8 @@ export interface InputLayoutProps
|
|
|
24
24
|
suffix?: React.ReactNode;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export type InputLayoutSlotType = 'prefix' | 'suffix';
|
|
28
|
-
|
|
29
27
|
export interface InputLayoutSlotProps
|
|
30
|
-
extends
|
|
31
|
-
/**
|
|
32
|
-
* The type of the slot.
|
|
33
|
-
*
|
|
34
|
-
* When not provided, the type will be automatically inferred from the
|
|
35
|
-
* `InputLayout` context if the slot is used within a `prefix` or `suffix`.
|
|
36
|
-
*/
|
|
37
|
-
type?: InputLayoutSlotType;
|
|
28
|
+
extends React.HTMLAttributes< HTMLDivElement > {
|
|
38
29
|
/**
|
|
39
30
|
* The padding of the slot.
|
|
40
31
|
*
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ export * as Dialog from './dialog';
|
|
|
4
4
|
export * from './form/primitives';
|
|
5
5
|
export * from './icon';
|
|
6
6
|
export * from './icon-button';
|
|
7
|
+
export * as Notice from './notice';
|
|
7
8
|
export * from './stack';
|
|
8
9
|
export * as Tabs from './tabs';
|
|
9
10
|
export * as Tooltip from './tooltip';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { forwardRef } from '@wordpress/element';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import { Button } from '../button';
|
|
4
|
+
import type { ActionButtonProps } from './types';
|
|
5
|
+
import styles from './style.module.css';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* An action button for use within Notice.Actions.
|
|
9
|
+
*/
|
|
10
|
+
export const ActionButton = forwardRef< HTMLButtonElement, ActionButtonProps >(
|
|
11
|
+
function NoticeActionButton(
|
|
12
|
+
{ className, loading, loadingAnnouncement, variant, ...props },
|
|
13
|
+
ref
|
|
14
|
+
) {
|
|
15
|
+
const loadingProps =
|
|
16
|
+
loading !== undefined
|
|
17
|
+
? { loading, loadingAnnouncement: loadingAnnouncement ?? '' }
|
|
18
|
+
: {};
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Button
|
|
22
|
+
{ ...props }
|
|
23
|
+
{ ...loadingProps }
|
|
24
|
+
ref={ ref }
|
|
25
|
+
size="compact"
|
|
26
|
+
tone="neutral"
|
|
27
|
+
variant={ variant }
|
|
28
|
+
className={ clsx(
|
|
29
|
+
styles[ 'action-button' ],
|
|
30
|
+
styles[ `is-action-button-${ variant }` ],
|
|
31
|
+
className
|
|
32
|
+
) }
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { forwardRef } from '@wordpress/element';
|
|
2
|
+
import { useRender, mergeProps } from '@base-ui/react';
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
import type { ActionLinkProps } from './types';
|
|
5
|
+
import styles from './style.module.css';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* An action link for use within Notice.Actions.
|
|
9
|
+
*
|
|
10
|
+
* TODO: This should use a shared Link component.
|
|
11
|
+
*/
|
|
12
|
+
export const ActionLink = forwardRef< HTMLAnchorElement, ActionLinkProps >(
|
|
13
|
+
function NoticeActionLink( { className, render, ...props }, ref ) {
|
|
14
|
+
const element = useRender( {
|
|
15
|
+
defaultTagName: 'a',
|
|
16
|
+
render,
|
|
17
|
+
ref,
|
|
18
|
+
props: mergeProps< 'a' >(
|
|
19
|
+
{
|
|
20
|
+
className: clsx( styles[ 'action-link' ], className ),
|
|
21
|
+
},
|
|
22
|
+
props
|
|
23
|
+
),
|
|
24
|
+
} );
|
|
25
|
+
|
|
26
|
+
return element;
|
|
27
|
+
}
|
|
28
|
+
);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { forwardRef } from '@wordpress/element';
|
|
2
|
+
import { useRender, mergeProps } from '@base-ui/react';
|
|
3
|
+
import type { ActionsProps } from './types';
|
|
4
|
+
import styles from './style.module.css';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A container for Notice.ActionButton and Notice.ActionLink.
|
|
8
|
+
*/
|
|
9
|
+
export const Actions = forwardRef< HTMLDivElement, ActionsProps >(
|
|
10
|
+
function NoticeActions( { render, ...props }, ref ) {
|
|
11
|
+
const element = useRender( {
|
|
12
|
+
defaultTagName: 'div',
|
|
13
|
+
render,
|
|
14
|
+
ref,
|
|
15
|
+
props: mergeProps< 'div' >(
|
|
16
|
+
{
|
|
17
|
+
className: styles.actions,
|
|
18
|
+
},
|
|
19
|
+
props
|
|
20
|
+
),
|
|
21
|
+
} );
|
|
22
|
+
|
|
23
|
+
return element;
|
|
24
|
+
}
|
|
25
|
+
);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { forwardRef } from '@wordpress/element';
|
|
2
|
+
import { __ } from '@wordpress/i18n';
|
|
3
|
+
import { closeSmall } from '@wordpress/icons';
|
|
4
|
+
import clsx from 'clsx';
|
|
5
|
+
import { IconButton } from '../icon-button';
|
|
6
|
+
import type { CloseIconProps } from './types';
|
|
7
|
+
import styles from './style.module.css';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The close button for a notice. Renders an icon button with a close icon.
|
|
11
|
+
*/
|
|
12
|
+
export const CloseIcon = forwardRef< HTMLButtonElement, CloseIconProps >(
|
|
13
|
+
function NoticeCloseIcon(
|
|
14
|
+
{ className, icon = closeSmall, label = __( 'Dismiss' ), ...props },
|
|
15
|
+
ref
|
|
16
|
+
) {
|
|
17
|
+
return (
|
|
18
|
+
<IconButton
|
|
19
|
+
{ ...props }
|
|
20
|
+
ref={ ref }
|
|
21
|
+
className={ clsx( styles[ 'close-icon' ], className ) }
|
|
22
|
+
variant="minimal"
|
|
23
|
+
size="small"
|
|
24
|
+
tone="neutral"
|
|
25
|
+
icon={ icon }
|
|
26
|
+
label={ label }
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { forwardRef } from '@wordpress/element';
|
|
2
|
+
import { useRender, mergeProps } from '@base-ui/react';
|
|
3
|
+
import type { DescriptionProps } from './types';
|
|
4
|
+
import styles from './style.module.css';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The description text for a notice.
|
|
8
|
+
*/
|
|
9
|
+
export const Description = forwardRef< HTMLDivElement, DescriptionProps >(
|
|
10
|
+
function NoticeDescription( { render, ...props }, ref ) {
|
|
11
|
+
const element = useRender( {
|
|
12
|
+
defaultTagName: 'div',
|
|
13
|
+
render,
|
|
14
|
+
ref,
|
|
15
|
+
props: mergeProps< 'div' >(
|
|
16
|
+
{ className: styles.description },
|
|
17
|
+
props
|
|
18
|
+
),
|
|
19
|
+
} );
|
|
20
|
+
|
|
21
|
+
return element;
|
|
22
|
+
}
|
|
23
|
+
);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Root } from './root';
|
|
2
|
+
import { Title } from './title';
|
|
3
|
+
import { Description } from './description';
|
|
4
|
+
import { Actions } from './actions';
|
|
5
|
+
import { CloseIcon } from './close-icon';
|
|
6
|
+
import { ActionButton } from './action-button';
|
|
7
|
+
import { ActionLink } from './action-link';
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
Root,
|
|
11
|
+
Title,
|
|
12
|
+
Description,
|
|
13
|
+
Actions,
|
|
14
|
+
CloseIcon,
|
|
15
|
+
ActionButton,
|
|
16
|
+
ActionLink,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type { NoticeIntent } from './types';
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { speak } from '@wordpress/a11y';
|
|
2
|
+
import { forwardRef, renderToString, useEffect } from '@wordpress/element';
|
|
3
|
+
import { info, published, error, caution } from '@wordpress/icons';
|
|
4
|
+
import { useRender, mergeProps } from '@base-ui/react';
|
|
5
|
+
import clsx from 'clsx';
|
|
6
|
+
import { Icon } from '../icon';
|
|
7
|
+
import resetStyles from '../utils/css/resets.module.css';
|
|
8
|
+
import type { NoticeIntent, RootProps } from './types';
|
|
9
|
+
import type { IconProps } from '../icon/types';
|
|
10
|
+
import styles from './style.module.css';
|
|
11
|
+
|
|
12
|
+
const icons: { [ key in NoticeIntent ]: IconProps[ 'icon' ] | null } = {
|
|
13
|
+
neutral: null,
|
|
14
|
+
info,
|
|
15
|
+
warning: caution,
|
|
16
|
+
success: published,
|
|
17
|
+
error,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Returns the default politeness level based on the notice intent.
|
|
22
|
+
* Error uses 'assertive' for urgent announcements, others use 'polite'.
|
|
23
|
+
*/
|
|
24
|
+
function getDefaultPoliteness( intent: NoticeIntent ): 'polite' | 'assertive' {
|
|
25
|
+
return intent === 'error' ? 'assertive' : 'polite';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Safely converts a message to a string for screen reader announcement.
|
|
30
|
+
* Returns undefined if the message can't be safely serialized.
|
|
31
|
+
*/
|
|
32
|
+
function safeRenderToString( message: RootProps[ 'spokenMessage' ] ) {
|
|
33
|
+
if ( ! message ) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
if ( typeof message === 'string' ) {
|
|
37
|
+
return message;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
return renderToString( message );
|
|
41
|
+
} catch {
|
|
42
|
+
// If renderToString fails (e.g., due to complex components like Tooltip),
|
|
43
|
+
// return undefined and skip the announcement
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Custom hook which announces the message with the given politeness.
|
|
50
|
+
*/
|
|
51
|
+
function useSpokenMessage(
|
|
52
|
+
message: RootProps[ 'spokenMessage' ],
|
|
53
|
+
politeness: 'polite' | 'assertive'
|
|
54
|
+
) {
|
|
55
|
+
const spokenMessage = safeRenderToString( message );
|
|
56
|
+
|
|
57
|
+
useEffect( () => {
|
|
58
|
+
if ( spokenMessage ) {
|
|
59
|
+
speak( spokenMessage, politeness );
|
|
60
|
+
}
|
|
61
|
+
}, [ spokenMessage, politeness ] );
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A notice component that communicates system status and provides actions.
|
|
66
|
+
*
|
|
67
|
+
* ```jsx
|
|
68
|
+
* import { Notice } from '@wordpress/ui';
|
|
69
|
+
*
|
|
70
|
+
* function MyComponent() {
|
|
71
|
+
* return (
|
|
72
|
+
* <Notice.Root intent="info">
|
|
73
|
+
* <Notice.Title>Heading</Notice.Title>
|
|
74
|
+
* <Notice.Description>Body text</Notice.Description>
|
|
75
|
+
* <Notice.Actions>
|
|
76
|
+
* <Notice.ActionButton>Action</Notice.ActionButton>
|
|
77
|
+
* </Notice.Actions>
|
|
78
|
+
* <Notice.CloseIcon onClick={() => {}} />
|
|
79
|
+
* </Notice.Root>
|
|
80
|
+
* );
|
|
81
|
+
* }
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export const Root = forwardRef< HTMLDivElement, RootProps >( function Notice(
|
|
85
|
+
{
|
|
86
|
+
intent = 'neutral',
|
|
87
|
+
children,
|
|
88
|
+
icon,
|
|
89
|
+
spokenMessage = children,
|
|
90
|
+
politeness = getDefaultPoliteness( intent ),
|
|
91
|
+
render,
|
|
92
|
+
...restProps
|
|
93
|
+
},
|
|
94
|
+
ref
|
|
95
|
+
) {
|
|
96
|
+
// Announce to screen readers via speak() API - no role attribute needed
|
|
97
|
+
// as it would cause double announcements
|
|
98
|
+
useSpokenMessage( spokenMessage, politeness );
|
|
99
|
+
|
|
100
|
+
const iconElement = icon === null ? null : icon ?? icons[ intent ];
|
|
101
|
+
|
|
102
|
+
const mergedClassName = clsx(
|
|
103
|
+
styles.notice,
|
|
104
|
+
styles[ `is-${ intent }` ],
|
|
105
|
+
resetStyles[ 'box-sizing' ]
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const element = useRender( {
|
|
109
|
+
defaultTagName: 'div',
|
|
110
|
+
render,
|
|
111
|
+
ref,
|
|
112
|
+
props: mergeProps< 'div' >(
|
|
113
|
+
{
|
|
114
|
+
className: mergedClassName,
|
|
115
|
+
children: (
|
|
116
|
+
<>
|
|
117
|
+
{ children }
|
|
118
|
+
{ iconElement && (
|
|
119
|
+
<Icon
|
|
120
|
+
className={ styles.icon }
|
|
121
|
+
icon={ iconElement }
|
|
122
|
+
/>
|
|
123
|
+
) }
|
|
124
|
+
</>
|
|
125
|
+
),
|
|
126
|
+
},
|
|
127
|
+
restProps
|
|
128
|
+
),
|
|
129
|
+
} );
|
|
130
|
+
|
|
131
|
+
return element;
|
|
132
|
+
} );
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import * as Notice from '../index';
|
|
3
|
+
|
|
4
|
+
const meta: Meta< typeof Notice.Root > = {
|
|
5
|
+
title: 'Design System/Components/Notice',
|
|
6
|
+
component: Notice.Root,
|
|
7
|
+
subcomponents: {
|
|
8
|
+
'Notice.Title': Notice.Title,
|
|
9
|
+
'Notice.Description': Notice.Description,
|
|
10
|
+
'Notice.Actions': Notice.Actions,
|
|
11
|
+
'Notice.CloseIcon': Notice.CloseIcon,
|
|
12
|
+
'Notice.ActionButton': Notice.ActionButton,
|
|
13
|
+
'Notice.ActionLink': Notice.ActionLink,
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
export default meta;
|
|
17
|
+
|
|
18
|
+
type Story = StoryObj< typeof Notice.Root >;
|
|
19
|
+
|
|
20
|
+
export const Default: Story = {
|
|
21
|
+
args: {
|
|
22
|
+
children: (
|
|
23
|
+
<>
|
|
24
|
+
<Notice.Title>Notice Title</Notice.Title>
|
|
25
|
+
<Notice.Description>
|
|
26
|
+
Description text with details about this notification.
|
|
27
|
+
</Notice.Description>
|
|
28
|
+
<Notice.Actions>
|
|
29
|
+
<Notice.ActionButton>Primary button</Notice.ActionButton>
|
|
30
|
+
<Notice.ActionButton variant="outline">
|
|
31
|
+
Secondary button
|
|
32
|
+
</Notice.ActionButton>
|
|
33
|
+
<Notice.ActionLink href="#">Link</Notice.ActionLink>
|
|
34
|
+
</Notice.Actions>
|
|
35
|
+
<Notice.CloseIcon />
|
|
36
|
+
</>
|
|
37
|
+
),
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const Info: Story = {
|
|
42
|
+
...Default,
|
|
43
|
+
args: {
|
|
44
|
+
...Default.args,
|
|
45
|
+
intent: 'info',
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const Warning: Story = {
|
|
50
|
+
...Default,
|
|
51
|
+
args: {
|
|
52
|
+
...Default.args,
|
|
53
|
+
intent: 'warning',
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const Success: Story = {
|
|
58
|
+
...Default,
|
|
59
|
+
args: {
|
|
60
|
+
...Default.args,
|
|
61
|
+
intent: 'success',
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const Error: Story = {
|
|
66
|
+
...Default,
|
|
67
|
+
args: {
|
|
68
|
+
...Default.args,
|
|
69
|
+
intent: 'error',
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Omit Notice.CloseIcon to make the notice non-dismissable.
|
|
75
|
+
*/
|
|
76
|
+
export const NonDismissible: Story = {
|
|
77
|
+
args: {
|
|
78
|
+
intent: 'warning',
|
|
79
|
+
children: (
|
|
80
|
+
<>
|
|
81
|
+
<Notice.Title>Action Required</Notice.Title>
|
|
82
|
+
<Notice.Description>
|
|
83
|
+
This notice cannot be dismissed by the user.
|
|
84
|
+
</Notice.Description>
|
|
85
|
+
<Notice.Actions>
|
|
86
|
+
<Notice.ActionButton>Take Action</Notice.ActionButton>
|
|
87
|
+
<Notice.ActionLink href="#">Visit link</Notice.ActionLink>
|
|
88
|
+
</Notice.Actions>
|
|
89
|
+
</>
|
|
90
|
+
),
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Pass `icon={ null }` to hide the default decorative icon.
|
|
96
|
+
*/
|
|
97
|
+
export const WithoutIcon: Story = {
|
|
98
|
+
args: {
|
|
99
|
+
intent: 'info',
|
|
100
|
+
icon: null,
|
|
101
|
+
children: (
|
|
102
|
+
<>
|
|
103
|
+
<Notice.Title>No Icon</Notice.Title>
|
|
104
|
+
<Notice.Description>
|
|
105
|
+
This notice has no decorative icon displayed.
|
|
106
|
+
</Notice.Description>
|
|
107
|
+
<Notice.CloseIcon />
|
|
108
|
+
</>
|
|
109
|
+
),
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const WithoutActions: Story = {
|
|
114
|
+
args: {
|
|
115
|
+
intent: 'info',
|
|
116
|
+
children: (
|
|
117
|
+
<>
|
|
118
|
+
<Notice.Title>Simple Notice</Notice.Title>
|
|
119
|
+
<Notice.Description>
|
|
120
|
+
A dismissable notice without any action buttons or links.
|
|
121
|
+
</Notice.Description>
|
|
122
|
+
<Notice.CloseIcon />
|
|
123
|
+
</>
|
|
124
|
+
),
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Title only, no description or actions.
|
|
130
|
+
*/
|
|
131
|
+
export const TitleOnly: Story = {
|
|
132
|
+
args: {
|
|
133
|
+
children: (
|
|
134
|
+
<>
|
|
135
|
+
<Notice.Title>Just a title</Notice.Title>
|
|
136
|
+
<Notice.CloseIcon />
|
|
137
|
+
</>
|
|
138
|
+
),
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Description only, no title or actions.
|
|
144
|
+
*/
|
|
145
|
+
export const DescriptionOnly: Story = {
|
|
146
|
+
args: {
|
|
147
|
+
intent: 'info',
|
|
148
|
+
children: (
|
|
149
|
+
<>
|
|
150
|
+
<Notice.Description>
|
|
151
|
+
Just a description without title or actions.
|
|
152
|
+
</Notice.Description>
|
|
153
|
+
<Notice.CloseIcon />
|
|
154
|
+
</>
|
|
155
|
+
),
|
|
156
|
+
},
|
|
157
|
+
};
|