newspack-components 4.7.0 → 4.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 +20 -0
- package/DEVELOPMENT.md +42 -21
- package/dist/cjs/_mixins.scss +13 -0
- package/dist/cjs/_variables.scss +12 -0
- package/dist/cjs/action-card/index.js +18 -10
- package/dist/cjs/action-card/index.test.js +91 -0
- package/dist/cjs/action-card/style.scss +2 -40
- package/dist/cjs/button/index.js +8 -3
- package/dist/cjs/button/index.test.js +61 -5
- package/dist/cjs/card/core-card.js +0 -2
- package/dist/cjs/card/style-core.scss +7 -5
- package/dist/cjs/card-feature/index.js +75 -61
- package/dist/cjs/card-feature/index.test.js +396 -54
- package/dist/cjs/card-feature/style.scss +20 -31
- package/dist/cjs/card-form/index.js +5 -5
- package/dist/cjs/card-sortable-list/index.js +5 -3
- package/dist/cjs/card-sortable-list/index.test.js +50 -0
- package/dist/cjs/collapsible-group/context.js +19 -0
- package/dist/cjs/collapsible-group/index.js +24 -0
- package/dist/cjs/collapsible-group/index.test.js +215 -0
- package/dist/cjs/collapsible-group/item.js +61 -0
- package/dist/cjs/collapsible-group/root.js +62 -0
- package/dist/cjs/collapsible-group/style.scss +47 -0
- package/dist/cjs/collapsible-group/types.js +5 -0
- package/dist/cjs/empty-state/actions.js +45 -0
- package/dist/cjs/empty-state/context.js +41 -0
- package/dist/cjs/empty-state/header.js +66 -0
- package/dist/cjs/empty-state/index.js +31 -0
- package/dist/cjs/empty-state/index.test.js +303 -0
- package/dist/cjs/empty-state/root.js +56 -0
- package/dist/cjs/empty-state/style.scss +51 -0
- package/dist/cjs/empty-state/types.js +5 -0
- package/dist/cjs/index.js +34 -20
- package/dist/cjs/info-button/index.js +59 -43
- package/dist/cjs/info-button/index.test.js +199 -0
- package/dist/cjs/info-button/style.scss +41 -9
- package/dist/cjs/modal/style.scss +12 -12
- package/dist/cjs/newspack-icon/style.scss +4 -4
- package/dist/cjs/notice/style.scss +3 -3
- package/dist/cjs/page/style.scss +16 -16
- package/dist/cjs/plugin-installer/index.js +5 -9
- package/dist/cjs/plugin-installer/style.scss +3 -14
- package/dist/cjs/section-header/index.js +31 -18
- package/dist/cjs/section-header/index.test.js +40 -0
- package/dist/cjs/section-header/style.scss +22 -6
- package/dist/cjs/settings/DateRangeSetting.js +309 -0
- package/dist/cjs/settings/SettingsSection.js +19 -5
- package/dist/cjs/settings/SettingsSection.test.js +49 -0
- package/dist/cjs/settings/index.js +3 -1
- package/dist/cjs/settings/style.scss +40 -7
- package/dist/cjs/stat-card/body.js +41 -0
- package/dist/cjs/stat-card/constants.js +11 -0
- package/dist/cjs/stat-card/context.js +54 -0
- package/dist/cjs/stat-card/delta.js +91 -0
- package/dist/cjs/stat-card/footer.js +72 -0
- package/dist/cjs/stat-card/index.js +47 -0
- package/dist/cjs/stat-card/index.test.js +938 -0
- package/dist/cjs/stat-card/label.js +66 -0
- package/dist/cjs/stat-card/root.js +69 -0
- package/dist/cjs/stat-card/secondary.js +38 -0
- package/dist/cjs/stat-card/style.scss +89 -0
- package/dist/cjs/stat-card/types.js +5 -0
- package/dist/cjs/stat-card/value.js +78 -0
- package/dist/cjs/status-indicator/index.js +66 -0
- package/dist/cjs/status-indicator/index.test.js +101 -0
- package/dist/cjs/status-indicator/statuses.js +39 -0
- package/dist/cjs/status-indicator/style.scss +9 -0
- package/dist/cjs/status-indicator/types.js +5 -0
- package/dist/cjs/style.scss +9 -9
- package/dist/cjs/tabbed-navigation/style.scss +6 -4
- package/dist/cjs/types.js +5 -0
- package/dist/cjs/web-preview/style.scss +11 -11
- package/dist/cjs/with-wizard/style.scss +10 -10
- package/dist/cjs/with-wizard-screen/style.scss +9 -9
- package/dist/cjs/wizard/index.js +33 -4
- package/dist/cjs/wizard/store/index.js +23 -1
- package/dist/esm/_mixins.scss +13 -0
- package/dist/esm/_variables.scss +12 -0
- package/dist/esm/action-card/index.js +18 -10
- package/dist/esm/action-card/index.test.js +89 -0
- package/dist/esm/action-card/style.scss +2 -40
- package/dist/esm/button/index.js +9 -4
- package/dist/esm/button/index.test.js +63 -5
- package/dist/esm/card/core-card.js +0 -2
- package/dist/esm/card/style-core.scss +7 -5
- package/dist/esm/card-feature/index.js +78 -62
- package/dist/esm/card-feature/index.test.js +397 -55
- package/dist/esm/card-feature/style.scss +20 -31
- package/dist/esm/card-form/index.js +5 -5
- package/dist/esm/card-sortable-list/index.js +6 -4
- package/dist/esm/card-sortable-list/index.test.js +46 -0
- package/dist/esm/collapsible-group/context.js +13 -0
- package/dist/esm/collapsible-group/index.js +17 -0
- package/dist/esm/collapsible-group/index.test.js +215 -0
- package/dist/esm/collapsible-group/item.js +53 -0
- package/dist/esm/collapsible-group/root.js +54 -0
- package/dist/esm/collapsible-group/style.scss +47 -0
- package/dist/esm/collapsible-group/types.js +1 -0
- package/dist/esm/empty-state/actions.js +37 -0
- package/dist/esm/empty-state/context.js +35 -0
- package/dist/esm/empty-state/header.js +58 -0
- package/dist/esm/empty-state/index.js +23 -0
- package/dist/esm/empty-state/index.test.js +299 -0
- package/dist/esm/empty-state/root.js +56 -0
- package/dist/esm/empty-state/style.scss +51 -0
- package/dist/esm/empty-state/types.js +1 -0
- package/dist/esm/index.js +4 -2
- package/dist/esm/info-button/index.js +54 -43
- package/dist/esm/info-button/index.test.js +195 -0
- package/dist/esm/info-button/style.scss +41 -9
- package/dist/esm/modal/style.scss +12 -12
- package/dist/esm/newspack-icon/style.scss +4 -4
- package/dist/esm/notice/style.scss +3 -3
- package/dist/esm/page/style.scss +16 -16
- package/dist/esm/plugin-installer/index.js +5 -9
- package/dist/esm/plugin-installer/style.scss +3 -14
- package/dist/esm/section-header/index.js +27 -14
- package/dist/esm/section-header/index.test.js +36 -0
- package/dist/esm/section-header/style.scss +22 -6
- package/dist/esm/settings/DateRangeSetting.js +304 -0
- package/dist/esm/settings/SettingsSection.js +16 -3
- package/dist/esm/settings/SettingsSection.test.js +45 -0
- package/dist/esm/settings/index.js +3 -1
- package/dist/esm/settings/style.scss +40 -7
- package/dist/esm/stat-card/body.js +36 -0
- package/dist/esm/stat-card/constants.js +5 -0
- package/dist/esm/stat-card/context.js +48 -0
- package/dist/esm/stat-card/delta.js +86 -0
- package/dist/esm/stat-card/footer.js +67 -0
- package/dist/esm/stat-card/index.js +33 -0
- package/dist/esm/stat-card/index.test.js +939 -0
- package/dist/esm/stat-card/label.js +61 -0
- package/dist/esm/stat-card/root.js +64 -0
- package/dist/esm/stat-card/secondary.js +33 -0
- package/dist/esm/stat-card/style.scss +89 -0
- package/dist/esm/stat-card/types.js +1 -0
- package/dist/esm/stat-card/value.js +73 -0
- package/dist/esm/status-indicator/index.js +52 -0
- package/dist/esm/status-indicator/index.test.js +96 -0
- package/dist/esm/status-indicator/statuses.js +33 -0
- package/dist/esm/status-indicator/style.scss +9 -0
- package/dist/esm/status-indicator/types.js +1 -0
- package/dist/esm/style.scss +9 -9
- package/dist/esm/tabbed-navigation/style.scss +6 -4
- package/dist/esm/types.js +1 -0
- package/dist/esm/web-preview/style.scss +11 -11
- package/dist/esm/with-wizard/style.scss +10 -10
- package/dist/esm/with-wizard-screen/style.scss +9 -9
- package/dist/esm/wizard/index.js +38 -7
- package/dist/esm/wizard/store/index.js +23 -1
- package/package.json +1 -1
- package/src/_mixins.scss +13 -0
- package/src/_variables.scss +12 -0
- package/src/action-card/action-card.d.ts +3 -2
- package/src/action-card/index.js +12 -12
- package/src/action-card/index.test.js +56 -0
- package/src/action-card/style.scss +2 -40
- package/src/button/index.test.js +52 -5
- package/src/button/index.tsx +9 -4
- package/src/card/core-card.js +0 -2
- package/src/card/style-core.scss +7 -5
- package/src/card-feature/README.md +68 -27
- package/src/card-feature/index.test.js +242 -27
- package/src/card-feature/index.tsx +77 -67
- package/src/card-feature/style.scss +20 -31
- package/src/card-form/README.md +10 -10
- package/src/card-form/index.tsx +4 -8
- package/src/card-sortable-list/README.md +4 -5
- package/src/card-sortable-list/index.test.js +29 -0
- package/src/card-sortable-list/index.tsx +7 -4
- package/src/collapsible-group/README.md +50 -0
- package/src/collapsible-group/context.ts +13 -0
- package/src/collapsible-group/index.test.js +168 -0
- package/src/collapsible-group/index.tsx +17 -0
- package/src/collapsible-group/item.tsx +42 -0
- package/src/collapsible-group/root.tsx +42 -0
- package/src/collapsible-group/style.scss +47 -0
- package/src/collapsible-group/types.ts +22 -0
- package/src/drawer/README.md +2 -2
- package/src/empty-state/README.md +165 -0
- package/src/empty-state/actions.tsx +37 -0
- package/src/empty-state/context.ts +40 -0
- package/src/empty-state/header.tsx +48 -0
- package/src/empty-state/index.test.js +247 -0
- package/src/empty-state/index.tsx +22 -0
- package/src/empty-state/root.tsx +39 -0
- package/src/empty-state/style.scss +51 -0
- package/src/empty-state/types.ts +33 -0
- package/src/index.js +4 -2
- package/src/info-button/README.md +115 -0
- package/src/info-button/index.test.js +163 -0
- package/src/info-button/index.tsx +64 -0
- package/src/info-button/style.scss +41 -9
- package/src/modal/style.scss +12 -12
- package/src/newspack-icon/style.scss +4 -4
- package/src/notice/style.scss +3 -3
- package/src/page/style.scss +16 -16
- package/src/plugin-installer/index.js +3 -9
- package/src/plugin-installer/style.scss +3 -14
- package/src/section-header/index.js +27 -14
- package/src/section-header/index.test.js +28 -0
- package/src/section-header/style.scss +22 -6
- package/src/settings/DateRangeSetting.js +266 -0
- package/src/settings/SettingsSection.js +24 -4
- package/src/settings/SettingsSection.test.js +45 -0
- package/src/settings/index.js +2 -0
- package/src/settings/style.scss +40 -7
- package/src/stat-card/README.md +434 -0
- package/src/stat-card/body.tsx +28 -0
- package/src/stat-card/constants.ts +5 -0
- package/src/stat-card/context.ts +53 -0
- package/src/stat-card/delta.tsx +88 -0
- package/src/stat-card/footer.tsx +68 -0
- package/src/stat-card/index.test.js +818 -0
- package/src/stat-card/index.tsx +48 -0
- package/src/stat-card/label.tsx +61 -0
- package/src/stat-card/root.tsx +40 -0
- package/src/stat-card/secondary.tsx +27 -0
- package/src/stat-card/style.scss +89 -0
- package/src/stat-card/types.ts +84 -0
- package/src/stat-card/value.tsx +72 -0
- package/src/status-indicator/README.md +123 -0
- package/src/status-indicator/index.test.js +70 -0
- package/src/status-indicator/index.tsx +39 -0
- package/src/status-indicator/statuses.ts +35 -0
- package/src/status-indicator/style.scss +9 -0
- package/src/status-indicator/types.ts +28 -0
- package/src/style.scss +9 -9
- package/src/tabbed-navigation/style.scss +6 -4
- package/src/types.ts +25 -0
- package/src/web-preview/style.scss +11 -11
- package/src/with-wizard/style.scss +10 -10
- package/src/with-wizard-screen/style.scss +9 -9
- package/src/wizard/index.js +49 -7
- package/src/wizard/store/index.js +26 -1
- package/dist/cjs/accordion/index.js +0 -72
- package/dist/cjs/accordion/index.test.js +0 -80
- package/dist/cjs/accordion/style.scss +0 -35
- package/dist/cjs/badge/index.js +0 -32
- package/dist/cjs/badge/style.scss +0 -36
- package/dist/esm/accordion/index.js +0 -63
- package/dist/esm/accordion/index.test.js +0 -79
- package/dist/esm/accordion/style.scss +0 -35
- package/dist/esm/badge/index.js +0 -24
- package/dist/esm/badge/style.scss +0 -36
- package/src/accordion/index.js +0 -46
- package/src/accordion/index.test.js +0 -62
- package/src/accordion/style.scss +0 -35
- package/src/badge/index.tsx +0 -26
- package/src/badge/style.scss +0 -36
- package/src/info-button/index.js +0 -38
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StatCard
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Internal dependencies.
|
|
7
|
+
*/
|
|
8
|
+
import Body from './body';
|
|
9
|
+
import Delta from './delta';
|
|
10
|
+
import Footer from './footer';
|
|
11
|
+
import Label from './label';
|
|
12
|
+
import Root from './root';
|
|
13
|
+
import Secondary from './secondary';
|
|
14
|
+
import Value from './value';
|
|
15
|
+
|
|
16
|
+
export { STAT_CARD_NULL_GLYPH } from './constants';
|
|
17
|
+
export type {
|
|
18
|
+
StatCardBodyProps,
|
|
19
|
+
StatCardDeltaDirection,
|
|
20
|
+
StatCardDeltaProps,
|
|
21
|
+
StatCardDeltaTone,
|
|
22
|
+
StatCardFooterProps,
|
|
23
|
+
StatCardHeadingLevel,
|
|
24
|
+
StatCardLabelProps,
|
|
25
|
+
StatCardLabels,
|
|
26
|
+
StatCardRootProps,
|
|
27
|
+
StatCardSecondaryProps,
|
|
28
|
+
StatCardValue,
|
|
29
|
+
StatCardValueProps,
|
|
30
|
+
StatCardValueVariant,
|
|
31
|
+
} from './types';
|
|
32
|
+
|
|
33
|
+
// Compound components here export one namespace object, as Drawer does.
|
|
34
|
+
export const StatCard = {
|
|
35
|
+
Root,
|
|
36
|
+
Label,
|
|
37
|
+
Body,
|
|
38
|
+
Value,
|
|
39
|
+
Delta,
|
|
40
|
+
Secondary,
|
|
41
|
+
Footer,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
Object.entries( StatCard ).forEach( ( [ name, part ] ) => {
|
|
45
|
+
( part as { displayName?: string } ).displayName = `StatCard.${ name }`;
|
|
46
|
+
} );
|
|
47
|
+
|
|
48
|
+
export default StatCard;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies.
|
|
3
|
+
*/
|
|
4
|
+
import { forwardRef, useEffect } from '@wordpress/element';
|
|
5
|
+
import { Stack } from '@wordpress/ui';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* External dependencies.
|
|
9
|
+
*/
|
|
10
|
+
import classnames from 'classnames';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Internal dependencies.
|
|
14
|
+
*/
|
|
15
|
+
import { useStatCardContext } from './context';
|
|
16
|
+
import type { StatCardHeadingLevel, StatCardLabelProps } from './types';
|
|
17
|
+
|
|
18
|
+
const headings = {
|
|
19
|
+
2: 'h2',
|
|
20
|
+
3: 'h3',
|
|
21
|
+
4: 'h4',
|
|
22
|
+
5: 'h5',
|
|
23
|
+
6: 'h6',
|
|
24
|
+
} as const;
|
|
25
|
+
|
|
26
|
+
const Label = forwardRef< HTMLDivElement, StatCardLabelProps >( function Label( { suffix, heading, className, children, ...props }, ref ) {
|
|
27
|
+
const context = useStatCardContext();
|
|
28
|
+
const level = ( heading ?? context.heading ) as StatCardHeadingLevel;
|
|
29
|
+
// Consumers are largely untyped JS, where an out-of-range level would
|
|
30
|
+
// otherwise render an <h7>, which carries no heading role at all.
|
|
31
|
+
const Heading = headings[ level ] || headings[ 3 ];
|
|
32
|
+
|
|
33
|
+
useEffect( () => {
|
|
34
|
+
if ( 'production' === process.env.NODE_ENV || headings[ level ] ) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// eslint-disable-next-line no-console
|
|
38
|
+
console.warn(
|
|
39
|
+
`StatCard: unknown heading level "${ level }", falling back to 3. Set \`heading\` on StatCard.Root or StatCard.Label to one of ${ Object.keys(
|
|
40
|
+
headings
|
|
41
|
+
).join( ', ' ) }.`
|
|
42
|
+
);
|
|
43
|
+
}, [ level ] );
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<Stack
|
|
47
|
+
ref={ ref }
|
|
48
|
+
direction="row"
|
|
49
|
+
align="flex-start"
|
|
50
|
+
justify="space-between"
|
|
51
|
+
gap="sm"
|
|
52
|
+
className={ classnames( 'newspack-stat-card__label', className ) }
|
|
53
|
+
{ ...props }
|
|
54
|
+
>
|
|
55
|
+
<Heading className="newspack-stat-card__label-text">{ children }</Heading>
|
|
56
|
+
{ suffix }
|
|
57
|
+
</Stack>
|
|
58
|
+
);
|
|
59
|
+
} );
|
|
60
|
+
|
|
61
|
+
export default Label;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies.
|
|
3
|
+
*/
|
|
4
|
+
import { forwardRef, useMemo } from '@wordpress/element';
|
|
5
|
+
// Aliased: this package exports a different `Card` of its own.
|
|
6
|
+
import { Card as UICard, Stack } from '@wordpress/ui';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* External dependencies.
|
|
10
|
+
*/
|
|
11
|
+
import classnames from 'classnames';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Internal dependencies.
|
|
15
|
+
*/
|
|
16
|
+
import { resolveStatCardLabels, StatCardContext } from './context';
|
|
17
|
+
import type { StatCardRootProps } from './types';
|
|
18
|
+
import './style.scss';
|
|
19
|
+
|
|
20
|
+
const Root = forwardRef< HTMLDivElement, StatCardRootProps >( function Root( { heading = 3, labels, className, children, ...props }, ref ) {
|
|
21
|
+
// Keyed on the strings rather than the object: a caller writing `labels` inline
|
|
22
|
+
// hands over a fresh object each render, which would defeat the memo entirely.
|
|
23
|
+
const { notApplicable, up, down } = labels ?? {};
|
|
24
|
+
const context = useMemo(
|
|
25
|
+
() => ( { heading, labels: resolveStatCardLabels( { notApplicable, up, down } ) } ),
|
|
26
|
+
[ heading, notApplicable, up, down ]
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<StatCardContext.Provider value={ context }>
|
|
31
|
+
<UICard.Root ref={ ref } className={ classnames( 'newspack-stat-card', className ) } { ...props }>
|
|
32
|
+
<UICard.Content render={ <Stack direction="column" gap="sm" /> } className="newspack-stat-card__content">
|
|
33
|
+
{ children }
|
|
34
|
+
</UICard.Content>
|
|
35
|
+
</UICard.Root>
|
|
36
|
+
</StatCardContext.Provider>
|
|
37
|
+
);
|
|
38
|
+
} );
|
|
39
|
+
|
|
40
|
+
export default Root;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies.
|
|
3
|
+
*/
|
|
4
|
+
import { forwardRef } from '@wordpress/element';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* External dependencies.
|
|
8
|
+
*/
|
|
9
|
+
import classnames from 'classnames';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Internal dependencies.
|
|
13
|
+
*/
|
|
14
|
+
import { useStatCardContext } from './context';
|
|
15
|
+
import type { StatCardSecondaryProps } from './types';
|
|
16
|
+
|
|
17
|
+
const Secondary = forwardRef< HTMLDivElement, StatCardSecondaryProps >( function Secondary( { className, children, ...props }, ref ) {
|
|
18
|
+
useStatCardContext();
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div ref={ ref } className={ classnames( 'newspack-stat-card__secondary', className ) } { ...props }>
|
|
22
|
+
{ children }
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
} );
|
|
26
|
+
|
|
27
|
+
export default Secondary;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
@use "~@wordpress/base-styles/colors" as wp-colors;
|
|
2
|
+
@use "~@wordpress/base-styles/variables" as wp-vars;
|
|
3
|
+
@use "~@wordpress/base-styles/mixins" as wp-mixins;
|
|
4
|
+
@use "../../../colors/colors.module" as colors;
|
|
5
|
+
@use "../variables" as np-vars;
|
|
6
|
+
|
|
7
|
+
$stat-card-min-height: wp-vars.$grid-unit * 21;
|
|
8
|
+
|
|
9
|
+
.newspack-stat-card {
|
|
10
|
+
--wp-admin-theme-color: #{colors.$primary-600};
|
|
11
|
+
--wp-admin-theme-color--rgb: #{colors.$primary-600--rgb};
|
|
12
|
+
--wp-admin-theme-color-darker-10: #{colors.$primary-700};
|
|
13
|
+
--wp-admin-theme-color-darker-10--rgb: #{colors.$primary-700--rgb};
|
|
14
|
+
--wp-admin-theme-color-darker-20: #{colors.$primary-800};
|
|
15
|
+
--wp-admin-theme-color-darker-20--rgb: #{colors.$primary-800--rgb};
|
|
16
|
+
--wp-admin-theme-color-lighter-10: #{colors.$primary-000};
|
|
17
|
+
--wp-admin-theme-color-lighter-10--rgb: #{colors.$primary-000--rgb};
|
|
18
|
+
|
|
19
|
+
container-type: inline-size;
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-direction: column;
|
|
22
|
+
min-height: $stat-card-min-height;
|
|
23
|
+
|
|
24
|
+
&__content {
|
|
25
|
+
flex: 1;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
&__label .newspack-info-button {
|
|
29
|
+
margin-block: calc((#{wp-vars.$font-line-height-small} - #{wp-vars.$button-size-small}) / 2);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
&__label-text {
|
|
33
|
+
@include wp-mixins.heading-large();
|
|
34
|
+
color: wp-colors.$gray-900;
|
|
35
|
+
margin: 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
&__body {
|
|
39
|
+
flex: 1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
&__value {
|
|
43
|
+
color: var(--wp-admin-theme-color);
|
|
44
|
+
font-size: np-vars.$font-size-3x-large;
|
|
45
|
+
font-size: clamp(#{wp-vars.$font-size-x-large}, 14cqi, #{np-vars.$font-size-3x-large});
|
|
46
|
+
font-variant-numeric: tabular-nums;
|
|
47
|
+
font-weight: wp-vars.$font-weight-medium;
|
|
48
|
+
line-height: np-vars.$font-line-height-3x-large;
|
|
49
|
+
|
|
50
|
+
&--text {
|
|
51
|
+
font-size: wp-vars.$font-size-x-large;
|
|
52
|
+
line-height: wp-vars.$font-line-height-x-large;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
&__delta {
|
|
57
|
+
@include wp-mixins.heading-medium();
|
|
58
|
+
color: wp-colors.$gray-700;
|
|
59
|
+
|
|
60
|
+
&--positive {
|
|
61
|
+
color: colors.$success-600;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
&--negative {
|
|
65
|
+
color: colors.$error-600;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
&__action,
|
|
70
|
+
&__action.components-button {
|
|
71
|
+
font-size: wp-vars.$helptext-font-size;
|
|
72
|
+
line-height: wp-vars.$font-line-height-x-small;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
&__secondary {
|
|
76
|
+
@include wp-mixins.heading-medium();
|
|
77
|
+
color: var(--wp-admin-theme-color);
|
|
78
|
+
margin: 0;
|
|
79
|
+
text-wrap: balance;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
&__description {
|
|
83
|
+
color: wp-colors.$gray-700;
|
|
84
|
+
font-size: wp-vars.$helptext-font-size;
|
|
85
|
+
line-height: wp-vars.$font-line-height-x-small;
|
|
86
|
+
margin: 0;
|
|
87
|
+
text-wrap: balance;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export type StatCardHeadingLevel = 2 | 3 | 4 | 5 | 6;
|
|
2
|
+
|
|
3
|
+
export type StatCardValueVariant = 'figure' | 'text';
|
|
4
|
+
|
|
5
|
+
/** Pre-formatted by the caller. Null, undefined and a blank string all render the null glyph. */
|
|
6
|
+
export type StatCardValue = string | number | null | undefined;
|
|
7
|
+
|
|
8
|
+
type DivProps = Omit< React.ComponentPropsWithoutRef< 'div' >, 'children' >;
|
|
9
|
+
|
|
10
|
+
type SpanProps = Omit< React.ComponentPropsWithoutRef< 'span' >, 'children' >;
|
|
11
|
+
|
|
12
|
+
/** The strings the card speaks for itself, when the caller has supplied none of its own. */
|
|
13
|
+
export type StatCardLabels = {
|
|
14
|
+
/** Names the null glyph. */
|
|
15
|
+
notApplicable: string;
|
|
16
|
+
/** Spoken for an up arrow. */
|
|
17
|
+
up: string;
|
|
18
|
+
/** Spoken for a down arrow. */
|
|
19
|
+
down: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type StatCardRootProps = DivProps & {
|
|
23
|
+
/** Heading level for `StatCard.Label`, read through context. */
|
|
24
|
+
heading?: StatCardHeadingLevel;
|
|
25
|
+
/** Replaces the spoken defaults for every card underneath, e.g. from a consumer's own text domain. */
|
|
26
|
+
labels?: Partial< StatCardLabels >;
|
|
27
|
+
/** Merged onto the card, which is the element the hero scale queries. */
|
|
28
|
+
className?: string;
|
|
29
|
+
children?: React.ReactNode;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type StatCardLabelProps = DivProps & {
|
|
33
|
+
/** Rendered beside the heading rather than inside it, so a control here stays out of the document outline. */
|
|
34
|
+
suffix?: React.ReactNode;
|
|
35
|
+
/** Overrides the level set on `StatCard.Root`. */
|
|
36
|
+
heading?: StatCardHeadingLevel;
|
|
37
|
+
/** Merged onto the label row, not the heading. */
|
|
38
|
+
className?: string;
|
|
39
|
+
children?: React.ReactNode;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type StatCardBodyProps = DivProps & {
|
|
43
|
+
className?: string;
|
|
44
|
+
children?: React.ReactNode;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type StatCardValueProps = SpanProps & {
|
|
48
|
+
value: StatCardValue;
|
|
49
|
+
/** Spoken instead of the visible value, whose meaning may rest on punctuation. */
|
|
50
|
+
valueLabel?: string;
|
|
51
|
+
/** `text` drops the hero scale, for a phrase standing in for a number. */
|
|
52
|
+
variant?: StatCardValueVariant;
|
|
53
|
+
/** Rendered in a row beside the figure, e.g. a `StatCard.Delta`. */
|
|
54
|
+
suffix?: React.ReactNode;
|
|
55
|
+
className?: string;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type StatCardDeltaDirection = 'up' | 'down';
|
|
59
|
+
|
|
60
|
+
export type StatCardDeltaTone = 'positive' | 'negative' | 'neutral';
|
|
61
|
+
|
|
62
|
+
export type StatCardDeltaProps = SpanProps & {
|
|
63
|
+
/** Which arrow to show. Says nothing about whether the change is good. */
|
|
64
|
+
direction: StatCardDeltaDirection;
|
|
65
|
+
/** Which colour to use. The caller decides, because a rise is not always good news. */
|
|
66
|
+
tone?: StatCardDeltaTone;
|
|
67
|
+
/** Spoken in place of "Up" or "Down". */
|
|
68
|
+
directionLabel?: string;
|
|
69
|
+
/** Spoken in place of the whole delta, arrow and change together. Wins over `directionLabel`. */
|
|
70
|
+
label?: string;
|
|
71
|
+
className?: string;
|
|
72
|
+
/** The change, pre-formatted. Must be non-interactive: `label` hides it from assistive technology. */
|
|
73
|
+
children?: React.ReactNode;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export type StatCardSecondaryProps = DivProps & {
|
|
77
|
+
className?: string;
|
|
78
|
+
children?: React.ReactNode;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export type StatCardFooterProps = DivProps & {
|
|
82
|
+
className?: string;
|
|
83
|
+
children?: React.ReactNode;
|
|
84
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies.
|
|
3
|
+
*/
|
|
4
|
+
import { forwardRef, useEffect } from '@wordpress/element';
|
|
5
|
+
import { Stack, VisuallyHidden } from '@wordpress/ui';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* External dependencies.
|
|
9
|
+
*/
|
|
10
|
+
import classnames from 'classnames';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Internal dependencies.
|
|
14
|
+
*/
|
|
15
|
+
import { STAT_CARD_NULL_GLYPH } from './constants';
|
|
16
|
+
import { useStatCardContext } from './context';
|
|
17
|
+
import type { StatCardValueProps, StatCardValueVariant } from './types';
|
|
18
|
+
|
|
19
|
+
const variants: StatCardValueVariant[] = [ 'figure', 'text' ];
|
|
20
|
+
|
|
21
|
+
const Value = forwardRef< HTMLSpanElement, StatCardValueProps >( function Value(
|
|
22
|
+
{ value, valueLabel, variant = 'figure', suffix, className, ...props },
|
|
23
|
+
ref
|
|
24
|
+
) {
|
|
25
|
+
const { labels } = useStatCardContext();
|
|
26
|
+
|
|
27
|
+
useEffect( () => {
|
|
28
|
+
if ( 'production' === process.env.NODE_ENV || variants.includes( variant ) ) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
// eslint-disable-next-line no-console
|
|
32
|
+
console.warn( `StatCard.Value: unknown variant "${ variant }", falling back to figure. Use one of ${ variants.join( ', ' ) }.` );
|
|
33
|
+
}, [ variant ] );
|
|
34
|
+
|
|
35
|
+
// A blank string is a missing figure too, and an empty hero would read as one
|
|
36
|
+
// that never loaded. A zero is a figure, so it stays out of this.
|
|
37
|
+
const isNull = null === value || undefined === value || ( 'string' === typeof value && '' === value.trim() );
|
|
38
|
+
const shown = isNull ? STAT_CARD_NULL_GLYPH : value;
|
|
39
|
+
// Trimmed, and `||` not `??`: a blank label is a missing one, and the glyph must never be left unnamed.
|
|
40
|
+
const spoken = valueLabel?.trim() || ( isNull ? labels.notApplicable : undefined );
|
|
41
|
+
|
|
42
|
+
const figure = (
|
|
43
|
+
<span
|
|
44
|
+
ref={ ref }
|
|
45
|
+
className={ classnames( 'newspack-stat-card__value', 'text' === variant && 'newspack-stat-card__value--text', className ) }
|
|
46
|
+
{ ...props }
|
|
47
|
+
>
|
|
48
|
+
{ spoken ? (
|
|
49
|
+
<>
|
|
50
|
+
{ /* Hidden, not labelled: ARIA forbids naming a generic element, and `role="img"` announces a graphic. */ }
|
|
51
|
+
<span aria-hidden="true">{ shown }</span>
|
|
52
|
+
<VisuallyHidden render={ <span /> }>{ spoken }</VisuallyHidden>
|
|
53
|
+
</>
|
|
54
|
+
) : (
|
|
55
|
+
shown
|
|
56
|
+
) }
|
|
57
|
+
</span>
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if ( ! suffix ) {
|
|
61
|
+
return figure;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<Stack direction="row" align="baseline" gap="sm" className="newspack-stat-card__figure">
|
|
66
|
+
{ figure }
|
|
67
|
+
{ suffix }
|
|
68
|
+
</Stack>
|
|
69
|
+
);
|
|
70
|
+
} );
|
|
71
|
+
|
|
72
|
+
export default Value;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# StatusIndicator
|
|
2
|
+
|
|
3
|
+
A status glyph followed by its label, for the Status column of a DataView.
|
|
4
|
+
|
|
5
|
+
A badge is an attention marker. In a column where every row carries one it marks
|
|
6
|
+
nothing and adds a block of colour to each row, so the quiet treatment is the
|
|
7
|
+
default there and a badge is kept for the rare row that genuinely stands out,
|
|
8
|
+
such as a group with a seat request waiting on payment.
|
|
9
|
+
|
|
10
|
+
## Importing
|
|
11
|
+
|
|
12
|
+
```jsx
|
|
13
|
+
// The barrel.
|
|
14
|
+
import { StatusIndicator } from 'newspack-components';
|
|
15
|
+
|
|
16
|
+
// The component on its own.
|
|
17
|
+
import StatusIndicator from '../../packages/components/src/status-indicator';
|
|
18
|
+
|
|
19
|
+
// The vocabulary, for a column's own test. Deliberately not on the barrel: that
|
|
20
|
+
// entry also pulls `Page`'s `:root` token block and the wizards store into
|
|
21
|
+
// whichever bundle asks for it.
|
|
22
|
+
import { statusGlyph, STATUS_NAMES } from '../../packages/components/src/status-indicator';
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
Name the status and the component draws it:
|
|
28
|
+
|
|
29
|
+
```jsx
|
|
30
|
+
<StatusIndicator status="active">{ __( 'Active', 'newspack-plugin' ) }</StatusIndicator>;
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
In a field definition, where the screen maps its own status keys onto the
|
|
34
|
+
vocabulary:
|
|
35
|
+
|
|
36
|
+
```jsx
|
|
37
|
+
{
|
|
38
|
+
id: 'status',
|
|
39
|
+
label: __( 'Status', 'newspack-plugin' ),
|
|
40
|
+
getValue: ( { item } ) => item.status,
|
|
41
|
+
render: ( { item } ) => <StatusIndicator status={ STATUS_INDICATORS[ item.status ] }>{ item.status_label }</StatusIndicator>,
|
|
42
|
+
elements: statusElements,
|
|
43
|
+
filterBy: { operators: [ 'is' ] },
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Props
|
|
48
|
+
|
|
49
|
+
| Prop | Type | Required | Description |
|
|
50
|
+
| --- | --- | --- | --- |
|
|
51
|
+
| `status` | `StatusName` | one of the two | The status to draw, from the vocabulary below. |
|
|
52
|
+
| `icon` | `Icon`'s `icon` prop | one of the two | A glyph, for a field the vocabulary does not cover. |
|
|
53
|
+
| `children` | `ReactNode` | yes | The status label. `@wordpress/primitives` forces `aria-hidden` on the glyph, so this is the whole accessible name. |
|
|
54
|
+
|
|
55
|
+
`status` and `icon` are mutually exclusive, and one of them is required.
|
|
56
|
+
Anything else is spread onto the wrapper, a `Stack` from `@wordpress/ui`, which
|
|
57
|
+
takes the props of a `div`.
|
|
58
|
+
|
|
59
|
+
## The vocabulary
|
|
60
|
+
|
|
61
|
+
| Name | Glyph | What it means |
|
|
62
|
+
| --- | --- | --- |
|
|
63
|
+
| `active` | check circle | Live now: a published plan, a running subscription. |
|
|
64
|
+
| `done` | check circle | Finished successfully: a sent newsletter, a completed sync. |
|
|
65
|
+
| `scheduled` | clock | Waiting for a date to arrive. |
|
|
66
|
+
| `draft` | half circle | Not live yet, or switched off. |
|
|
67
|
+
| `pending` | part-filled circle | Waiting on something outside the publisher's hands. |
|
|
68
|
+
| `attention` | exclamation circle | Live but needing a look, usually a payment. |
|
|
69
|
+
| `error` | error | Failed, and the one state that asks the reader to act. |
|
|
70
|
+
| `progress` | update | Running right now. |
|
|
71
|
+
| `cancelled` | slash circle | Stopped on purpose. |
|
|
72
|
+
| `ended` | slash circle | Stopped because its window closed. |
|
|
73
|
+
| `private` | lock | Live, but not publicly reachable. |
|
|
74
|
+
| `trash` | trash | Binned. |
|
|
75
|
+
|
|
76
|
+
The component owns this so one meaning draws one mark everywhere. Before it
|
|
77
|
+
existed the vocabulary lived in ten screen-level maps, and had already drifted:
|
|
78
|
+
Pending was the half circle in Subscribers and the part-filled one in the
|
|
79
|
+
Audience lists.
|
|
80
|
+
|
|
81
|
+
**No two statuses in one column may draw the same mark.** A DataViews Status
|
|
82
|
+
column offers its statuses as separate filters, so two that look identical make
|
|
83
|
+
two different states indistinguishable in the one place the difference matters.
|
|
84
|
+
|
|
85
|
+
Two names may share a glyph where they read differently at the call site but
|
|
86
|
+
mean the same to a reader: a sent newsletter is finished rather than live, and
|
|
87
|
+
an ad whose window closed was not cancelled. Splitting them leaves room to draw
|
|
88
|
+
them apart later without touching a consumer. The pairs are `active`/`done` and
|
|
89
|
+
`cancelled`/`ended`, and `index.test.js` pins that list, so a column keeps the
|
|
90
|
+
rule by using distinct names and at most one half of a pair. `statusGlyph` is
|
|
91
|
+
exported for the columns that want to assert it directly.
|
|
92
|
+
|
|
93
|
+
The glyph does the separating on its own: the component inherits its colour from
|
|
94
|
+
the surrounding text and tints nothing, so no status leans on colour to carry
|
|
95
|
+
its meaning. That also means a state that needs to shout cannot, which is the
|
|
96
|
+
trade the quiet treatment makes.
|
|
97
|
+
|
|
98
|
+
## When to pass a glyph instead
|
|
99
|
+
|
|
100
|
+
`icon` is for fields that classify rather than track a lifecycle, where a status
|
|
101
|
+
name would be the wrong shape. Two exist: Plans' Availability (a gift, a lock
|
|
102
|
+
and a globe for Free, Private and Public) and newsletters' Visibility (a globe
|
|
103
|
+
or an envelope). Both are still Status-column-shaped in every other way, and
|
|
104
|
+
both keep the same distinctness rule.
|
|
105
|
+
|
|
106
|
+
## The icon's footprint
|
|
107
|
+
|
|
108
|
+
`@wordpress/icons` draws a 16px glyph inside a 24px viewBox, so a 24px icon
|
|
109
|
+
carries 4px of transparent padding on every side. The component trims that back
|
|
110
|
+
to the visible footprint with a negative margin, which is what makes the 8px gap
|
|
111
|
+
measure 8px between the glyph and the label rather than 12px. An icon that fills
|
|
112
|
+
its viewBox would be cropped by 4px a side; the statuses all come from
|
|
113
|
+
`@wordpress/icons`, which does not.
|
|
114
|
+
|
|
115
|
+
The margin is derived rather than written as `-4px`, so it follows the two
|
|
116
|
+
values it depends on: `calc((#{wp-vars.$grid-unit-20} - #{wp-vars.$icon-size}) / 2)`,
|
|
117
|
+
the glyph's footprint minus the box it sits in, halved for one side. Sass folds
|
|
118
|
+
it to a literal at build time, so nothing is paid for at runtime.
|
|
119
|
+
|
|
120
|
+
The box it sits in is the `size={ 24 }` the component passes to `Icon`, which is
|
|
121
|
+
`$icon-size`. The two are written in different files, so anything that changes
|
|
122
|
+
the rendered size has to change the token the margin reads, or the trim stops
|
|
123
|
+
matching the padding it is there to remove.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { render, screen } from '@testing-library/react';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* WordPress dependencies
|
|
8
|
+
*/
|
|
9
|
+
import { drafts, gift, published } from '@wordpress/icons';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Internal dependencies
|
|
13
|
+
*/
|
|
14
|
+
import StatusIndicator, { statusGlyph, STATUS_NAMES } from '.';
|
|
15
|
+
|
|
16
|
+
describe( 'StatusIndicator', () => {
|
|
17
|
+
it( 'renders the glyph alongside the label', () => {
|
|
18
|
+
const { container } = render( <StatusIndicator status="active">Active</StatusIndicator> );
|
|
19
|
+
expect( screen.getByText( 'Active' ) ).toBeInTheDocument();
|
|
20
|
+
expect( container.querySelector( 'svg.newspack-status-indicator__icon' ) ).toBeInTheDocument();
|
|
21
|
+
} );
|
|
22
|
+
|
|
23
|
+
// The trim is what makes the 8px gap measure 8px, so it is styled through a
|
|
24
|
+
// class rather than left to the consumer.
|
|
25
|
+
it( 'carries the class the icon trim is scoped to', () => {
|
|
26
|
+
const { container } = render( <StatusIndicator status="active">Active</StatusIndicator> );
|
|
27
|
+
expect( container.querySelector( '.newspack-status-indicator' ) ).toBeInTheDocument();
|
|
28
|
+
expect( container.querySelector( '.newspack-status-indicator__icon' ) ).toBeInTheDocument();
|
|
29
|
+
} );
|
|
30
|
+
|
|
31
|
+
it( 'draws a named status the way the vocabulary says', () => {
|
|
32
|
+
const { container: named } = render( <StatusIndicator status="draft">Draft</StatusIndicator> );
|
|
33
|
+
const { container: given } = render( <StatusIndicator icon={ drafts }>Draft</StatusIndicator> );
|
|
34
|
+
expect( named.querySelector( 'svg' ).innerHTML ).toBe( given.querySelector( 'svg' ).innerHTML );
|
|
35
|
+
} );
|
|
36
|
+
|
|
37
|
+
it( 'draws the glyph it is given when there is no name for it', () => {
|
|
38
|
+
const { container: free } = render( <StatusIndicator icon={ gift }>Free</StatusIndicator> );
|
|
39
|
+
const { container: active } = render( <StatusIndicator icon={ published }>Active</StatusIndicator> );
|
|
40
|
+
expect( free.querySelector( 'svg' ).innerHTML ).not.toBe( active.querySelector( 'svg' ).innerHTML );
|
|
41
|
+
} );
|
|
42
|
+
|
|
43
|
+
it( 'keeps the consumer class and passes the rest through', () => {
|
|
44
|
+
const { container } = render(
|
|
45
|
+
<StatusIndicator className="custom" data-testid="status" status="active">
|
|
46
|
+
Active
|
|
47
|
+
</StatusIndicator>
|
|
48
|
+
);
|
|
49
|
+
const root = container.querySelector( '.newspack-status-indicator' );
|
|
50
|
+
expect( root ).toHaveClass( 'custom' );
|
|
51
|
+
expect( root ).toHaveAttribute( 'data-testid', 'status' );
|
|
52
|
+
} );
|
|
53
|
+
} );
|
|
54
|
+
|
|
55
|
+
describe( 'statusGlyph', () => {
|
|
56
|
+
it( 'draws every name in the vocabulary', () => {
|
|
57
|
+
STATUS_NAMES.forEach( name => expect( statusGlyph( name ) ).toBeTruthy() );
|
|
58
|
+
} );
|
|
59
|
+
|
|
60
|
+
// Pinning the complete list is what lets a column assert its own distinctness:
|
|
61
|
+
// any pair not named here reads apart.
|
|
62
|
+
it( 'shares a mark only where two names mean the same to a reader', () => {
|
|
63
|
+
const byGlyph = new Map();
|
|
64
|
+
STATUS_NAMES.forEach( name => byGlyph.set( statusGlyph( name ), [ ...( byGlyph.get( statusGlyph( name ) ) || [] ), name ] ) );
|
|
65
|
+
expect( [ ...byGlyph.values() ].filter( names => names.length > 1 ) ).toEqual( [
|
|
66
|
+
[ 'active', 'done' ],
|
|
67
|
+
[ 'cancelled', 'ended' ],
|
|
68
|
+
] );
|
|
69
|
+
} );
|
|
70
|
+
} );
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StatusIndicator
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* WordPress dependencies.
|
|
7
|
+
*/
|
|
8
|
+
import { Icon } from '@wordpress/components';
|
|
9
|
+
import { forwardRef } from '@wordpress/element';
|
|
10
|
+
import { Stack } from '@wordpress/ui';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* External dependencies.
|
|
14
|
+
*/
|
|
15
|
+
import classnames from 'classnames';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Internal dependencies.
|
|
19
|
+
*/
|
|
20
|
+
import type { StatusIndicatorProps } from './types';
|
|
21
|
+
import { statusGlyph } from './statuses';
|
|
22
|
+
import './style.scss';
|
|
23
|
+
|
|
24
|
+
const StatusIndicator = forwardRef< HTMLDivElement, StatusIndicatorProps >( function StatusIndicator(
|
|
25
|
+
{ status, icon, className, children, ...props },
|
|
26
|
+
ref
|
|
27
|
+
) {
|
|
28
|
+
return (
|
|
29
|
+
<Stack ref={ ref } direction="row" align="center" gap="sm" className={ classnames( 'newspack-status-indicator', className ) } { ...props }>
|
|
30
|
+
<Icon className="newspack-status-indicator__icon" icon={ status ? statusGlyph( status ) : icon } size={ 24 } />
|
|
31
|
+
<span>{ children }</span>
|
|
32
|
+
</Stack>
|
|
33
|
+
);
|
|
34
|
+
} );
|
|
35
|
+
|
|
36
|
+
export { statusGlyph, STATUS_NAMES } from './statuses';
|
|
37
|
+
export type { StatusName } from './statuses';
|
|
38
|
+
|
|
39
|
+
export default StatusIndicator;
|