@wordpress/admin-ui 1.10.0 → 1.11.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,21 +2,28 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 1.11.0 (2026-04-01)
6
+
7
+ ### Bug Fixes
8
+
9
+ - `Breadcrumbs`: throw a runtime error when non-last items are missing a `to` prop [#76493](https://github.com/WordPress/gutenberg/pull/76493/)
10
+ - Fix Page Header not rendering when only `actions` prop is provided. [#76695](https://github.com/WordPress/gutenberg/pull/76695)
11
+
5
12
  ## 1.10.0 (2026-03-18)
6
13
 
7
- - Update Title and Breadcrumbs font sizes. [#76452](https://github.com/WordPress/gutenberg/pull/76452)
14
+ - Update Title and Breadcrumbs font sizes. [#76452](https://github.com/WordPress/gutenberg/pull/76452)
8
15
 
9
16
  ## 1.9.0 (2026-03-04)
10
17
 
11
18
  ### Bug Fixes
12
19
 
13
- - Fix type mismatch between Page `title` (ReactNode) and NavigableRegion `ariaLabel` (string) by adding an optional `ariaLabel` prop to Page that falls back to `title` when it is a string. [#75899](https://github.com/WordPress/gutenberg/pull/75899/)
20
+ - Fix type mismatch between Page `title` (ReactNode) and NavigableRegion `ariaLabel` (string) by adding an optional `ariaLabel` prop to Page that falls back to `title` when it is a string. [#75899](https://github.com/WordPress/gutenberg/pull/75899/)
14
21
 
15
22
  ## 1.8.0 (2026-02-18)
16
23
 
17
24
  ### Enhancements
18
25
 
19
- - Apply `text-wrap: pretty` for more balanced text in Page component [#74907](https://github.com/WordPress/gutenberg/pull/74907)
26
+ - Apply `text-wrap: pretty` for more balanced text in Page component [#74907](https://github.com/WordPress/gutenberg/pull/74907)
20
27
 
21
28
  ## 1.7.0 (2026-01-29)
22
29
 
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # Admin UI
2
2
 
3
- Generic components to be used to build the Admin UI.
3
+ UI components for building consistent admin page layouts.
4
+
5
+ While `@wordpress/ui` provides low-level, generic UI components that can be composed in flexible arrangements for building admin features, the purpose of this package is to guarantee consistency in the common page structure of an admin page layout. This includes high-level abstractions for a page, its sidebar, header, navigation, and other standardized page layout elements. The goal of standardizing these layouts is to provide a cohesive and predictable experience for users.
4
6
 
5
7
  ## Installation
6
8
 
@@ -16,7 +18,26 @@ npm install @wordpress/admin-ui --save
16
18
 
17
19
  ### Breadcrumbs
18
20
 
19
- Undocumented declaration.
21
+ Renders a breadcrumb navigation trail.
22
+
23
+ All items except the last one must provide a `to` prop for navigation. In development mode, an error is thrown when a non-last item is missing `to`. The last item represents the current page and its `to` prop is optional. Only the last item (when it has no `to` prop) is rendered as an `h1`.
24
+
25
+ _Usage_
26
+
27
+ ```jsx
28
+ <Breadcrumbs
29
+ items={ [
30
+ { label: 'Home', to: '/' },
31
+ { label: 'Settings', to: '/settings' },
32
+ { label: 'General' },
33
+ ] }
34
+ />
35
+ ```
36
+
37
+ _Parameters_
38
+
39
+ - _props_ `BreadcrumbsProps`:
40
+ - _props.items_ `BreadcrumbsProps[ 'items' ]`: The breadcrumb items to display.
20
41
 
21
42
  ### NavigableRegion
22
43
 
@@ -28,19 +28,21 @@ var import_route = require("@wordpress/route");
28
28
  var import_i18n = require("@wordpress/i18n");
29
29
  var import_components = require("@wordpress/components");
30
30
  var import_jsx_runtime = require("react/jsx-runtime");
31
- var BreadcrumbItem = ({
32
- item: { label, to }
33
- }) => {
34
- if (!to) {
35
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_components.__experimentalHeading, { level: 1, truncate: true, children: label }) });
36
- }
37
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_route.Link, { to, children: label }) });
38
- };
39
31
  var Breadcrumbs = ({ items }) => {
40
32
  if (!items.length) {
41
33
  return null;
42
34
  }
43
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("nav", { "aria-label": (0, import_i18n.__)("Breadcrumbs"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
35
+ const precedingItems = items.slice(0, -1);
36
+ const lastItem = items[items.length - 1];
37
+ if (process.env.NODE_ENV !== "production") {
38
+ const invalidItem = precedingItems.find((item) => !item.to);
39
+ if (invalidItem) {
40
+ throw new Error(
41
+ `Breadcrumbs: item "${invalidItem.label}" is missing a \`to\` prop. All items except the last one must have a \`to\` prop.`
42
+ );
43
+ }
44
+ }
45
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("nav", { "aria-label": (0, import_i18n.__)("Breadcrumbs"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
44
46
  import_components.__experimentalHStack,
45
47
  {
46
48
  as: "ul",
@@ -48,7 +50,10 @@ var Breadcrumbs = ({ items }) => {
48
50
  spacing: 0,
49
51
  justify: "flex-start",
50
52
  alignment: "center",
51
- children: items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BreadcrumbItem, { item }, index))
53
+ children: [
54
+ precedingItems.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_route.Link, { to: item.to, children: item.label }) }, index)),
55
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: lastItem.to ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_route.Link, { to: lastItem.to, children: lastItem.label }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_components.__experimentalHeading, { level: 1, truncate: true, children: lastItem.label }) })
56
+ ]
52
57
  }
53
58
  ) });
