@automattic/jetpack-components 6.0.0 → 6.1.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 CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ### This is a list detailing changes for the Jetpack RNA Components package releases.
4
4
 
5
+ ## [6.1.0] - 2026-10-28
6
+ ### Added
7
+ - Add a PeriodSelector component: a dropdown of period presets with optional previous and next buttons. [#54149]
8
+ - Add a RadioCard component. [#54212]
9
+ - AdminPage: Add a `jp-admin-page__content` class to the content container. [#54105]
10
+ - PeriodSelector: Add a disabled prop. [#54202]
11
+ - Period Selector: Add a label that screen readers announce with the selected period. [#54209]
12
+
13
+ ### Changed
14
+ - AutomatticIconLogo: Use the black Automattic symbol by default, without blue. [#54208]
15
+ - Update package dependencies. [#54118]
16
+
5
17
  ## [6.0.0] - 2026-10-26
6
18
  ### Added
7
19
  - AdminPage: Add an ariaLabel prop that names the page region when the header shows breadcrumbs instead of a title. [#54055]
@@ -2036,6 +2048,7 @@
2036
2048
  ### Changed
2037
2049
  - Update node version requirement to 14.16.1
2038
2050
 
2051
+ [6.1.0]: https://github.com/Automattic/jetpack-components/compare/6.0.0...6.1.0
2039
2052
  [6.0.0]: https://github.com/Automattic/jetpack-components/compare/5.0.1...6.0.0
2040
2053
  [5.0.1]: https://github.com/Automattic/jetpack-components/compare/5.0.0...5.0.1
2041
2054
  [5.0.0]: https://github.com/Automattic/jetpack-components/compare/4.0.4...5.0.0
@@ -49,7 +49,7 @@ const AdminPage = ({ children, className, showHeader = true, showFooter = true,
49
49
  }, []);
50
50
  // When title or breadcrumbs are provided, use admin-ui Page for the full page layout.
51
51
  if (showHeader && (title || breadcrumbs)) {
52
- return (_jsx("div", { className: rootClassName, children: _jsxs(Page, { className: "jp-admin-page__page", visual: logo || _jsx(JetpackLogo, { showText: false, height: 20 }), breadcrumbs: breadcrumbs, title: title, ariaLabel: ariaLabel, subTitle: subTitle, actions: globalControls ? (_jsxs(_Fragment, { children: [actions, actions && (_jsx("span", { role: "separator", "aria-orientation": "vertical", className: styles['global-controls-separator'] })), globalControls] })) : (actions), showSidebarToggle: false, children: [tabs, unwrapped ? (children) : (_jsx(Container, { fluid: true, horizontalSpacing: 0, children: _jsx(Col, { children: children }) })), showFooter && _jsx(JetpackFooter, { menu: optionalMenuItems })] }) }));
52
+ return (_jsx("div", { className: rootClassName, children: _jsxs(Page, { className: "jp-admin-page__page", visual: logo || _jsx(JetpackLogo, { showText: false, height: 20 }), breadcrumbs: breadcrumbs, title: title, ariaLabel: ariaLabel, subTitle: subTitle, actions: globalControls ? (_jsxs(_Fragment, { children: [actions, actions && (_jsx("span", { role: "separator", "aria-orientation": "vertical", className: styles['global-controls-separator'] })), globalControls] })) : (actions), showSidebarToggle: false, children: [tabs, unwrapped ? (children) : (_jsx(Container, { className: "jp-admin-page__content", fluid: true, horizontalSpacing: 0, children: _jsx(Col, { children: children }) })), showFooter && _jsx(JetpackFooter, { menu: optionalMenuItems })] }) }));
53
53
  }
54
54
  // Legacy path: no title provided, render the classic header.
55
55
  return (_jsxs("div", { className: rootClassName, children: [showHeader && (_jsx(Container, { horizontalSpacing: 5, children: _jsxs(Col, { className: clsx(styles['admin-page-header'], 'jp-admin-page-header'), children: [header ? header : _jsx(JetpackLogo, {}), sandboxedDomain && (_jsx("code", { className: styles['sandbox-domain-badge'], onClick: testConnection, onKeyDown: testConnection,
@@ -2,9 +2,11 @@ import type { ReactElement } from 'react';
2
2
  /**
3
3
  * Automattic Icon Logo component
4
4
  *
5
+ * The Automattic logo is black or white only: pass white for both colors on dark backgrounds.
6
+ *
5
7
  * @param {object} props - Component props
6
- * @param {string} props.innerColor - Color code for the line in the middle of the logo.
7
- * @param {string} props.outerColor - Color code for the logo's outer
8
+ * @param {string} props.innerColor - Color code for the logo's ring.
9
+ * @param {string} props.outerColor - Color code for the slash inside the ring.
8
10
  * @return {ReactElement} Component template
9
11
  */
10
12
  export default function AutomatticIconLogo({ innerColor, outerColor, }: {
@@ -3,11 +3,13 @@ import { Path, SVG } from '@wordpress/components';
3
3
  /**
4
4
  * Automattic Icon Logo component
5
5
  *
6
+ * The Automattic logo is black or white only: pass white for both colors on dark backgrounds.
7
+ *
6
8
  * @param {object} props - Component props
7
- * @param {string} props.innerColor - Color code for the line in the middle of the logo.
8
- * @param {string} props.outerColor - Color code for the logo's outer
9
+ * @param {string} props.innerColor - Color code for the logo's ring.
10
+ * @param {string} props.outerColor - Color code for the slash inside the ring.
9
11
  * @return {ReactElement} Component template
10
12
  */
11
- export default function AutomatticIconLogo({ innerColor = '#00A3E0', outerColor = '#FFFFFF', }) {
12
- return (_jsxs(SVG, { clipRule: "evenodd", fill: "none", fillRule: "evenodd", height: "43", viewBox: "0 0 47 43", width: "47", xmlns: "http://www.w3.org/2000/svg", children: [_jsx(Path, { fillRule: "evenodd", clipRule: "evenodd", d: "M39.3164 20.9589C39.3164 13.2129 33.656 6.30469 23.4214 6.30469C13.1867 6.30469 7.58624 13.2129 7.58624 20.9589V21.9206C7.58624 29.6677 13.1867 36.6942 23.4214 36.6942C33.656 36.6942 39.3164 29.6677 39.3164 21.9206V20.9589ZM23.4214 43C9.21187 43 0 32.7913 0 22.1604V20.8407C0 10.0285 9.21187 0 23.4214 0C37.6919 0 46.9038 10.0285 46.9038 20.8407V22.1604C46.9038 32.7913 37.6919 43 23.4214 43Z", fill: innerColor }), _jsx(Path, { fillRule: "evenodd", clipRule: "evenodd", d: "M28.4548 14.1897C29.752 15.0401 30.1158 16.8077 29.2739 18.1343L22.712 28.4682C21.8691 29.7959 20.1348 30.1799 18.8397 29.3295C17.5446 28.477 17.1765 26.7137 18.0205 25.386L24.5824 15.0522C25.4253 13.7256 27.1597 13.3404 28.4548 14.1897Z", fill: outerColor })] }));
13
+ export default function AutomatticIconLogo({ innerColor = '#000000', outerColor = '#000000', }) {
14
+ return (_jsxs(SVG, { clipRule: "evenodd", fill: "none", fillRule: "evenodd", height: "43", viewBox: "0 0 171 156", width: "47", xmlns: "http://www.w3.org/2000/svg", children: [_jsx(Path, { fillRule: "evenodd", clipRule: "evenodd", d: "M85.398 155.632C34.1992 155.632 1.01562 118.788 1.01562 80.3993V75.6007C1.01562 36.5611 34.1992 0.368164 85.398 0.368164C136.597 0.368164 169.984 36.5611 169.984 75.6007V80.3993C169.984 118.666 136.8 155.632 85.398 155.632ZM145.191 76.048C145.191 48.0696 122.323 23.1819 85.398 23.1819C48.4731 23.1819 25.9304 48.1103 25.9304 76.048V79.5047C25.9304 107.483 48.7984 132.859 85.398 132.859C121.998 132.859 145.191 107.483 145.191 79.5047V76.048Z", fill: innerColor }), _jsx(Path, { fillRule: "evenodd", clipRule: "evenodd", d: "M62.7978 91.4965L87.6364 53.0284C91.1593 47.5724 98.4381 46.0054 103.894 49.5283L103.928 49.5504C109.384 53.0732 110.951 60.352 107.428 65.808L82.5899 104.276C79.067 109.732 71.7882 111.299 66.3322 107.776L66.298 107.754C60.842 104.231 59.275 96.9525 62.7978 91.4965Z", fill: outerColor })] }));
13
15
  }
@@ -1,54 +1,26 @@
1
- @use "@automattic/jetpack-base-styles/gutenberg-base-styles" as gb;
1
+ @use "../selectable-card/selectable-card";
2
2
 
3
3
  .jp-checkbox-card-grid {
4
- display: grid;
5
- grid-template-columns: repeat(2, minmax(0, 1fr));
6
- column-gap: var(--wpds-dimension-gap-md);
7
- row-gap: var(--wpds-dimension-gap-2xl);
8
-
9
- @media (max-width: 720px) {
10
- grid-template-columns: minmax(0, 1fr);
11
- }
4
+
5
+ @include selectable-card.grid;
12
6
  }
13
7
 
14
8
  .jp-checkbox-card {
15
9
 
16
- &[data-checked="true"] {
17
- border-color: var(--wpds-color-stroke-interactive-brand-active);
18
- }
19
-
20
- &:hover:not([data-checked="true"]) {
21
- border-color: var(--wpds-color-stroke-surface-neutral-strong);
22
- }
23
-
24
- &:has(:focus-visible) {
25
-
26
- @include gb.outset-ring__focus();
27
- }
28
-
29
- @media (prefers-reduced-motion: no-preference) {
30
- transition: border-color 0.15s ease-out;
31
- }
10
+ @include selectable-card.card('[data-checked="true"]');
32
11
  }
33
12
 
34
13
  .jp-checkbox-card__surface {
35
- position: relative;
14
+
15
+ @include selectable-card.surface;
36
16
  }
37
17
 
38
18
  .jp-checkbox-card__illustration {
39
- display: flex;
40
- aspect-ratio: 294 / 192;
41
- background: var(--wpds-color-background-surface-neutral-weak);
42
- cursor: var(--wpds-cursor-control);
43
-
44
- > svg {
45
- inline-size: 100%;
46
- block-size: 100%;
47
- }
19
+
20
+ @include selectable-card.illustration;
48
21
  }
49
22
 
50
23
  .jp-checkbox-card__checkbox {
51
- position: absolute;
52
- inset-block-start: var(--wpds-dimension-padding-md);
53
- inset-inline-start: var(--wpds-dimension-padding-md);
24
+
25
+ @include selectable-card.control;
54
26
  }
@@ -0,0 +1,9 @@
1
+ import type { PeriodSelectorProps } from './types.ts';
2
+ import type { JSX } from 'react';
3
+ /**
4
+ * A dropdown of period presets, with optional buttons to step to the previous or next period.
5
+ *
6
+ * @param {PeriodSelectorProps} props - Component props.
7
+ * @return {JSX.Element} The selector.
8
+ */
9
+ export default function PeriodSelector<Value extends string>({ presets, value, label, onChange, onPrevious, onNext, isPreviousDisabled, isNextDisabled, disabled, className, }: PeriodSelectorProps<Value>): JSX.Element;
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { __, isRTL, sprintf } from '@wordpress/i18n';
3
+ import { calendar, chevronLeft, chevronRight } from '@wordpress/icons';
4
+ import { Button, IconButton, Menu, Stack } from '@wordpress/ui';
5
+ /**
6
+ * A dropdown of period presets, with optional buttons to step to the previous or next period.
7
+ *
8
+ * @param {PeriodSelectorProps} props - Component props.
9
+ * @return {JSX.Element} The selector.
10
+ */
11
+ export default function PeriodSelector({ presets, value, label, onChange, onPrevious, onNext, isPreviousDisabled = false, isNextDisabled = false, disabled = false, className, }) {
12
+ const selected = presets.find(preset => preset.value === value);
13
+ const selectedLabel = selected?.label ?? __('Select period', 'jetpack-components');
14
+ const [previousIcon, nextIcon] = isRTL()
15
+ ? [chevronRight, chevronLeft]
16
+ : [chevronLeft, chevronRight];
17
+ return (_jsxs(Stack, { direction: "row", align: "center", gap: "xs", className: className, children: [_jsxs(Menu.Root, { children: [_jsxs(Menu.Trigger, { render: _jsx(Button, { variant: "outline", tone: "neutral", size: "compact" }), disabled: disabled, "aria-label": label &&
18
+ sprintf(
19
+ /* translators: %1$s: What the control picks, such as "Date range". %2$s: The selected period, such as "Last 30 days". */
20
+ __('%1$s: %2$s', 'jetpack-components'), label, selectedLabel), children: [_jsx(Button.Icon, { icon: calendar }), selectedLabel] }), _jsx(Menu.Popup, { positioner: _jsx(Menu.Positioner, { align: "end" }), "aria-label": label, children: _jsx(Menu.RadioGroup, { value: value, onValueChange: onChange, children: presets.map(preset => (_jsx(Menu.RadioItem, { value: preset.value, closeOnClick: true, children: _jsx(Menu.ItemLabel, { children: preset.label }) }, preset.value))) }) })] }), onPrevious && (_jsx(IconButton, { variant: "outline", tone: "neutral", size: "compact", icon: previousIcon, label: __('Previous period', 'jetpack-components'), onClick: onPrevious, disabled: disabled || isPreviousDisabled })), onNext && (_jsx(IconButton, { variant: "outline", tone: "neutral", size: "compact", icon: nextIcon, label: __('Next period', 'jetpack-components'), onClick: onNext, disabled: disabled || isNextDisabled }))] }));
21
+ }
@@ -0,0 +1,28 @@
1
+ export type PeriodPreset<Value extends string = string> = {
2
+ /** Identifies the preset; passed to `onChange`. */
3
+ value: Value;
4
+ /** The preset's name, such as "Last 7 days". */
5
+ label: string;
6
+ };
7
+ export type PeriodSelectorProps<Value extends string = string> = {
8
+ /** The presets the dropdown offers, in order. */
9
+ presets: PeriodPreset<Value>[];
10
+ /** Names what the control picks, such as "Date range"; screen readers hear it before the selected period. */
11
+ label?: string;
12
+ /** The selected preset's value. */
13
+ value: Value;
14
+ /** Called with the chosen preset's value. */
15
+ onChange: (value: Value) => void;
16
+ /** Steps to the previous period; the button shows only when set. */
17
+ onPrevious?: () => void;
18
+ /** Steps to the next period; the button shows only when set. */
19
+ onNext?: () => void;
20
+ /** Whether there is no previous period to step to. */
21
+ isPreviousDisabled?: boolean;
22
+ /** Whether there is no next period to step to, such as on the latest one. */
23
+ isNextDisabled?: boolean;
24
+ /** Whether the dropdown and both step buttons are disabled. */
25
+ disabled?: boolean;
26
+ /** Class name for the wrapper. */
27
+ className?: string;
28
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import './style.scss';
2
+ import type { RadioCardGridProps, RadioCardProps } from './types.ts';
3
+ import type { JSX } from 'react';
4
+ /**
5
+ * Lays out radio cards in two columns, or one on narrow screens.
6
+ *
7
+ * @param {RadioCardGridProps} props - Component props.
8
+ * @return {JSX.Element} The grid.
9
+ */
10
+ export declare function RadioCardGrid({ children, className }: RadioCardGridProps): JSX.Element;
11
+ /**
12
+ * A card with a radio, picked by a click on its illustration. Render it in a `RadioGroup` inside a `Field.Root`.
13
+ *
14
+ * @param {RadioCardProps} props - Component props.
15
+ * @return {JSX.Element} The card.
16
+ */
17
+ export default function RadioCard({ id, value, title, illustration, previewUrl, onPreviewClick, disabled, }: RadioCardProps): JSX.Element;
18
+ export { RadioCard };
19
+ export type { RadioCardProps, RadioCardGridProps };
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback } from '@wordpress/element';
3
+ import { __ } from '@wordpress/i18n';
4
+ import { Card, Field, Link, Radio, Stack, Text } from '@wordpress/ui';
5
+ import clsx from 'clsx';
6
+ import './style.scss';
7
+ /**
8
+ * Lays out radio cards in two columns, or one on narrow screens.
9
+ *
10
+ * @param {RadioCardGridProps} props - Component props.
11
+ * @return {JSX.Element} The grid.
12
+ */
13
+ export function RadioCardGrid({ children, className }) {
14
+ return _jsx("div", { className: clsx('jp-radio-card-grid', className), children: children });
15
+ }
16
+ /**
17
+ * A card with a radio, picked by a click on its illustration. Render it in a `RadioGroup` inside a `Field.Root`.
18
+ *
19
+ * @param {RadioCardProps} props - Component props.
20
+ * @return {JSX.Element} The card.
21
+ */
22
+ export default function RadioCard({ id, value, title, illustration, previewUrl, onPreviewClick, disabled, }) {
23
+ const titleId = `${id}__title`;
24
+ const handlePreviewClick = useCallback(() => {
25
+ onPreviewClick?.(value);
26
+ }, [value, onPreviewClick]);
27
+ return (_jsxs(Field.Item, { disabled: disabled, children: [_jsx(Card.Root, { className: "jp-radio-card", children: _jsx(Card.Content, { children: _jsxs(Card.FullBleed, { className: "jp-radio-card__surface", children: [_jsx(Field.Label, { className: "jp-radio-card__illustration", children: illustration }), _jsx(Radio, { id: id, className: "jp-radio-card__radio", value: value, disabled: disabled, "aria-labelledby": titleId })] }) }) }), _jsxs(Stack, { gap: "xs", direction: "column", children: [_jsx(Text, { id: titleId, variant: "body-sm", children: title }), previewUrl && (_jsx(Link, { href: previewUrl, onClick: handlePreviewClick, children: __('Preview and edit', 'jetpack-components') }))] })] }));
28
+ }
29
+ export { RadioCard };
@@ -0,0 +1,26 @@
1
+ @use "../selectable-card/selectable-card";
2
+
3
+ .jp-radio-card-grid {
4
+
5
+ @include selectable-card.grid;
6
+ }
7
+
8
+ .jp-radio-card {
9
+
10
+ @include selectable-card.card( ":has(.jp-radio-card__radio[data-checked])" );
11
+ }
12
+
13
+ .jp-radio-card__surface {
14
+
15
+ @include selectable-card.surface;
16
+ }
17
+
18
+ .jp-radio-card__illustration {
19
+
20
+ @include selectable-card.illustration;
21
+ }
22
+
23
+ .jp-radio-card__radio {
24
+
25
+ @include selectable-card.control;
26
+ }
@@ -0,0 +1,41 @@
1
+ import type { ReactNode } from 'react';
2
+ export type RadioCardProps = {
3
+ /**
4
+ * Id of the radio; the title's id is built from it.
5
+ */
6
+ id: string;
7
+ /**
8
+ * The value the surrounding `RadioGroup` takes when this card is picked. Also passed to `onPreviewClick`.
9
+ */
10
+ value: string;
11
+ /**
12
+ * Caption under the card, and the radio's accessible name.
13
+ */
14
+ title: string;
15
+ /**
16
+ * Picture filling the card, drawn at a 294×192 aspect ratio.
17
+ */
18
+ illustration?: ReactNode;
19
+ /**
20
+ * Shows a "Preview and edit" link to this URL under the title.
21
+ */
22
+ previewUrl?: string;
23
+ /**
24
+ * Called with the card's `value` when the "Preview and edit" link is clicked.
25
+ */
26
+ onPreviewClick?: (value: string) => void;
27
+ /**
28
+ * Disables the radio.
29
+ */
30
+ disabled?: boolean;
31
+ };
32
+ export type RadioCardGridProps = {
33
+ /**
34
+ * The `RadioCard`s to lay out.
35
+ */
36
+ children: ReactNode;
37
+ /**
38
+ * Extra class for the grid.
39
+ */
40
+ className?: string;
41
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,57 @@
1
+ @use "@automattic/jetpack-base-styles/gutenberg-base-styles" as gb;
2
+
3
+ // Shared by CheckboxCard and RadioCard, which differ only in their control.
4
+
5
+ @mixin grid {
6
+ display: grid;
7
+ grid-template-columns: repeat(2, minmax(0, 1fr));
8
+ column-gap: var(--wpds-dimension-gap-md);
9
+ row-gap: var(--wpds-dimension-gap-2xl);
10
+
11
+ @media (max-width: 720px) {
12
+ grid-template-columns: minmax(0, 1fr);
13
+ }
14
+ }
15
+
16
+ // $selected matches the card while its control is selected.
17
+ @mixin card($selected) {
18
+
19
+ &#{$selected} {
20
+ border-color: var(--wpds-color-stroke-interactive-brand-active);
21
+ }
22
+
23
+ &:hover:not(#{$selected}) {
24
+ border-color: var(--wpds-color-stroke-surface-neutral-strong);
25
+ }
26
+
27
+ &:has(:focus-visible) {
28
+
29
+ @include gb.outset-ring__focus();
30
+ }
31
+
32
+ @media (prefers-reduced-motion: no-preference) {
33
+ transition: border-color 0.15s ease-out;
34
+ }
35
+ }
36
+
37
+ @mixin surface {
38
+ position: relative;
39
+ }
40
+
41
+ @mixin illustration {
42
+ display: flex;
43
+ aspect-ratio: 294 / 192;
44
+ background: var(--wpds-color-background-surface-neutral-weak);
45
+ cursor: var(--wpds-cursor-control);
46
+
47
+ > svg {
48
+ inline-size: 100%;
49
+ block-size: 100%;
50
+ }
51
+ }
52
+
53
+ @mixin control {
54
+ position: absolute;
55
+ inset-block-start: var(--wpds-dimension-padding-md);
56
+ inset-inline-start: var(--wpds-dimension-padding-md);
57
+ }
package/build/index.d.ts CHANGED
@@ -11,6 +11,8 @@ export { default as Gravatar } from './components/gravatar/index.tsx';
11
11
  export type { GravatarProps } from './components/gravatar/index.tsx';
12
12
  export { default as CheckboxCard, CheckboxCardGrid } from './components/checkbox-card/index.tsx';
13
13
  export type { CheckboxCardProps, CheckboxCardGridProps } from './components/checkbox-card/types.ts';
14
+ export { default as RadioCard, RadioCardGrid } from './components/radio-card/index.tsx';
15
+ export type { RadioCardProps, RadioCardGridProps } from './components/radio-card/types.ts';
14
16
  export { default as IconTooltip } from './components/icon-tooltip/index.tsx';
15
17
  export { default as ActionButton } from './components/action-button/index.tsx';
16
18
  export { default as PricingCard } from './components/pricing-card/index.tsx';
@@ -45,6 +47,8 @@ export { default as ActionPopover } from './components/action-popover/index.tsx'
45
47
  export { default as UpsellBanner } from './components/upsell-banner/index.tsx';
46
48
  export { getUserLocale, cleanLocale } from './lib/locale/index.ts';
47
49
  export { default as StatCard } from './components/stat-card/index.tsx';
50
+ export { default as PeriodSelector } from './components/period-selector/index.tsx';
51
+ export type { PeriodPreset, PeriodSelectorProps } from './components/period-selector/types.ts';
48
52
  export { default as DiffViewer } from './components/diff-viewer/index.tsx';
49
53
  export { default as MarkedLines } from './components/marked-lines/index.tsx';
50
54
  export { default as DetailsViewer } from './components/details-viewer/index.tsx';
package/build/index.js CHANGED
@@ -23,6 +23,7 @@ export { default as AutomatticForAgenciesLogo } from './components/automattic-fo
23
23
  export { default as JetpackFooter } from './components/jetpack-footer/index.js';
24
24
  export { default as Gravatar } from './components/gravatar/index.js';
25
25
  export { default as CheckboxCard, CheckboxCardGrid } from './components/checkbox-card/index.js';
26
+ export { default as RadioCard, RadioCardGrid } from './components/radio-card/index.js';
26
27
  export { default as IconTooltip } from './components/icon-tooltip/index.js';
27
28
  export { default as ActionButton } from './components/action-button/index.js';
28
29
  export { default as PricingCard } from './components/pricing-card/index.js';
@@ -56,6 +57,7 @@ export { default as ActionPopover } from './components/action-popover/index.js';
56
57
  export { default as UpsellBanner } from './components/upsell-banner/index.js';
57
58
  export { getUserLocale, cleanLocale } from './lib/locale/index.js';
58
59
  export { default as StatCard } from './components/stat-card/index.js';
60
+ export { default as PeriodSelector } from './components/period-selector/index.js';
59
61
  export { default as DiffViewer } from './components/diff-viewer/index.js';
60
62
  export { default as MarkedLines } from './components/marked-lines/index.js';
61
63
  export { default as DetailsViewer } from './components/details-viewer/index.js';
@@ -111,7 +111,7 @@ const AdminPage: FC< AdminPageProps > = ( {
111
111
  { unwrapped ? (
112
112
  children
113
113
  ) : (
114
- <Container fluid horizontalSpacing={ 0 }>
114
+ <Container className="jp-admin-page__content" fluid horizontalSpacing={ 0 }>
115
115
  <Col>{ children }</Col>
116
116
  </Container>
117
117
  ) }
@@ -4,14 +4,16 @@ import type { ReactElement } from 'react';
4
4
  /**
5
5
  * Automattic Icon Logo component
6
6
  *
7
+ * The Automattic logo is black or white only: pass white for both colors on dark backgrounds.
8
+ *
7
9
  * @param {object} props - Component props
8
- * @param {string} props.innerColor - Color code for the line in the middle of the logo.
9
- * @param {string} props.outerColor - Color code for the logo's outer
10
+ * @param {string} props.innerColor - Color code for the logo's ring.
11
+ * @param {string} props.outerColor - Color code for the slash inside the ring.
10
12
  * @return {ReactElement} Component template
11
13
  */
12
14
  export default function AutomatticIconLogo( {
13
- innerColor = '#00A3E0',
14
- outerColor = '#FFFFFF',
15
+ innerColor = '#000000',
16
+ outerColor = '#000000',
15
17
  }: {
16
18
  innerColor: string;
17
19
  outerColor: string;
@@ -22,20 +24,20 @@ export default function AutomatticIconLogo( {
22
24
  fill="none"
23
25
  fillRule="evenodd"
24
26
  height="43"
25
- viewBox="0 0 47 43"
27
+ viewBox="0 0 171 156"
26
28
  width="47"
27
29
  xmlns="http://www.w3.org/2000/svg"
28
30
  >
29
31
  <Path
30
32
  fillRule="evenodd"
31
33
  clipRule="evenodd"
32
- d="M39.3164 20.9589C39.3164 13.2129 33.656 6.30469 23.4214 6.30469C13.1867 6.30469 7.58624 13.2129 7.58624 20.9589V21.9206C7.58624 29.6677 13.1867 36.6942 23.4214 36.6942C33.656 36.6942 39.3164 29.6677 39.3164 21.9206V20.9589ZM23.4214 43C9.21187 43 0 32.7913 0 22.1604V20.8407C0 10.0285 9.21187 0 23.4214 0C37.6919 0 46.9038 10.0285 46.9038 20.8407V22.1604C46.9038 32.7913 37.6919 43 23.4214 43Z"
34
+ d="M85.398 155.632C34.1992 155.632 1.01562 118.788 1.01562 80.3993V75.6007C1.01562 36.5611 34.1992 0.368164 85.398 0.368164C136.597 0.368164 169.984 36.5611 169.984 75.6007V80.3993C169.984 118.666 136.8 155.632 85.398 155.632ZM145.191 76.048C145.191 48.0696 122.323 23.1819 85.398 23.1819C48.4731 23.1819 25.9304 48.1103 25.9304 76.048V79.5047C25.9304 107.483 48.7984 132.859 85.398 132.859C121.998 132.859 145.191 107.483 145.191 79.5047V76.048Z"
33
35
  fill={ innerColor }
34
36
  />
35
37
  <Path
36
38
  fillRule="evenodd"
37
39
  clipRule="evenodd"
38
- d="M28.4548 14.1897C29.752 15.0401 30.1158 16.8077 29.2739 18.1343L22.712 28.4682C21.8691 29.7959 20.1348 30.1799 18.8397 29.3295C17.5446 28.477 17.1765 26.7137 18.0205 25.386L24.5824 15.0522C25.4253 13.7256 27.1597 13.3404 28.4548 14.1897Z"
40
+ d="M62.7978 91.4965L87.6364 53.0284C91.1593 47.5724 98.4381 46.0054 103.894 49.5283L103.928 49.5504C109.384 53.0732 110.951 60.352 107.428 65.808L82.5899 104.276C79.067 109.732 71.7882 111.299 66.3322 107.776L66.298 107.754C60.842 104.231 59.275 96.9525 62.7978 91.4965Z"
39
41
  fill={ outerColor }
40
42
  />
41
43
  </SVG>
@@ -1,54 +1,26 @@
1
- @use "@automattic/jetpack-base-styles/gutenberg-base-styles" as gb;
1
+ @use "../selectable-card/selectable-card";
2
2
 
3
3
  .jp-checkbox-card-grid {
4
- display: grid;
5
- grid-template-columns: repeat(2, minmax(0, 1fr));
6
- column-gap: var(--wpds-dimension-gap-md);
7
- row-gap: var(--wpds-dimension-gap-2xl);
8
-
9
- @media (max-width: 720px) {
10
- grid-template-columns: minmax(0, 1fr);
11
- }
4
+
5
+ @include selectable-card.grid;
12
6
  }
13
7
 
14
8
  .jp-checkbox-card {
15
9
 
16
- &[data-checked="true"] {
17
- border-color: var(--wpds-color-stroke-interactive-brand-active);
18
- }
19
-
20
- &:hover:not([data-checked="true"]) {
21
- border-color: var(--wpds-color-stroke-surface-neutral-strong);
22
- }
23
-
24
- &:has(:focus-visible) {
25
-
26
- @include gb.outset-ring__focus();
27
- }
28
-
29
- @media (prefers-reduced-motion: no-preference) {
30
- transition: border-color 0.15s ease-out;
31
- }
10
+ @include selectable-card.card('[data-checked="true"]');
32
11
  }
33
12
 
34
13
  .jp-checkbox-card__surface {
35
- position: relative;
14
+
15
+ @include selectable-card.surface;
36
16
  }
37
17
 
38
18
  .jp-checkbox-card__illustration {
39
- display: flex;
40
- aspect-ratio: 294 / 192;
41
- background: var(--wpds-color-background-surface-neutral-weak);
42
- cursor: var(--wpds-cursor-control);
43
-
44
- > svg {
45
- inline-size: 100%;
46
- block-size: 100%;
47
- }
19
+
20
+ @include selectable-card.illustration;
48
21
  }
49
22
 
50
23
  .jp-checkbox-card__checkbox {
51
- position: absolute;
52
- inset-block-start: var(--wpds-dimension-padding-md);
53
- inset-inline-start: var(--wpds-dimension-padding-md);
24
+
25
+ @include selectable-card.control;
54
26
  }
@@ -0,0 +1,84 @@
1
+ import { __, isRTL, sprintf } from '@wordpress/i18n';
2
+ import { calendar, chevronLeft, chevronRight } from '@wordpress/icons';
3
+ import { Button, IconButton, Menu, Stack } from '@wordpress/ui';
4
+ import type { PeriodSelectorProps } from './types.ts';
5
+ import type { JSX } from 'react';
6
+
7
+ /**
8
+ * A dropdown of period presets, with optional buttons to step to the previous or next period.
9
+ *
10
+ * @param {PeriodSelectorProps} props - Component props.
11
+ * @return {JSX.Element} The selector.
12
+ */
13
+ export default function PeriodSelector< Value extends string >( {
14
+ presets,
15
+ value,
16
+ label,
17
+ onChange,
18
+ onPrevious,
19
+ onNext,
20
+ isPreviousDisabled = false,
21
+ isNextDisabled = false,
22
+ disabled = false,
23
+ className,
24
+ }: PeriodSelectorProps< Value > ): JSX.Element {
25
+ const selected = presets.find( preset => preset.value === value );
26
+ const selectedLabel = selected?.label ?? __( 'Select period', 'jetpack-components' );
27
+ const [ previousIcon, nextIcon ] = isRTL()
28
+ ? [ chevronRight, chevronLeft ]
29
+ : [ chevronLeft, chevronRight ];
30
+
31
+ return (
32
+ <Stack direction="row" align="center" gap="xs" className={ className }>
33
+ <Menu.Root>
34
+ <Menu.Trigger
35
+ render={ <Button variant="outline" tone="neutral" size="compact" /> }
36
+ disabled={ disabled }
37
+ aria-label={
38
+ label &&
39
+ sprintf(
40
+ /* translators: %1$s: What the control picks, such as "Date range". %2$s: The selected period, such as "Last 30 days". */
41
+ __( '%1$s: %2$s', 'jetpack-components' ),
42
+ label,
43
+ selectedLabel
44
+ )
45
+ }
46
+ >
47
+ <Button.Icon icon={ calendar } />
48
+ { selectedLabel }
49
+ </Menu.Trigger>
50
+ <Menu.Popup positioner={ <Menu.Positioner align="end" /> } aria-label={ label }>
51
+ <Menu.RadioGroup value={ value } onValueChange={ onChange }>
52
+ { presets.map( preset => (
53
+ <Menu.RadioItem key={ preset.value } value={ preset.value } closeOnClick>
54
+ <Menu.ItemLabel>{ preset.label }</Menu.ItemLabel>
55
+ </Menu.RadioItem>
56
+ ) ) }
57
+ </Menu.RadioGroup>
58
+ </Menu.Popup>
59
+ </Menu.Root>
60
+ { onPrevious && (
61
+ <IconButton
62
+ variant="outline"
63
+ tone="neutral"
64
+ size="compact"
65
+ icon={ previousIcon }
66
+ label={ __( 'Previous period', 'jetpack-components' ) }
67
+ onClick={ onPrevious }
68
+ disabled={ disabled || isPreviousDisabled }
69
+ />
70
+ ) }
71
+ { onNext && (
72
+ <IconButton
73
+ variant="outline"
74
+ tone="neutral"
75
+ size="compact"
76
+ icon={ nextIcon }
77
+ label={ __( 'Next period', 'jetpack-components' ) }
78
+ onClick={ onNext }
79
+ disabled={ disabled || isNextDisabled }
80
+ />
81
+ ) }
82
+ </Stack>
83
+ );
84
+ }
@@ -0,0 +1,29 @@
1
+ export type PeriodPreset< Value extends string = string > = {
2
+ /** Identifies the preset; passed to `onChange`. */
3
+ value: Value;
4
+ /** The preset's name, such as "Last 7 days". */
5
+ label: string;
6
+ };
7
+
8
+ export type PeriodSelectorProps< Value extends string = string > = {
9
+ /** The presets the dropdown offers, in order. */
10
+ presets: PeriodPreset< Value >[];
11
+ /** Names what the control picks, such as "Date range"; screen readers hear it before the selected period. */
12
+ label?: string;
13
+ /** The selected preset's value. */
14
+ value: Value;
15
+ /** Called with the chosen preset's value. */
16
+ onChange: ( value: Value ) => void;
17
+ /** Steps to the previous period; the button shows only when set. */
18
+ onPrevious?: () => void;
19
+ /** Steps to the next period; the button shows only when set. */
20
+ onNext?: () => void;
21
+ /** Whether there is no previous period to step to. */
22
+ isPreviousDisabled?: boolean;
23
+ /** Whether there is no next period to step to, such as on the latest one. */
24
+ isNextDisabled?: boolean;
25
+ /** Whether the dropdown and both step buttons are disabled. */
26
+ disabled?: boolean;
27
+ /** Class name for the wrapper. */
28
+ className?: string;
29
+ };
@@ -0,0 +1,72 @@
1
+ import { useCallback } from '@wordpress/element';
2
+ import { __ } from '@wordpress/i18n';
3
+ import { Card, Field, Link, Radio, Stack, Text } from '@wordpress/ui';
4
+ import clsx from 'clsx';
5
+ import './style.scss';
6
+ import type { RadioCardGridProps, RadioCardProps } from './types.ts';
7
+ import type { JSX } from 'react';
8
+
9
+ /**
10
+ * Lays out radio cards in two columns, or one on narrow screens.
11
+ *
12
+ * @param {RadioCardGridProps} props - Component props.
13
+ * @return {JSX.Element} The grid.
14
+ */
15
+ export function RadioCardGrid( { children, className }: RadioCardGridProps ): JSX.Element {
16
+ return <div className={ clsx( 'jp-radio-card-grid', className ) }>{ children }</div>;
17
+ }
18
+
19
+ /**
20
+ * A card with a radio, picked by a click on its illustration. Render it in a `RadioGroup` inside a `Field.Root`.
21
+ *
22
+ * @param {RadioCardProps} props - Component props.
23
+ * @return {JSX.Element} The card.
24
+ */
25
+ export default function RadioCard( {
26
+ id,
27
+ value,
28
+ title,
29
+ illustration,
30
+ previewUrl,
31
+ onPreviewClick,
32
+ disabled,
33
+ }: RadioCardProps ): JSX.Element {
34
+ const titleId = `${ id }__title`;
35
+
36
+ const handlePreviewClick = useCallback( () => {
37
+ onPreviewClick?.( value );
38
+ }, [ value, onPreviewClick ] );
39
+
40
+ return (
41
+ <Field.Item disabled={ disabled }>
42
+ <Card.Root className="jp-radio-card">
43
+ <Card.Content>
44
+ <Card.FullBleed className="jp-radio-card__surface">
45
+ { /* Only the illustration picks the card: the title and link sit outside the label. */ }
46
+ <Field.Label className="jp-radio-card__illustration">{ illustration }</Field.Label>
47
+ <Radio
48
+ id={ id }
49
+ className="jp-radio-card__radio"
50
+ value={ value }
51
+ disabled={ disabled }
52
+ aria-labelledby={ titleId }
53
+ />
54
+ </Card.FullBleed>
55
+ </Card.Content>
56
+ </Card.Root>
57
+ <Stack gap="xs" direction="column">
58
+ <Text id={ titleId } variant="body-sm">
59
+ { title }
60
+ </Text>
61
+ { previewUrl && (
62
+ <Link href={ previewUrl } onClick={ handlePreviewClick }>
63
+ { __( 'Preview and edit', 'jetpack-components' ) }
64
+ </Link>
65
+ ) }
66
+ </Stack>
67
+ </Field.Item>
68
+ );
69
+ }
70
+
71
+ export { RadioCard };
72
+ export type { RadioCardProps, RadioCardGridProps };
@@ -0,0 +1,26 @@
1
+ @use "../selectable-card/selectable-card";
2
+
3
+ .jp-radio-card-grid {
4
+
5
+ @include selectable-card.grid;
6
+ }
7
+
8
+ .jp-radio-card {
9
+
10
+ @include selectable-card.card( ":has(.jp-radio-card__radio[data-checked])" );
11
+ }
12
+
13
+ .jp-radio-card__surface {
14
+
15
+ @include selectable-card.surface;
16
+ }
17
+
18
+ .jp-radio-card__illustration {
19
+
20
+ @include selectable-card.illustration;
21
+ }
22
+
23
+ .jp-radio-card__radio {
24
+
25
+ @include selectable-card.control;
26
+ }
@@ -0,0 +1,43 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export type RadioCardProps = {
4
+ /**
5
+ * Id of the radio; the title's id is built from it.
6
+ */
7
+ id: string;
8
+ /**
9
+ * The value the surrounding `RadioGroup` takes when this card is picked. Also passed to `onPreviewClick`.
10
+ */
11
+ value: string;
12
+ /**
13
+ * Caption under the card, and the radio's accessible name.
14
+ */
15
+ title: string;
16
+ /**
17
+ * Picture filling the card, drawn at a 294×192 aspect ratio.
18
+ */
19
+ illustration?: ReactNode;
20
+ /**
21
+ * Shows a "Preview and edit" link to this URL under the title.
22
+ */
23
+ previewUrl?: string;
24
+ /**
25
+ * Called with the card's `value` when the "Preview and edit" link is clicked.
26
+ */
27
+ onPreviewClick?: ( value: string ) => void;
28
+ /**
29
+ * Disables the radio.
30
+ */
31
+ disabled?: boolean;
32
+ };
33
+
34
+ export type RadioCardGridProps = {
35
+ /**
36
+ * The `RadioCard`s to lay out.
37
+ */
38
+ children: ReactNode;
39
+ /**
40
+ * Extra class for the grid.
41
+ */
42
+ className?: string;
43
+ };
@@ -0,0 +1,57 @@
1
+ @use "@automattic/jetpack-base-styles/gutenberg-base-styles" as gb;
2
+
3
+ // Shared by CheckboxCard and RadioCard, which differ only in their control.
4
+
5
+ @mixin grid {
6
+ display: grid;
7
+ grid-template-columns: repeat(2, minmax(0, 1fr));
8
+ column-gap: var(--wpds-dimension-gap-md);
9
+ row-gap: var(--wpds-dimension-gap-2xl);
10
+
11
+ @media (max-width: 720px) {
12
+ grid-template-columns: minmax(0, 1fr);
13
+ }
14
+ }
15
+
16
+ // $selected matches the card while its control is selected.
17
+ @mixin card($selected) {
18
+
19
+ &#{$selected} {
20
+ border-color: var(--wpds-color-stroke-interactive-brand-active);
21
+ }
22
+
23
+ &:hover:not(#{$selected}) {
24
+ border-color: var(--wpds-color-stroke-surface-neutral-strong);
25
+ }
26
+
27
+ &:has(:focus-visible) {
28
+
29
+ @include gb.outset-ring__focus();
30
+ }
31
+
32
+ @media (prefers-reduced-motion: no-preference) {
33
+ transition: border-color 0.15s ease-out;
34
+ }
35
+ }
36
+
37
+ @mixin surface {
38
+ position: relative;
39
+ }
40
+
41
+ @mixin illustration {
42
+ display: flex;
43
+ aspect-ratio: 294 / 192;
44
+ background: var(--wpds-color-background-surface-neutral-weak);
45
+ cursor: var(--wpds-cursor-control);
46
+
47
+ > svg {
48
+ inline-size: 100%;
49
+ block-size: 100%;
50
+ }
51
+ }
52
+
53
+ @mixin control {
54
+ position: absolute;
55
+ inset-block-start: var(--wpds-dimension-padding-md);
56
+ inset-inline-start: var(--wpds-dimension-padding-md);
57
+ }
package/index.ts CHANGED
@@ -26,6 +26,8 @@ export { default as Gravatar } from './components/gravatar/index.tsx';
26
26
  export type { GravatarProps } from './components/gravatar/index.tsx';
27
27
  export { default as CheckboxCard, CheckboxCardGrid } from './components/checkbox-card/index.tsx';
28
28
  export type { CheckboxCardProps, CheckboxCardGridProps } from './components/checkbox-card/types.ts';
29
+ export { default as RadioCard, RadioCardGrid } from './components/radio-card/index.tsx';
30
+ export type { RadioCardProps, RadioCardGridProps } from './components/radio-card/types.ts';
29
31
  export { default as IconTooltip } from './components/icon-tooltip/index.tsx';
30
32
  export { default as ActionButton } from './components/action-button/index.tsx';
31
33
  export { default as PricingCard } from './components/pricing-card/index.tsx';
@@ -65,6 +67,8 @@ export { default as ActionPopover } from './components/action-popover/index.tsx'
65
67
  export { default as UpsellBanner } from './components/upsell-banner/index.tsx';
66
68
  export { getUserLocale, cleanLocale } from './lib/locale/index.ts';
67
69
  export { default as StatCard } from './components/stat-card/index.tsx';
70
+ export { default as PeriodSelector } from './components/period-selector/index.tsx';
71
+ export type { PeriodPreset, PeriodSelectorProps } from './components/period-selector/types.ts';
68
72
  export { default as DiffViewer } from './components/diff-viewer/index.tsx';
69
73
  export { default as MarkedLines } from './components/marked-lines/index.tsx';
70
74
  export { default as DetailsViewer } from './components/details-viewer/index.tsx';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/jetpack-components",
3
- "version": "6.0.0",
3
+ "version": "6.1.0",
4
4
  "description": "Jetpack Components Package",
5
5
  "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/components/#readme",
6
6
  "bugs": {
@@ -67,6 +67,10 @@
67
67
  "types": "./build/components/product-price/index.d.ts",
68
68
  "default": "./build/components/product-price/index.js"
69
69
  },
70
+ "./radio-card": {
71
+ "types": "./build/components/radio-card/index.d.ts",
72
+ "default": "./build/components/radio-card/index.js"
73
+ },
70
74
  "./tools/jp-redirect": {
71
75
  "types": "./build/tools/jp-redirect/index.d.ts",
72
76
  "default": "./build/tools/jp-redirect/index.js"
@@ -114,7 +118,7 @@
114
118
  "uplot-react": "1.1.4"
115
119
  },
116
120
  "devDependencies": {
117
- "@automattic/jetpack-base-styles": "^1.2.19",
121
+ "@automattic/jetpack-base-styles": "^1.2.20",
118
122
  "@babel/core": "7.29.7",
119
123
  "@jest/globals": "30.5.2",
120
124
  "@storybook/addon-docs": "10.6.3",
@@ -133,7 +137,7 @@
133
137
  "storybook": "10.6.3",
134
138
  "ts-dedent": "2.2.0",
135
139
  "typescript": "6.0.3",
136
- "webpack": "5.111.1",
140
+ "webpack": "5.112.2",
137
141
  "webpack-cli": "7.2.1"
138
142
  },
139
143
  "peerDependencies": {