54
59
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/breadcrumbs/index.tsx"],
4
- "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { Link } from '@wordpress/route';\nimport { __ } from '@wordpress/i18n';\nimport {\n\t__experimentalHeading as Heading,\n\t__experimentalHStack as HStack,\n} from '@wordpress/components';\n\n/**\n * Internal dependencies\n */\nimport type {\n\tBreadcrumbsProps,\n\tBreadcrumbItem as BreadcrumbItemType,\n} from './types';\n\nconst BreadcrumbItem = ( {\n\titem: { label, to },\n}: {\n\titem: BreadcrumbItemType;\n} ) => {\n\tif ( ! to ) {\n\t\treturn (\n\t\t\t<li>\n\t\t\t\t<Heading level={ 1 } truncate>\n\t\t\t\t\t{ label }\n\t\t\t\t</Heading>\n\t\t\t</li>\n\t\t);\n\t}\n\n\treturn (\n\t\t<li>\n\t\t\t<Link to={ to }>{ label }</Link>\n\t\t</li>\n\t);\n};\n\nexport const Breadcrumbs = ( { items }: BreadcrumbsProps ) => {\n\tif ( ! items.length ) {\n\t\treturn null;\n\t}\n\n\treturn (\n\t\t<nav aria-label={ __( 'Breadcrumbs' ) }>\n\t\t\t<HStack\n\t\t\t\tas=\"ul\"\n\t\t\t\tclassName=\"admin-ui-breadcrumbs__list\"\n\t\t\t\tspacing={ 0 }\n\t\t\t\tjustify=\"flex-start\"\n\t\t\t\talignment=\"center\"\n\t\t\t>\n\t\t\t\t{ items.map( ( item, index ) => (\n\t\t\t\t\t<BreadcrumbItem key={ index } item={ item } />\n\t\t\t\t) ) }\n\t\t\t</HStack>\n\t\t</nav>\n\t);\n};\n\nexport default Breadcrumbs;\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,mBAAqB;AACrB,kBAAmB;AACnB,wBAGO;AAkBH;AARJ,IAAM,iBAAiB,CAAE;AAAA,EACxB,MAAM,EAAE,OAAO,GAAG;AACnB,MAEO;AACN,MAAK,CAAE,IAAK;AACX,WACC,4CAAC,QACA,sDAAC,kBAAAA,uBAAA,EAAQ,OAAQ,GAAI,UAAQ,MAC1B,iBACH,GACD;AAAA,EAEF;AAEA,SACC,4CAAC,QACA,sDAAC,qBAAK,IAAY,iBAAO,GAC1B;AAEF;AAEO,IAAM,cAAc,CAAE,EAAE,MAAM,MAAyB;AAC7D,MAAK,CAAE,MAAM,QAAS;AACrB,WAAO;AAAA,EACR;AAEA,SACC,4CAAC,SAAI,kBAAa,gBAAI,aAAc,GACnC;AAAA,IAAC,kBAAAC;AAAA,IAAA;AAAA,MACA,IAAG;AAAA,MACH,WAAU;AAAA,MACV,SAAU;AAAA,MACV,SAAQ;AAAA,MACR,WAAU;AAAA,MAER,gBAAM,IAAK,CAAE,MAAM,UACpB,4CAAC,kBAA6B,QAAR,KAAsB,CAC3C;AAAA;AAAA,EACH,GACD;AAEF;AAEA,IAAO,sBAAQ;",
6
- "names": ["Heading", "HStack"]
4
+ "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { Link } from '@wordpress/route';\nimport { __ } from '@wordpress/i18n';\nimport {\n\t__experimentalHeading as Heading,\n\t__experimentalHStack as HStack,\n} from '@wordpress/components';\n\n/**\n * Internal dependencies\n */\nimport type { BreadcrumbsProps } from './types';\n\n/**\n * Renders a breadcrumb navigation trail.\n *\n * All items except the last one must provide a `to` prop for navigation.\n * In development mode, an error is thrown when a non-last item is missing `to`.\n * The last item represents the current page and its `to` prop is optional.\n * Only the last item (when it has no `to` prop) is rendered as an `h1`.\n *\n * @param props\n * @param props.items The breadcrumb items to display.\n *\n * @example\n * ```jsx\n * <Breadcrumbs\n * items={ [\n * { label: 'Home', to: '/' },\n * { label: 'Settings', to: '/settings' },\n * { label: 'General' },\n * ] }\n * />\n * ```\n */\nexport const Breadcrumbs = ( { items }: BreadcrumbsProps ) => {\n\tif ( ! items.length ) {\n\t\treturn null;\n\t}\n\n\tconst precedingItems = items.slice( 0, -1 );\n\tconst lastItem = items[ items.length - 1 ];\n\n\tif ( process.env.NODE_ENV !== 'production' ) {\n\t\tconst invalidItem = precedingItems.find( ( item ) => ! item.to );\n\t\tif ( invalidItem ) {\n\t\t\tthrow new Error(\n\t\t\t\t`Breadcrumbs: item \"${ invalidItem.label }\" is missing a \\`to\\` prop. All items except the last one must have a \\`to\\` prop.`\n\t\t\t);\n\t\t}\n\t}\n\n\treturn (\n\t\t<nav aria-label={ __( 'Breadcrumbs' ) }>\n\t\t\t<HStack\n\t\t\t\tas=\"ul\"\n\t\t\t\tclassName=\"admin-ui-breadcrumbs__list\"\n\t\t\t\tspacing={ 0 }\n\t\t\t\tjustify=\"flex-start\"\n\t\t\t\talignment=\"center\"\n\t\t\t>\n\t\t\t\t{ precedingItems.map( ( item, index ) => (\n\t\t\t\t\t<li key={ index }>\n\t\t\t\t\t\t<Link to={ item.to }>{ item.label }</Link>\n\t\t\t\t\t</li>\n\t\t\t\t) ) }\n\t\t\t\t<li>\n\t\t\t\t\t{ lastItem.to ? (\n\t\t\t\t\t\t<Link to={ lastItem.to }>{ lastItem.label }</Link>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<Heading level={ 1 } truncate>\n\t\t\t\t\t\t\t{ lastItem.label }\n\t\t\t\t\t\t</Heading>\n\t\t\t\t\t) }\n\t\t\t\t</li>\n\t\t\t</HStack>\n\t\t</nav>\n\t);\n};\n\nexport default Breadcrumbs;\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,mBAAqB;AACrB,kBAAmB;AACnB,wBAGO;AAgDJ;AAnBI,IAAM,cAAc,CAAE,EAAE,MAAM,MAAyB;AAC7D,MAAK,CAAE,MAAM,QAAS;AACrB,WAAO;AAAA,EACR;AAEA,QAAM,iBAAiB,MAAM,MAAO,GAAG,EAAG;AAC1C,QAAM,WAAW,MAAO,MAAM,SAAS,CAAE;AAEzC,MAAK,QAAQ,IAAI,aAAa,cAAe;AAC5C,UAAM,cAAc,eAAe,KAAM,CAAE,SAAU,CAAE,KAAK,EAAG;AAC/D,QAAK,aAAc;AAClB,YAAM,IAAI;AAAA,QACT,sBAAuB,YAAY,KAAM;AAAA,MAC1C;AAAA,IACD;AAAA,EACD;AAEA,SACC,4CAAC,SAAI,kBAAa,gBAAI,aAAc,GACnC;AAAA,IAAC,kBAAAA;AAAA,IAAA;AAAA,MACA,IAAG;AAAA,MACH,WAAU;AAAA,MACV,SAAU;AAAA,MACV,SAAQ;AAAA,MACR,WAAU;AAAA,MAER;AAAA,uBAAe,IAAK,CAAE,MAAM,UAC7B,4CAAC,QACA,sDAAC,qBAAK,IAAK,KAAK,IAAO,eAAK,OAAO,KAD1B,KAEV,CACC;AAAA,QACF,4CAAC,QACE,mBAAS,KACV,4CAAC,qBAAK,IAAK,SAAS,IAAO,mBAAS,OAAO,IAE3C,4CAAC,kBAAAC,uBAAA,EAAQ,OAAQ,GAAI,UAAQ,MAC1B,mBAAS,OACZ,GAEF;AAAA;AAAA;AAAA,EACD,GACD;AAEF;AAEA,IAAO,sBAAQ;",
6
+ "names": ["HStack", "Heading"]
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/breadcrumbs/types.ts"],
4
- "sourcesContent": ["export interface BreadcrumbItem {\n\t/**\n\t * The label text for the breadcrumb item.\n\t */\n\tlabel: string;\n\n\t/**\n\t * The router path that the breadcrumb item should link to.\n\t * It is optional because the current item does not have a link.\n\t */\n\tto?: string;\n}\n\nexport interface BreadcrumbsProps extends React.HTMLAttributes< HTMLElement > {\n\t/**\n\t * An array of items to display in the breadcrumb trail.\n\t * The last item is considered the current item.\n\t */\n\titems: BreadcrumbItem[];\n\t/**\n\t * A boolean to show/hide the current item in the trail.\n\t * Note that when `false` the current item is only visually hidden.\n\t */\n\tshowCurrentItem?: boolean;\n}\n"],
4
+ "sourcesContent": ["export interface BreadcrumbItem {\n\t/**\n\t * The label text for the breadcrumb item.\n\t */\n\tlabel: string;\n\n\t/**\n\t * The router path that the breadcrumb item should link to.\n\t * It is optional for the last item (the current page).\n\t * All preceding items should provide a `to` prop.\n\t */\n\tto?: string;\n}\n\nexport interface BreadcrumbsProps extends React.HTMLAttributes< HTMLElement > {\n\t/**\n\t * An array of items to display in the breadcrumb trail.\n\t * The last item is considered the current item and has an optional `to` prop.\n\t * All preceding items must have a `to` prop \u2014 in development mode,\n\t * an error is thrown when this requirement is not met.\n\t */\n\titems: BreadcrumbItem[];\n\t/**\n\t * A boolean to show/hide the current item in the trail.\n\t * Note that when `false` the current item is only visually hidden.\n\t */\n\tshowCurrentItem?: boolean;\n}\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;AAAA;AAAA;",
6
6
  "names": []
7
7
  }
@@ -54,7 +54,7 @@ function Page({
54
54
  const classes = (0, import_clsx.default)("admin-ui-page", className);
55
55
  const effectiveAriaLabel = ariaLabel ?? (typeof title === "string" ? title : "");
56
56
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_navigable_region.default, { className: classes, ariaLabel: effectiveAriaLabel, children: [
57
- (title || breadcrumbs || badges) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
57
+ (title || breadcrumbs || badges || actions) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
58
58
  import_header.default,
59
59
  {
60
60
  headingLevel,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/page/index.tsx"],
4
- "sourcesContent": ["/**\n * External dependencies\n */\nimport clsx from 'clsx';\n\n/**\n * Internal dependencies\n */\nimport Header from './header';\nimport NavigableRegion from '../navigable-region';\nimport { SidebarToggleFill } from './sidebar-toggle-slot';\n\nfunction Page( {\n\theadingLevel,\n\tbreadcrumbs,\n\tbadges,\n\ttitle,\n\tsubTitle,\n\tchildren,\n\tclassName,\n\tactions,\n\tariaLabel,\n\thasPadding = false,\n\tshowSidebarToggle = true,\n}: {\n\theadingLevel?: 1 | 2 | 3 | 4 | 5 | 6;\n\tbreadcrumbs?: React.ReactNode;\n\tbadges?: React.ReactNode;\n\ttitle?: React.ReactNode;\n\tsubTitle?: React.ReactNode;\n\tchildren: React.ReactNode;\n\tclassName?: string;\n\tactions?: React.ReactNode;\n\tariaLabel?: string;\n\thasPadding?: boolean;\n\tshowSidebarToggle?: boolean;\n} ) {\n\tconst classes = clsx( 'admin-ui-page', className );\n\tconst effectiveAriaLabel =\n\t\tariaLabel ?? ( typeof title === 'string' ? title : '' );\n\n\treturn (\n\t\t<NavigableRegion className={ classes } ariaLabel={ effectiveAriaLabel }>\n\t\t\t{ ( title || breadcrumbs || badges ) && (\n\t\t\t\t<Header\n\t\t\t\t\theadingLevel={ headingLevel }\n\t\t\t\t\tbreadcrumbs={ breadcrumbs }\n\t\t\t\t\tbadges={ badges }\n\t\t\t\t\ttitle={ title }\n\t\t\t\t\tsubTitle={ subTitle }\n\t\t\t\t\tactions={ actions }\n\t\t\t\t\tshowSidebarToggle={ showSidebarToggle }\n\t\t\t\t/>\n\t\t\t) }\n\t\t\t{ hasPadding ? (\n\t\t\t\t<div className=\"admin-ui-page__content has-padding\">\n\t\t\t\t\t{ children }\n\t\t\t\t</div>\n\t\t\t) : (\n\t\t\t\tchildren\n\t\t\t) }\n\t\t</NavigableRegion>\n\t);\n}\n\nPage.SidebarToggleFill = SidebarToggleFill;\n\nexport default Page;\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,kBAAiB;AAKjB,oBAAmB;AACnB,8BAA4B;AAC5B,iCAAkC;AAgChC;AA9BF,SAAS,KAAM;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb,oBAAoB;AACrB,GAYI;AACH,QAAM,cAAU,YAAAA,SAAM,iBAAiB,SAAU;AACjD,QAAM,qBACL,cAAe,OAAO,UAAU,WAAW,QAAQ;AAEpD,SACC,6CAAC,wBAAAC,SAAA,EAAgB,WAAY,SAAU,WAAY,oBAC9C;AAAA,cAAS,eAAe,WAC3B;AAAA,MAAC,cAAAC;AAAA,MAAA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,IACD;AAAA,IAEC,aACD,4CAAC,SAAI,WAAU,sCACZ,UACH,IAEA;AAAA,KAEF;AAEF;AAEA,KAAK,oBAAoB;AAEzB,IAAO,eAAQ;",
4
+ "sourcesContent": ["/**\n * External dependencies\n */\nimport clsx from 'clsx';\n\n/**\n * Internal dependencies\n */\nimport Header from './header';\nimport NavigableRegion from '../navigable-region';\nimport { SidebarToggleFill } from './sidebar-toggle-slot';\n\nfunction Page( {\n\theadingLevel,\n\tbreadcrumbs,\n\tbadges,\n\ttitle,\n\tsubTitle,\n\tchildren,\n\tclassName,\n\tactions,\n\tariaLabel,\n\thasPadding = false,\n\tshowSidebarToggle = true,\n}: {\n\theadingLevel?: 1 | 2 | 3 | 4 | 5 | 6;\n\tbreadcrumbs?: React.ReactNode;\n\tbadges?: React.ReactNode;\n\ttitle?: React.ReactNode;\n\tsubTitle?: React.ReactNode;\n\tchildren: React.ReactNode;\n\tclassName?: string;\n\tactions?: React.ReactNode;\n\tariaLabel?: string;\n\thasPadding?: boolean;\n\tshowSidebarToggle?: boolean;\n} ) {\n\tconst classes = clsx( 'admin-ui-page', className );\n\tconst effectiveAriaLabel =\n\t\tariaLabel ?? ( typeof title === 'string' ? title : '' );\n\n\treturn (\n\t\t<NavigableRegion className={ classes } ariaLabel={ effectiveAriaLabel }>\n\t\t\t{ ( title || breadcrumbs || badges || actions ) && (\n\t\t\t\t<Header\n\t\t\t\t\theadingLevel={ headingLevel }\n\t\t\t\t\tbreadcrumbs={ breadcrumbs }\n\t\t\t\t\tbadges={ badges }\n\t\t\t\t\ttitle={ title }\n\t\t\t\t\tsubTitle={ subTitle }\n\t\t\t\t\tactions={ actions }\n\t\t\t\t\tshowSidebarToggle={ showSidebarToggle }\n\t\t\t\t/>\n\t\t\t) }\n\t\t\t{ hasPadding ? (\n\t\t\t\t<div className=\"admin-ui-page__content has-padding\">\n\t\t\t\t\t{ children }\n\t\t\t\t</div>\n\t\t\t) : (\n\t\t\t\tchildren\n\t\t\t) }\n\t\t</NavigableRegion>\n\t);\n}\n\nPage.SidebarToggleFill = SidebarToggleFill;\n\nexport default Page;\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,kBAAiB;AAKjB,oBAAmB;AACnB,8BAA4B;AAC5B,iCAAkC;AAgChC;AA9BF,SAAS,KAAM;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb,oBAAoB;AACrB,GAYI;AACH,QAAM,cAAU,YAAAA,SAAM,iBAAiB,SAAU;AACjD,QAAM,qBACL,cAAe,OAAO,UAAU,WAAW,QAAQ;AAEpD,SACC,6CAAC,wBAAAC,SAAA,EAAgB,WAAY,SAAU,WAAY,oBAC9C;AAAA,cAAS,eAAe,UAAU,YACrC;AAAA,MAAC,cAAAC;AAAA,MAAA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,IACD;AAAA,IAEC,aACD,4CAAC,SAAI,WAAU,sCACZ,UACH,IAEA;AAAA,KAEF;AAEF;AAEA,KAAK,oBAAoB;AAEzB,IAAO,eAAQ;",
6
6
  "names": ["clsx", "NavigableRegion", "Header"]
7
7
  }
@@ -5,20 +5,22 @@ import {
5
5
  __experimentalHeading as Heading,
6
6
  __experimentalHStack as HStack
7
7
  } from "@wordpress/components";
8
- import { jsx } from "react/jsx-runtime";
9
- var BreadcrumbItem = ({
10
- item: { label, to }
11
- }) => {
12
- if (!to) {
13
- return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Heading, { level: 1, truncate: true, children: label }) });
14
- }
15
- return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Link, { to, children: label }) });
16
- };
8
+ import { jsx, jsxs } from "react/jsx-runtime";
17
9
  var Breadcrumbs = ({ items }) => {
18
10
  if (!items.length) {
19
11
  return null;
20
12
  }
21
- return /* @__PURE__ */ jsx("nav", { "aria-label": __("Breadcrumbs"), children: /* @__PURE__ */ jsx(
13
+ const precedingItems = items.slice(0, -1);
14
+ const lastItem = items[items.length - 1];
15
+ if (process.env.NODE_ENV !== "production") {
16
+ const invalidItem = precedingItems.find((item) => !item.to);
17
+ if (invalidItem) {
18
+ throw new Error(
19
+ `Breadcrumbs: item "${invalidItem.label}" is missing a \`to\` prop. All items except the last one must have a \`to\` prop.`
20
+ );
21
+ }
22
+ }
23
+ return /* @__PURE__ */ jsx("nav", { "aria-label": __("Breadcrumbs"), children: /* @__PURE__ */ jsxs(
22
24
  HStack,
23
25
  {
24
26
  as: "ul",
@@ -26,7 +28,10 @@ var Breadcrumbs = ({ items }) => {
26
28
  spacing: 0,
27
29
  justify: "flex-start",
28
30
  alignment: "center",
29
- children: items.map((item, index) => /* @__PURE__ */ jsx(BreadcrumbItem, { item }, index))
31
+ children: [
32
+ precedingItems.map((item, index) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Link, { to: item.to, children: item.label }) }, index)),
33
+ /* @__PURE__ */ jsx("li", { children: lastItem.to ? /* @__PURE__ */ jsx(Link, { to: lastItem.to, children: lastItem.label }) : /* @__PURE__ */ jsx(Heading, { level: 1, truncate: true, children: lastItem.label }) })
34
+ ]
30
35
  }
31
36
  ) });
32
37
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/breadcrumbs/index.tsx"],
4
- "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { Link } from '@wordpress/route';\nimport { __ } from '@wordpress/i18n';\nimport {\n\t__experimentalHeading as Heading,\n\t__experimentalHStack as HStack,\n} from '@wordpress/components';\n\n/**\n * Internal dependencies\n */\nimport type {\n\tBreadcrumbsProps,\n\tBreadcrumbItem as BreadcrumbItemType,\n} from './types';\n\nconst BreadcrumbItem = ( {\n\titem: { label, to },\n}: {\n\titem: BreadcrumbItemType;\n} ) => {\n\tif ( ! to ) {\n\t\treturn (\n\t\t\t<li>\n\t\t\t\t<Heading level={ 1 } truncate>\n\t\t\t\t\t{ label }\n\t\t\t\t</Heading>\n\t\t\t</li>\n\t\t);\n\t}\n\n\treturn (\n\t\t<li>\n\t\t\t<Link to={ to }>{ label }</Link>\n\t\t</li>\n\t);\n};\n\nexport const Breadcrumbs = ( { items }: BreadcrumbsProps ) => {\n\tif ( ! items.length ) {\n\t\treturn null;\n\t}\n\n\treturn (\n\t\t<nav aria-label={ __( 'Breadcrumbs' ) }>\n\t\t\t<HStack\n\t\t\t\tas=\"ul\"\n\t\t\t\tclassName=\"admin-ui-breadcrumbs__list\"\n\t\t\t\tspacing={ 0 }\n\t\t\t\tjustify=\"flex-start\"\n\t\t\t\talignment=\"center\"\n\t\t\t>\n\t\t\t\t{ items.map( ( item, index ) => (\n\t\t\t\t\t<BreadcrumbItem key={ index } item={ item } />\n\t\t\t\t) ) }\n\t\t\t</HStack>\n\t\t</nav>\n\t);\n};\n\nexport default Breadcrumbs;\n"],
5
- "mappings": ";AAGA,SAAS,YAAY;AACrB,SAAS,UAAU;AACnB;AAAA,EACC,yBAAyB;AAAA,EACzB,wBAAwB;AAAA,OAClB;AAkBH;AARJ,IAAM,iBAAiB,CAAE;AAAA,EACxB,MAAM,EAAE,OAAO,GAAG;AACnB,MAEO;AACN,MAAK,CAAE,IAAK;AACX,WACC,oBAAC,QACA,8BAAC,WAAQ,OAAQ,GAAI,UAAQ,MAC1B,iBACH,GACD;AAAA,EAEF;AAEA,SACC,oBAAC,QACA,8BAAC,QAAK,IAAY,iBAAO,GAC1B;AAEF;AAEO,IAAM,cAAc,CAAE,EAAE,MAAM,MAAyB;AAC7D,MAAK,CAAE,MAAM,QAAS;AACrB,WAAO;AAAA,EACR;AAEA,SACC,oBAAC,SAAI,cAAa,GAAI,aAAc,GACnC;AAAA,IAAC;AAAA;AAAA,MACA,IAAG;AAAA,MACH,WAAU;AAAA,MACV,SAAU;AAAA,MACV,SAAQ;AAAA,MACR,WAAU;AAAA,MAER,gBAAM,IAAK,CAAE,MAAM,UACpB,oBAAC,kBAA6B,QAAR,KAAsB,CAC3C;AAAA;AAAA,EACH,GACD;AAEF;AAEA,IAAO,sBAAQ;",
4
+ "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { Link } from '@wordpress/route';\nimport { __ } from '@wordpress/i18n';\nimport {\n\t__experimentalHeading as Heading,\n\t__experimentalHStack as HStack,\n} from '@wordpress/components';\n\n/**\n * Internal dependencies\n */\nimport type { BreadcrumbsProps } from './types';\n\n/**\n * Renders a breadcrumb navigation trail.\n *\n * All items except the last one must provide a `to` prop for navigation.\n * In development mode, an error is thrown when a non-last item is missing `to`.\n * The last item represents the current page and its `to` prop is optional.\n * Only the last item (when it has no `to` prop) is rendered as an `h1`.\n *\n * @param props\n * @param props.items The breadcrumb items to display.\n *\n * @example\n * ```jsx\n * <Breadcrumbs\n * items={ [\n * { label: 'Home', to: '/' },\n * { label: 'Settings', to: '/settings' },\n * { label: 'General' },\n * ] }\n * />\n * ```\n */\nexport const Breadcrumbs = ( { items }: BreadcrumbsProps ) => {\n\tif ( ! items.length ) {\n\t\treturn null;\n\t}\n\n\tconst precedingItems = items.slice( 0, -1 );\n\tconst lastItem = items[ items.length - 1 ];\n\n\tif ( process.env.NODE_ENV !== 'production' ) {\n\t\tconst invalidItem = precedingItems.find( ( item ) => ! item.to );\n\t\tif ( invalidItem ) {\n\t\t\tthrow new Error(\n\t\t\t\t`Breadcrumbs: item \"${ invalidItem.label }\" is missing a \\`to\\` prop. All items except the last one must have a \\`to\\` prop.`\n\t\t\t);\n\t\t}\n\t}\n\n\treturn (\n\t\t<nav aria-label={ __( 'Breadcrumbs' ) }>\n\t\t\t<HStack\n\t\t\t\tas=\"ul\"\n\t\t\t\tclassName=\"admin-ui-breadcrumbs__list\"\n\t\t\t\tspacing={ 0 }\n\t\t\t\tjustify=\"flex-start\"\n\t\t\t\talignment=\"center\"\n\t\t\t>\n\t\t\t\t{ precedingItems.map( ( item, index ) => (\n\t\t\t\t\t<li key={ index }>\n\t\t\t\t\t\t<Link to={ item.to }>{ item.label }</Link>\n\t\t\t\t\t</li>\n\t\t\t\t) ) }\n\t\t\t\t<li>\n\t\t\t\t\t{ lastItem.to ? (\n\t\t\t\t\t\t<Link to={ lastItem.to }>{ lastItem.label }</Link>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<Heading level={ 1 } truncate>\n\t\t\t\t\t\t\t{ lastItem.label }\n\t\t\t\t\t\t</Heading>\n\t\t\t\t\t) }\n\t\t\t\t</li>\n\t\t\t</HStack>\n\t\t</nav>\n\t);\n};\n\nexport default Breadcrumbs;\n"],
5
+ "mappings": ";AAGA,SAAS,YAAY;AACrB,SAAS,UAAU;AACnB;AAAA,EACC,yBAAyB;AAAA,EACzB,wBAAwB;AAAA,OAClB;AAgDJ,SASG,KATH;AAnBI,IAAM,cAAc,CAAE,EAAE,MAAM,MAAyB;AAC7D,MAAK,CAAE,MAAM,QAAS;AACrB,WAAO;AAAA,EACR;AAEA,QAAM,iBAAiB,MAAM,MAAO,GAAG,EAAG;AAC1C,QAAM,WAAW,MAAO,MAAM,SAAS,CAAE;AAEzC,MAAK,QAAQ,IAAI,aAAa,cAAe;AAC5C,UAAM,cAAc,eAAe,KAAM,CAAE,SAAU,CAAE,KAAK,EAAG;AAC/D,QAAK,aAAc;AAClB,YAAM,IAAI;AAAA,QACT,sBAAuB,YAAY,KAAM;AAAA,MAC1C;AAAA,IACD;AAAA,EACD;AAEA,SACC,oBAAC,SAAI,cAAa,GAAI,aAAc,GACnC;AAAA,IAAC;AAAA;AAAA,MACA,IAAG;AAAA,MACH,WAAU;AAAA,MACV,SAAU;AAAA,MACV,SAAQ;AAAA,MACR,WAAU;AAAA,MAER;AAAA,uBAAe,IAAK,CAAE,MAAM,UAC7B,oBAAC,QACA,8BAAC,QAAK,IAAK,KAAK,IAAO,eAAK,OAAO,KAD1B,KAEV,CACC;AAAA,QACF,oBAAC,QACE,mBAAS,KACV,oBAAC,QAAK,IAAK,SAAS,IAAO,mBAAS,OAAO,IAE3C,oBAAC,WAAQ,OAAQ,GAAI,UAAQ,MAC1B,mBAAS,OACZ,GAEF;AAAA;AAAA;AAAA,EACD,GACD;AAEF;AAEA,IAAO,sBAAQ;",
6
6
  "names": []
7
7
  }
@@ -20,7 +20,7 @@ function Page({
20
20
  const classes = clsx("admin-ui-page", className);
21
21
  const effectiveAriaLabel = ariaLabel ?? (typeof title === "string" ? title : "");
22
22
  return /* @__PURE__ */ jsxs(NavigableRegion, { className: classes, ariaLabel: effectiveAriaLabel, children: [
23
- (title || breadcrumbs || badges) && /* @__PURE__ */ jsx(
23
+ (title || breadcrumbs || badges || actions) && /* @__PURE__ */ jsx(
24
24
  Header,
25
25
  {
26
26
  headingLevel,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/page/index.tsx"],
4
- "sourcesContent": ["/**\n * External dependencies\n */\nimport clsx from 'clsx';\n\n/**\n * Internal dependencies\n */\nimport Header from './header';\nimport NavigableRegion from '../navigable-region';\nimport { SidebarToggleFill } from './sidebar-toggle-slot';\n\nfunction Page( {\n\theadingLevel,\n\tbreadcrumbs,\n\tbadges,\n\ttitle,\n\tsubTitle,\n\tchildren,\n\tclassName,\n\tactions,\n\tariaLabel,\n\thasPadding = false,\n\tshowSidebarToggle = true,\n}: {\n\theadingLevel?: 1 | 2 | 3 | 4 | 5 | 6;\n\tbreadcrumbs?: React.ReactNode;\n\tbadges?: React.ReactNode;\n\ttitle?: React.ReactNode;\n\tsubTitle?: React.ReactNode;\n\tchildren: React.ReactNode;\n\tclassName?: string;\n\tactions?: React.ReactNode;\n\tariaLabel?: string;\n\thasPadding?: boolean;\n\tshowSidebarToggle?: boolean;\n} ) {\n\tconst classes = clsx( 'admin-ui-page', className );\n\tconst effectiveAriaLabel =\n\t\tariaLabel ?? ( typeof title === 'string' ? title : '' );\n\n\treturn (\n\t\t<NavigableRegion className={ classes } ariaLabel={ effectiveAriaLabel }>\n\t\t\t{ ( title || breadcrumbs || badges ) && (\n\t\t\t\t<Header\n\t\t\t\t\theadingLevel={ headingLevel }\n\t\t\t\t\tbreadcrumbs={ breadcrumbs }\n\t\t\t\t\tbadges={ badges }\n\t\t\t\t\ttitle={ title }\n\t\t\t\t\tsubTitle={ subTitle }\n\t\t\t\t\tactions={ actions }\n\t\t\t\t\tshowSidebarToggle={ showSidebarToggle }\n\t\t\t\t/>\n\t\t\t) }\n\t\t\t{ hasPadding ? (\n\t\t\t\t<div className=\"admin-ui-page__content has-padding\">\n\t\t\t\t\t{ children }\n\t\t\t\t</div>\n\t\t\t) : (\n\t\t\t\tchildren\n\t\t\t) }\n\t\t</NavigableRegion>\n\t);\n}\n\nPage.SidebarToggleFill = SidebarToggleFill;\n\nexport default Page;\n"],
5
- "mappings": ";AAGA,OAAO,UAAU;AAKjB,OAAO,YAAY;AACnB,OAAO,qBAAqB;AAC5B,SAAS,yBAAyB;AAgChC,SAEE,KAFF;AA9BF,SAAS,KAAM;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb,oBAAoB;AACrB,GAYI;AACH,QAAM,UAAU,KAAM,iBAAiB,SAAU;AACjD,QAAM,qBACL,cAAe,OAAO,UAAU,WAAW,QAAQ;AAEpD,SACC,qBAAC,mBAAgB,WAAY,SAAU,WAAY,oBAC9C;AAAA,cAAS,eAAe,WAC3B;AAAA,MAAC;AAAA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,IACD;AAAA,IAEC,aACD,oBAAC,SAAI,WAAU,sCACZ,UACH,IAEA;AAAA,KAEF;AAEF;AAEA,KAAK,oBAAoB;AAEzB,IAAO,eAAQ;",
4
+ "sourcesContent": ["/**\n * External dependencies\n */\nimport clsx from 'clsx';\n\n/**\n * Internal dependencies\n */\nimport Header from './header';\nimport NavigableRegion from '../navigable-region';\nimport { SidebarToggleFill } from './sidebar-toggle-slot';\n\nfunction Page( {\n\theadingLevel,\n\tbreadcrumbs,\n\tbadges,\n\ttitle,\n\tsubTitle,\n\tchildren,\n\tclassName,\n\tactions,\n\tariaLabel,\n\thasPadding = false,\n\tshowSidebarToggle = true,\n}: {\n\theadingLevel?: 1 | 2 | 3 | 4 | 5 | 6;\n\tbreadcrumbs?: React.ReactNode;\n\tbadges?: React.ReactNode;\n\ttitle?: React.ReactNode;\n\tsubTitle?: React.ReactNode;\n\tchildren: React.ReactNode;\n\tclassName?: string;\n\tactions?: React.ReactNode;\n\tariaLabel?: string;\n\thasPadding?: boolean;\n\tshowSidebarToggle?: boolean;\n} ) {\n\tconst classes = clsx( 'admin-ui-page', className );\n\tconst effectiveAriaLabel =\n\t\tariaLabel ?? ( typeof title === 'string' ? title : '' );\n\n\treturn (\n\t\t<NavigableRegion className={ classes } ariaLabel={ effectiveAriaLabel }>\n\t\t\t{ ( title || breadcrumbs || badges || actions ) && (\n\t\t\t\t<Header\n\t\t\t\t\theadingLevel={ headingLevel }\n\t\t\t\t\tbreadcrumbs={ breadcrumbs }\n\t\t\t\t\tbadges={ badges }\n\t\t\t\t\ttitle={ title }\n\t\t\t\t\tsubTitle={ subTitle }\n\t\t\t\t\tactions={ actions }\n\t\t\t\t\tshowSidebarToggle={ showSidebarToggle }\n\t\t\t\t/>\n\t\t\t) }\n\t\t\t{ hasPadding ? (\n\t\t\t\t<div className=\"admin-ui-page__content has-padding\">\n\t\t\t\t\t{ children }\n\t\t\t\t</div>\n\t\t\t) : (\n\t\t\t\tchildren\n\t\t\t) }\n\t\t</NavigableRegion>\n\t);\n}\n\nPage.SidebarToggleFill = SidebarToggleFill;\n\nexport default Page;\n"],
5
+ "mappings": ";AAGA,OAAO,UAAU;AAKjB,OAAO,YAAY;AACnB,OAAO,qBAAqB;AAC5B,SAAS,yBAAyB;AAgChC,SAEE,KAFF;AA9BF,SAAS,KAAM;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb,oBAAoB;AACrB,GAYI;AACH,QAAM,UAAU,KAAM,iBAAiB,SAAU;AACjD,QAAM,qBACL,cAAe,OAAO,UAAU,WAAW,QAAQ;AAEpD,SACC,qBAAC,mBAAgB,WAAY,SAAU,WAAY,oBAC9C;AAAA,cAAS,eAAe,UAAU,YACrC;AAAA,MAAC;AAAA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,IACD;AAAA,IAEC,aACD,oBAAC,SAAI,WAAU,sCACZ,UACH,IAEA;AAAA,KAEF;AAEF;AAEA,KAAK,oBAAoB;AAEzB,IAAO,eAAQ;",
6
6
  "names": []
7
7
  }
@@ -2,6 +2,28 @@
2
2
  * Internal dependencies
3
3
  */
4
4
  import type { BreadcrumbsProps } from './types';
5
+ /**
6
+ * Renders a breadcrumb navigation trail.
7
+ *
8
+ * All items except the last one must provide a `to` prop for navigation.
9
+ * In development mode, an error is thrown when a non-last item is missing `to`.
10
+ * The last item represents the current page and its `to` prop is optional.
11
+ * Only the last item (when it has no `to` prop) is rendered as an `h1`.
12
+ *
13
+ * @param props
14
+ * @param props.items The breadcrumb items to display.
15
+ *
16
+ * @example
17
+ * ```jsx
18
+ * <Breadcrumbs
19
+ * items={ [
20
+ * { label: 'Home', to: '/' },
21
+ * { label: 'Settings', to: '/settings' },
22
+ * { label: 'General' },
23
+ * ] }
24
+ * />
25
+ * ```
26
+ */
5
27
  export declare const Breadcrumbs: ({ items }: BreadcrumbsProps) => import("react").JSX.Element | null;
6
28
  export default Breadcrumbs;
7
29
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/breadcrumbs/index.tsx"],"names":[],"mappings":"AAUA;;GAEG;AACH,OAAO,KAAK,EACX,gBAAgB,EAEhB,MAAM,SAAS,CAAC;AAwBjB,eAAO,MAAM,WAAW,GAAK,WAAW,gBAAgB,uCAoBvD,CAAC;AAEF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/breadcrumbs/index.tsx"],"names":[],"mappings":"AAUA;;GAEG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,WAAW,GAAK,WAAW,gBAAgB,uCA2CvD,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -5,14 +5,17 @@ export interface BreadcrumbItem {
5
5
  label: string;
6
6
  /**
7
7
  * The router path that the breadcrumb item should link to.
8
- * It is optional because the current item does not have a link.
8
+ * It is optional for the last item (the current page).
9
+ * All preceding items should provide a `to` prop.
9
10
  */
10
11
  to?: string;
11
12
  }
12
13
  export interface BreadcrumbsProps extends React.HTMLAttributes<HTMLElement> {
13
14
  /**
14
15
  * An array of items to display in the breadcrumb trail.
15
- * The last item is considered the current item.
16
+ * The last item is considered the current item and has an optional `to` prop.
17
+ * All preceding items must have a `to` prop — in development mode,
18
+ * an error is thrown when this requirement is not met.
16
19
  */
17
20
  items: BreadcrumbItem[];
18
21
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/breadcrumbs/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC9B;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,EAAE,CAAC,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,gBAAiB,SAAQ,KAAK,CAAC,cAAc,CAAE,WAAW,CAAE;IAC5E;;;OAGG;IACH,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC1B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/breadcrumbs/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC9B;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;;OAIG;IACH,EAAE,CAAC,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,gBAAiB,SAAQ,KAAK,CAAC,cAAc,CAAE,WAAW,CAAE;IAC5E;;;;;OAKG;IACH,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC1B"}
@@ -13,4 +13,9 @@ export declare const Default: Story;
13
13
  export declare const WithSubtitle: Story;
14
14
  export declare const WithBreadcrumbs: Story;
15
15
  export declare const WithBreadcrumbsAndSubtitle: Story;
16
+ export declare const WithoutHeader: Story;
17
+ export declare const WithTitleAndBadges: Story;
18
+ export declare const WithBreadcrumbsAndBadges: Story;
19
+ export declare const WithActions: Story;
20
+ export declare const FullHeader: Story;
16
21
  //# sourceMappingURL=index.story.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.story.d.ts","sourceRoot":"","sources":["../../../src/page/stories/index.story.tsx"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAG5D;;GAEG;AACH,OAAO,IAAI,MAAM,IAAI,CAAC;AAItB,QAAA,MAAM,IAAI,EAAE,IAAI,CAAE,OAAO,IAAI,CAc5B,CAAC;AAEF,eAAe,IAAI,CAAC;AAEpB,KAAK,KAAK,GAAG,QAAQ,CAAE,OAAO,IAAI,CAAE,CAAC;AAErC,eAAO,MAAM,OAAO,EAAE,KAOrB,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,KAQ1B,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,KAc7B,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,KAexC,CAAC"}
1
+ {"version":3,"file":"index.story.d.ts","sourceRoot":"","sources":["../../../src/page/stories/index.story.tsx"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAI5D;;GAEG;AACH,OAAO,IAAI,MAAM,IAAI,CAAC;AAItB,QAAA,MAAM,IAAI,EAAE,IAAI,CAAE,OAAO,IAAI,CAc5B,CAAC;AAEF,eAAe,IAAI,CAAC;AAEpB,KAAK,KAAK,GAAG,QAAQ,CAAE,OAAO,IAAI,CAAE,CAAC;AAErC,eAAO,MAAM,OAAO,EAAE,KAOrB,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,KAQ1B,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,KAc7B,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,KAexC,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,KAM3B,CAAC;AAEF,eAAO,MAAM,kBAAkB,EAAE,KAQhC,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,KAetC,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,KAiBzB,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,KA0BxB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/admin-ui",
3
- "version": "1.10.0",
3
+ "version": "1.11.0",
4
4
  "description": "Generic components to be used in the Admin UI.",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -43,15 +43,18 @@
43
43
  },
44
44
  "react-native": "src/index",
45
45
  "types": "build-types",
46
- "sideEffects": false,
46
+ "sideEffects": [
47
+ "build-style/**",
48
+ "src/**/*.scss"
49
+ ],
47
50
  "dependencies": {
48
- "@wordpress/base-styles": "^6.18.0",
49
- "@wordpress/components": "^32.4.0",
50
- "@wordpress/element": "^6.42.0",
51
- "@wordpress/i18n": "^6.15.0",
52
- "@wordpress/private-apis": "^1.42.0",
53
- "@wordpress/route": "^0.8.0",
54
- "@wordpress/ui": "^0.9.0",
51
+ "@wordpress/base-styles": "^6.19.0",
52
+ "@wordpress/components": "^32.5.0",
53
+ "@wordpress/element": "^6.43.0",
54
+ "@wordpress/i18n": "^6.16.0",
55
+ "@wordpress/private-apis": "^1.43.0",
56
+ "@wordpress/route": "^0.9.0",
57
+ "@wordpress/ui": "^0.10.0",
55
58
  "clsx": "^2.1.1"
56
59
  },
57
60
  "peerDependencies": {
@@ -60,5 +63,5 @@
60
63
  "publishConfig": {
61
64
  "access": "public"
62
65
  },
63
- "gitHead": "c20787b1778ae64c2db65643b1c236309d68e6ba"
66
+ "gitHead": "2cea90674d11aa521ec3f71652fb3a6a4c383969"
64
67
  }
@@ -11,38 +11,47 @@ import {
11
11
  /**
12
12
  * Internal dependencies
13
13
  */
14
- import type {
15
- BreadcrumbsProps,
16
- BreadcrumbItem as BreadcrumbItemType,
17
- } from './types';
18
-
19
- const BreadcrumbItem = ( {
20
- item: { label, to },
21
- }: {
22
- item: BreadcrumbItemType;
23
- } ) => {
24
- if ( ! to ) {
25
- return (
26
- <li>
27
- <Heading level={ 1 } truncate>
28
- { label }
29
- </Heading>
30
- </li>
31
- );
32
- }
33
-
34
- return (
35
- <li>
36
- <Link to={ to }>{ label }</Link>
37
- </li>
38
- );
39
- };
14
+ import type { BreadcrumbsProps } from './types';
40
15
 
16
+ /**
17
+ * Renders a breadcrumb navigation trail.
18
+ *
19
+ * All items except the last one must provide a `to` prop for navigation.
20
+ * In development mode, an error is thrown when a non-last item is missing `to`.
21
+ * The last item represents the current page and its `to` prop is optional.
22
+ * Only the last item (when it has no `to` prop) is rendered as an `h1`.
23
+ *
24
+ * @param props
25
+ * @param props.items The breadcrumb items to display.
26
+ *
27
+ * @example
28
+ * ```jsx
29
+ * <Breadcrumbs
30
+ * items={ [
31
+ * { label: 'Home', to: '/' },
32
+ * { label: 'Settings', to: '/settings' },
33
+ * { label: 'General' },
34
+ * ] }
35
+ * />
36
+ * ```
37
+ */
41
38
  export const Breadcrumbs = ( { items }: BreadcrumbsProps ) => {
42
39
  if ( ! items.length ) {
43
40
  return null;
44
41
  }
45
42
 
43
+ const precedingItems = items.slice( 0, -1 );
44
+ const lastItem = items[ items.length - 1 ];
45
+
46
+ if ( process.env.NODE_ENV !== 'production' ) {
47
+ const invalidItem = precedingItems.find( ( item ) => ! item.to );
48
+ if ( invalidItem ) {
49
+ throw new Error(
50
+ `Breadcrumbs: item "${ invalidItem.label }" is missing a \`to\` prop. All items except the last one must have a \`to\` prop.`
51
+ );
52
+ }
53
+ }
54
+
46
55
  return (
47
56
  <nav aria-label={ __( 'Breadcrumbs' ) }>
48
57
  <HStack
@@ -52,9 +61,20 @@ export const Breadcrumbs = ( { items }: BreadcrumbsProps ) => {
52
61
  justify="flex-start"
53
62
  alignment="center"
54
63
  >
55
- { items.map( ( item, index ) => (
56
- <BreadcrumbItem key={ index } item={ item } />
64
+ { precedingItems.map( ( item, index ) => (
65
+ <li key={ index }>
66
+ <Link to={ item.to }>{ item.label }</Link>
67
+ </li>
57
68
  ) ) }
69
+ <li>
70
+ { lastItem.to ? (
71
+ <Link to={ lastItem.to }>{ lastItem.label }</Link>
72
+ ) : (
73
+ <Heading level={ 1 } truncate>
74
+ { lastItem.label }
75
+ </Heading>
76
+ ) }
77
+ </li>
58
78
  </HStack>
59
79
  </nav>
60
80
  );
@@ -0,0 +1,169 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { render, screen } from '@testing-library/react';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import { Breadcrumbs } from '..';
10
+
11
+ jest.mock( '@wordpress/route', () => ( {
12
+ Link: ( { to, children }: { to: string; children: React.ReactNode } ) => (
13
+ <a href={ to }>{ children }</a>
14
+ ),
15
+ } ) );
16
+
17
+ describe( 'Breadcrumbs', () => {
18
+ describe( 'validation', () => {
19
+ it( 'should throw when a preceding item is missing `to`', () => {
20
+ expect( () =>
21
+ render(
22
+ <Breadcrumbs
23
+ items={ [
24
+ { label: 'Home' },
25
+ { label: 'Settings', to: '/settings' },
26
+ { label: 'General' },
27
+ ] }
28
+ />
29
+ )
30
+ ).toThrow( /item "Home" is missing a `to` prop/ );
31
+ expect( console ).toHaveErrored();
32
+ } );
33
+
34
+ it( 'should throw for the first preceding item missing `to`', () => {
35
+ expect( () =>
36
+ render(
37
+ <Breadcrumbs
38
+ items={ [
39
+ { label: 'Home' },
40
+ { label: 'Settings' },
41
+ { label: 'General' },
42
+ ] }
43
+ />
44
+ )
45
+ ).toThrow( /item "Home" is missing a `to` prop/ );
46
+ expect( console ).toHaveErrored();
47
+ } );
48
+
49
+ it( 'should not throw when all preceding items have `to`', () => {
50
+ expect( () =>
51
+ render(
52
+ <Breadcrumbs
53
+ items={ [
54
+ { label: 'Home', to: '/' },
55
+ { label: 'Settings', to: '/settings' },
56
+ { label: 'General' },
57
+ ] }
58
+ />
59
+ )
60
+ ).not.toThrow();
61
+ } );
62
+
63
+ it( 'should not throw when there is only one item without `to`', () => {
64
+ expect( () =>
65
+ render( <Breadcrumbs items={ [ { label: 'Dashboard' } ] } /> )
66
+ ).not.toThrow();
67
+ } );
68
+
69
+ it( 'should not throw when items is empty', () => {
70
+ expect( () =>
71
+ render( <Breadcrumbs items={ [] } /> )
72
+ ).not.toThrow();
73
+ } );
74
+ } );
75
+
76
+ describe( 'rendering', () => {
77
+ it( 'should render nothing when items is empty', () => {
78
+ const { container } = render( <Breadcrumbs items={ [] } /> );
79
+ expect( container ).toBeEmptyDOMElement();
80
+ } );
81
+
82
+ it( 'should render the last item as an h1 when it has no `to`', () => {
83
+ render(
84
+ <Breadcrumbs
85
+ items={ [
86
+ { label: 'Home', to: '/' },
87
+ { label: 'Current Page' },
88
+ ] }
89
+ />
90
+ );
91
+
92
+ expect(
93
+ screen.getByRole( 'heading', { level: 1 } )
94
+ ).toHaveTextContent( 'Current Page' );
95
+ } );
96
+
97
+ it( 'should render the last item as a link when it has `to`', () => {
98
+ render(
99
+ <Breadcrumbs
100
+ items={ [
101
+ { label: 'Home', to: '/' },
102
+ { label: 'Settings', to: '/settings' },
103
+ ] }
104
+ />
105
+ );
106
+
107
+ expect(
108
+ screen.queryByRole( 'heading', { level: 1 } )
109
+ ).not.toBeInTheDocument();
110
+
111
+ const links = screen.getAllByRole( 'link' );
112
+ expect( links ).toHaveLength( 2 );
113
+ expect( links[ 1 ] ).toHaveTextContent( 'Settings' );
114
+ expect( links[ 1 ] ).toHaveAttribute( 'href', '/settings' );
115
+ } );
116
+
117
+ it( 'should render preceding items as links', () => {
118
+ render(
119
+ <Breadcrumbs
120
+ items={ [
121
+ { label: 'Home', to: '/' },
122
+ { label: 'Settings', to: '/settings' },
123
+ { label: 'General' },
124
+ ] }
125
+ />
126
+ );
127
+
128
+ const links = screen.getAllByRole( 'link' );
129
+ expect( links ).toHaveLength( 2 );
130
+ expect( links[ 0 ] ).toHaveTextContent( 'Home' );
131
+ expect( links[ 0 ] ).toHaveAttribute( 'href', '/' );
132
+ expect( links[ 1 ] ).toHaveTextContent( 'Settings' );
133
+ expect( links[ 1 ] ).toHaveAttribute( 'href', '/settings' );
134
+ } );
135
+
136
+ it( 'should never render preceding items as headings', () => {
137
+ render(
138
+ <Breadcrumbs
139
+ items={ [
140
+ { label: 'Home', to: '/' },
141
+ { label: 'Settings', to: '/settings' },
142
+ { label: 'General' },
143
+ ] }
144
+ />
145
+ );
146
+
147
+ const headings = screen.getAllByRole( 'heading', { level: 1 } );
148
+ expect( headings ).toHaveLength( 1 );
149
+ expect( headings[ 0 ] ).toHaveTextContent( 'General' );
150
+ } );
151
+
152
+ it( 'should render a single item without `to` as an h1', () => {
153
+ render( <Breadcrumbs items={ [ { label: 'Dashboard' } ] } /> );
154
+
155
+ expect(
156
+ screen.getByRole( 'heading', { level: 1 } )
157
+ ).toHaveTextContent( 'Dashboard' );
158
+ expect( screen.queryByRole( 'link' ) ).not.toBeInTheDocument();
159
+ } );
160
+
161
+ it( 'should render inside a nav with an accessible label', () => {
162
+ render( <Breadcrumbs items={ [ { label: 'Home', to: '/' } ] } /> );
163
+
164
+ expect(
165
+ screen.getByRole( 'navigation', { name: 'Breadcrumbs' } )
166
+ ).toBeInTheDocument();
167
+ } );
168
+ } );
169
+ } );
@@ -6,7 +6,8 @@ export interface BreadcrumbItem {
6
6
 
7
7
  /**
8
8
  * The router path that the breadcrumb item should link to.
9
- * It is optional because the current item does not have a link.
9
+ * It is optional for the last item (the current page).
10
+ * All preceding items should provide a `to` prop.
10
11
  */
11
12
  to?: string;
12
13
  }
@@ -14,7 +15,9 @@ export interface BreadcrumbItem {
14
15
  export interface BreadcrumbsProps extends React.HTMLAttributes< HTMLElement > {
15
16
  /**
16
17
  * An array of items to display in the breadcrumb trail.
17
- * The last item is considered the current item.
18
+ * The last item is considered the current item and has an optional `to` prop.
19
+ * All preceding items must have a `to` prop — in development mode,
20
+ * an error is thrown when this requirement is not met.
18
21
  */
19
22
  items: BreadcrumbItem[];
20
23
  /**
@@ -41,7 +41,7 @@ function Page( {
41
41
 
42
42
  return (
43
43
  <NavigableRegion className={ classes } ariaLabel={ effectiveAriaLabel }>
44
- { ( title || breadcrumbs || badges ) && (
44
+ { ( title || breadcrumbs || badges || actions ) && (
45
45
  <Header
46
46
  headingLevel={ headingLevel }
47
47
  breadcrumbs={ breadcrumbs }
@@ -2,7 +2,8 @@
2
2
  * External dependencies
3
3
  */
4
4
  import type { Meta, StoryObj } from '@storybook/react-vite';
5
- import { __experimentalText as Text } from '@wordpress/components';
5
+ // eslint-disable-next-line @wordpress/use-recommended-components -- admin-ui is a bundled package that depends on @wordpress/ui
6
+ import { Badge, Button, Text } from '@wordpress/ui';
6
7
 
7
8
  /**
8
9
  * Internal dependencies
@@ -82,3 +83,85 @@ export const WithBreadcrumbsAndSubtitle: Story = {
82
83
  children: <Text>Page content here</Text>,
83
84
  },
84
85
  };
86
+
87
+ export const WithoutHeader: Story = {
88
+ args: {
89
+ showSidebarToggle: false,
90
+ hasPadding: true,
91
+ children: <Text>Page content here</Text>,
92
+ },
93
+ };
94
+
95
+ export const WithTitleAndBadges: Story = {
96
+ args: {
97
+ title: 'Page title',
98
+ badges: <Badge intent="informational">Status</Badge>,
99
+ showSidebarToggle: false,
100
+ hasPadding: true,
101
+ children: <Text>Page content here</Text>,
102
+ },
103
+ };
104
+
105
+ export const WithBreadcrumbsAndBadges: Story = {
106
+ args: {
107
+ showSidebarToggle: false,
108
+ breadcrumbs: (
109
+ <Breadcrumbs
110
+ items={ [
111
+ { label: 'Root breadcrumb', to: '/connectors' },
112
+ { label: 'Level 1 breadcrumb' },
113
+ ] }
114
+ />
115
+ ),
116
+ badges: <Badge intent="none">Published</Badge>,
117
+ hasPadding: true,
118
+ children: <Text>Page content here</Text>,
119
+ },
120
+ };
121
+
122
+ export const WithActions: Story = {
123
+ args: {
124
+ title: 'Page title',
125
+ actions: (
126
+ <>
127
+ <Button size="compact" variant="outline">
128
+ Cancel
129
+ </Button>
130
+ <Button size="compact" variant="solid">
131
+ Save
132
+ </Button>
133
+ </>
134
+ ),
135
+ showSidebarToggle: false,
136
+ hasPadding: true,
137
+ children: <Text>Page content here</Text>,
138
+ },
139
+ };
140
+
141
+ export const FullHeader: Story = {
142
+ args: {
143
+ subTitle: 'All of the subtitle text you need goes here.',
144
+ breadcrumbs: (
145
+ <Breadcrumbs
146
+ items={ [
147
+ { label: 'Root breadcrumb', to: '/connectors' },
148
+ { label: 'Level 1 breadcrumb' },
149
+ ] }
150
+ />
151
+ ),
152
+ badges: <Badge intent="informational">Status</Badge>,
153
+ actions: (
154
+ <>
155
+ <Button size="compact" variant="outline">
156
+ Cancel
157
+ </Button>
158
+ <Button size="compact" variant="solid">
159
+ Save
160
+ </Button>
161
+ </>
162
+ ),
163
+ showSidebarToggle: false,
164
+ hasPadding: true,
165
+ children: <Text>Page content here</Text>,
166
+ },
167
+ };
@@ -1,6 +1,10 @@
1
1
  .admin-ui-page {
2
2
  display: flex;
3
3
  height: 100%;
4
+ // @TODO: Revisit page background color. Consider using
5
+ // --wpds-color-bg-surface-neutral once existing pages (e.g. Styles)
6
+ // are updated to handle the contrast change.
7
+ // See https://github.com/WordPress/gutenberg/pull/76548
4
8
  background-color: var(--wpds-color-bg-surface-neutral-strong);
5
9
  color: var(--wpds-color-fg-content-neutral);
6
10
  position: relative;