@automattic/vip-design-system 2.4.5 → 2.6.1

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.
Files changed (97) hide show
  1. package/build/system/Breadcrumbs/Breadcrumbs.d.ts +1 -0
  2. package/build/system/Breadcrumbs/Breadcrumbs.js +75 -20
  3. package/build/system/Breadcrumbs/Breadcrumbs.stories.d.ts +2 -0
  4. package/build/system/Breadcrumbs/Breadcrumbs.stories.js +47 -7
  5. package/build/system/Breadcrumbs/Breadcrumbs.test.js +72 -0
  6. package/build/system/Breadcrumbs/styles.d.ts +2 -0
  7. package/build/system/Breadcrumbs/styles.js +8 -2
  8. package/build/system/Dropdown/Dropdown.d.ts +25 -36
  9. package/build/system/Dropdown/Dropdown.js +60 -99
  10. package/build/system/Dropdown/Dropdown.stories.d.ts +1 -26
  11. package/build/system/Dropdown/Dropdown.test.js +51 -28
  12. package/build/system/Dropdown/DropdownContent.d.ts +14 -10
  13. package/build/system/Dropdown/DropdownContent.js +43 -47
  14. package/build/system/Dropdown/DropdownItem.d.ts +20 -32
  15. package/build/system/Dropdown/DropdownItem.js +86 -103
  16. package/build/system/Dropdown/DropdownLabel.d.ts +11 -7
  17. package/build/system/Dropdown/DropdownLabel.js +29 -29
  18. package/build/system/Dropdown/DropdownSeparator.d.ts +10 -6
  19. package/build/system/Dropdown/DropdownSeparator.js +28 -28
  20. package/build/system/Dropdown/index.d.ts +17 -39
  21. package/build/system/Dropdown/index.js +23 -50
  22. package/build/system/FilterDropdown/FilterDropdown.d.ts +27 -0
  23. package/build/system/FilterDropdown/FilterDropdown.js +75 -0
  24. package/build/system/FilterDropdown/FilterDropdown.stories.d.ts +18 -0
  25. package/build/system/FilterDropdown/FilterDropdown.stories.js +46 -0
  26. package/build/system/FilterDropdown/FilterDropdown.test.d.ts +1 -0
  27. package/build/system/FilterDropdown/FilterDropdown.test.js +53 -0
  28. package/build/system/Hr/Hr.d.ts +7 -0
  29. package/build/system/Hr/Hr.js +22 -0
  30. package/build/system/Hr/Hr.stories.d.ts +23 -0
  31. package/build/system/Hr/Hr.stories.js +30 -0
  32. package/build/system/Hr/Hr.test.d.ts +1 -0
  33. package/build/system/Hr/Hr.test.js +41 -0
  34. package/build/system/Link/Link.d.ts +11 -1
  35. package/build/system/Link/Link.js +16 -1
  36. package/build/system/Link/Link.stories.d.ts +14 -1
  37. package/build/system/Link/Link.stories.js +16 -3
  38. package/build/system/Nav/styles.js +2 -1
  39. package/build/system/Page/Page.d.ts +2 -0
  40. package/build/system/Page/Page.js +10 -0
  41. package/build/system/Page/Page.test.d.ts +1 -0
  42. package/build/system/Page/Page.test.js +41 -0
  43. package/build/system/index.d.ts +3 -1
  44. package/build/system/index.js +4 -0
  45. package/build/system/theme/index.d.ts +889 -23
  46. package/build/system/theme/index.js +7 -8
  47. package/build/system/utils/stories/CustomLink.d.ts +1 -0
  48. package/build/system/utils/stories/CustomLink.js +7 -1
  49. package/package.json +1 -1
  50. package/src/system/Breadcrumbs/Breadcrumbs.stories.tsx +32 -3
  51. package/src/system/Breadcrumbs/Breadcrumbs.test.tsx +60 -0
  52. package/src/system/Breadcrumbs/Breadcrumbs.tsx +100 -29
  53. package/src/system/Breadcrumbs/styles.ts +11 -0
  54. package/src/system/Dropdown/{Dropdown.test.js → Dropdown.test.tsx} +2 -1
  55. package/src/system/Dropdown/Dropdown.tsx +72 -0
  56. package/src/system/Dropdown/DropdownContent.tsx +46 -0
  57. package/src/system/Dropdown/DropdownItem.tsx +112 -0
  58. package/src/system/Dropdown/DropdownLabel.tsx +29 -0
  59. package/src/system/Dropdown/DropdownSeparator.tsx +28 -0
  60. package/src/system/Dropdown/{index.js → index.ts} +1 -3
  61. package/src/system/FilterDropdown/FilterDropdown.stories.tsx +57 -0
  62. package/src/system/FilterDropdown/FilterDropdown.test.tsx +52 -0
  63. package/src/system/FilterDropdown/FilterDropdown.tsx +92 -0
  64. package/src/system/Hr/Hr.stories.tsx +48 -0
  65. package/src/system/Hr/Hr.test.tsx +22 -0
  66. package/src/system/Hr/Hr.tsx +11 -0
  67. package/src/system/Link/Link.stories.tsx +42 -1
  68. package/src/system/Link/Link.tsx +17 -8
  69. package/src/system/Nav/styles.ts +1 -0
  70. package/src/system/Page/Page.test.tsx +22 -0
  71. package/src/system/Page/Page.tsx +3 -0
  72. package/src/system/index.js +4 -0
  73. package/src/system/theme/index.js +7 -8
  74. package/src/system/utils/stories/CustomLink.tsx +6 -0
  75. package/tokens/valet-core/$metadata.json +1 -17
  76. package/tokens/valet-core/$themes.json +0 -2586
  77. package/src/system/Dropdown/Dropdown.js +0 -101
  78. package/src/system/Dropdown/DropdownContent.js +0 -50
  79. package/src/system/Dropdown/DropdownItem.js +0 -108
  80. package/src/system/Dropdown/DropdownLabel.js +0 -31
  81. package/src/system/Dropdown/DropdownSeparator.js +0 -30
  82. package/tokens/valet-core/figma-parsely-web-type.json +0 -1217
  83. package/tokens/valet-core/figma-valet-web-type.json +0 -1217
  84. package/tokens/valet-core/figma-wpvip-services-web-type.json +0 -1267
  85. package/tokens/valet-core/figma-wpvip-web-type.json +0 -1213
  86. package/tokens/valet-core/parsely-web-color.json +0 -729
  87. package/tokens/valet-core/parsely-web-core.json +0 -172
  88. package/tokens/valet-core/parsely-web-type.json +0 -362
  89. package/tokens/valet-core/valet-web-color.json +0 -677
  90. package/tokens/valet-core/valet-web-core.json +0 -172
  91. package/tokens/valet-core/wpvip-services-web-color.json +0 -730
  92. package/tokens/valet-core/wpvip-services-web-core.json +0 -172
  93. package/tokens/valet-core/wpvip-services-web-type.json +0 -412
  94. package/tokens/valet-core/wpvip-web-color-dark.json +0 -735
  95. package/tokens/valet-core/wpvip-web-color.json +0 -730
  96. package/tokens/valet-core/wpvip-web-type.json +0 -412
  97. package/tokens/valet-core/wpvip-web.json +0 -1310
@@ -6,6 +6,7 @@ import ColorBuilder from './colors';
6
6
  import ValetDark from './generated/valet-theme-dark.json';
7
7
  import Valet from './generated/valet-theme-light.json';
8
8
  import ThemeBuilder from './getPropValue';
9
+ import { linkUnderlineProperties } from '../Link/Link';
9
10
 
10
11
  // Light
11
12
  const { getPropValue, getVariants, ValetTheme, getHeadingStyles } = ThemeBuilder( Valet );
@@ -402,21 +403,19 @@ export default {
402
403
 
403
404
  links: {
404
405
  primary: {
406
+ ...linkUnderlineProperties,
407
+
405
408
  color: 'link',
406
409
  '&:visited': {
407
410
  color: 'links.visited',
408
411
  },
409
412
  '&:hover': {
410
413
  color: 'links.hover',
411
- textDecorationLine: 'underline',
412
- textDecorationThickness: '0.125rem',
414
+ textDecorationThickness: '0.15rem',
413
415
  },
414
416
  '&:active': {
415
417
  color: 'links.active',
416
418
  },
417
-
418
- textDecorationThickness: '0.125rem',
419
- textUnderlineOffset: '0.250rem',
420
419
  },
421
420
  'button-primary': {
422
421
  variant: 'buttons.primary',
@@ -526,12 +525,12 @@ export default {
526
525
  fontWeight: 'body',
527
526
  color: 'text',
528
527
  backgroundColor: 'backgrounds.primary',
529
- '-webkit-font-smoothing': 'antialiased',
530
- '-moz-osx-font-smoothing': 'grayscale',
528
+ webkitFontSmoothing: 'antialiased',
529
+ mozOsxFontmoothing: 'grayscale',
531
530
  a: {
532
531
  '&:hover': {
533
532
  textDecorationLine: 'underline',
534
- textDecorationThickness: '0.125rem',
533
+ textDecorationThickness: '0.1rem',
535
534
  textUnderlineOffset: '0.250rem',
536
535
  },
537
536
  },
@@ -1,2 +1,3 @@
1
1
  import React from 'react';
2
2
  export declare const CustomLink: React.ForwardRefExoticComponent<React.RefAttributes<HTMLAnchorElement>>;
3
+ export declare const CustomLinkComponentized: React.ForwardRefExoticComponent<React.RefAttributes<HTMLAnchorElement>>;
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
 
3
3
  exports.__esModule = true;
4
- exports.CustomLink = void 0;
4
+ exports.CustomLinkComponentized = exports.CustomLink = void 0;
5
5
  var _react = _interopRequireWildcard(require("react"));
6
+ var _Link = require("../../Link/Link");
6
7
  var _jsxRuntime = require("theme-ui/jsx-runtime");
7
8
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
8
9
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { "default": e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n["default"] = e, t && t.set(e, n), n; }
@@ -13,4 +14,9 @@ function (props, ref) {
13
14
  return (0, _jsxRuntime.jsx)("a", _extends({}, props, {
14
15
  ref: ref
15
16
  }));
17
+ });
18
+ var CustomLinkComponentized = exports.CustomLinkComponentized = /*#__PURE__*/(0, _react.forwardRef)(function (props, ref) {
19
+ return (0, _jsxRuntime.jsx)(_Link.Link, _extends({}, props, {
20
+ ref: ref
21
+ }));
16
22
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/vip-design-system",
3
- "version": "2.4.5",
3
+ "version": "2.6.1",
4
4
  "main": "build/system/index.js",
5
5
  "scripts": {
6
6
  "build-storybook": "storybook build",
@@ -1,12 +1,13 @@
1
+ /** @jsxImportSource theme-ui */
2
+
1
3
  import React from 'react';
2
4
 
3
5
  import { Breadcrumbs as Breadcrumbs } from './Breadcrumbs';
6
+ import { Box } from '../Box';
7
+ import { CustomLink, CustomLinkComponentized } from '../utils/stories/CustomLink';
4
8
 
5
9
  import type { StoryObj } from '@storybook/react';
6
10
 
7
- // eslint-disable-next-line jsx-a11y/anchor-has-content
8
- const CustomLink = props => <a { ...props } />;
9
-
10
11
  export default {
11
12
  title: 'Navigation/Breadcrumbs',
12
13
  component: Breadcrumbs,
@@ -78,3 +79,31 @@ export const Default: Story = {
78
79
  />
79
80
  ),
80
81
  };
82
+
83
+ export const Collapsible: Story = {
84
+ render: () => (
85
+ <Box sx={ { display: 'flex', flexDirection: 'column', gap: 4 } }>
86
+ <p>
87
+ When entering Mobile views, the first and the last link will appear. A button with a … will
88
+ also be visible. Once pressed, the rest of the links become available, and the focus is
89
+ moved to the next link.
90
+ </p>
91
+
92
+ <hr sx={ { width: '100%', my: 4 } } />
93
+
94
+ <Breadcrumbs
95
+ wrapMode="collapsible"
96
+ LinkComponent={ CustomLinkComponentized }
97
+ label="Nav Breadcrumbs"
98
+ links={ [
99
+ { href: '/', label: 'Home' },
100
+ { href: 'https://datadog.com/', label: 'Data dog' },
101
+ { href: 'https://newrelic.com/', label: 'New Relic' },
102
+ { href: 'https://rollbar.com/', label: 'Rollbar' },
103
+ { href: 'https://areallylong.com/', label: 'A really long name' },
104
+ { href: 'https://google.com/', label: 'I am the last item' },
105
+ ] }
106
+ />
107
+ </Box>
108
+ ),
109
+ };
@@ -2,12 +2,17 @@
2
2
  /* eslint-disable @typescript-eslint/ban-ts-comment */
3
3
  // @ts-nocheck
4
4
  import { render, screen } from '@testing-library/react';
5
+ import userEvent from '@testing-library/user-event';
6
+ import * as matchMedia from '@theme-ui/match-media';
5
7
  import { axe } from 'jest-axe';
6
8
  import { ThemeUIProvider } from 'theme-ui';
7
9
 
8
10
  import { Breadcrumbs } from './Breadcrumbs';
9
11
  import { theme } from '../';
10
12
 
13
+ jest.mock( '@theme-ui/match-media' );
14
+ const mockBreakpointIndex = matchMedia.useBreakpointIndex;
15
+
11
16
  // eslint-disable-next-line jsx-a11y/anchor-has-content
12
17
  const CustomLink = props => <a { ...props } />;
13
18
 
@@ -39,6 +44,7 @@ describe( '<Breadcrumbs />', () => {
39
44
  } ),
40
45
  } );
41
46
  } );
47
+
42
48
  it( 'renders the Breadcrumbs component', async () => {
43
49
  const { container } = renderComponent();
44
50
 
@@ -53,4 +59,58 @@ describe( '<Breadcrumbs />', () => {
53
59
  // Check for accessibility issues
54
60
  expect( await axe( container ) ).toHaveNoViolations();
55
61
  } );
62
+
63
+ describe( 'wrapMode tests', () => {
64
+ beforeEach( () => {
65
+ mockBreakpointIndex.mockReset();
66
+ } );
67
+
68
+ it( 'expands the breadcrumb items when clicking in the collapsible link', async () => {
69
+ mockBreakpointIndex.mockReturnValue( 0 );
70
+
71
+ const links = [
72
+ { label: 'Home', href: '/' },
73
+ { label: 'Applications', href: '/apps' },
74
+ { label: 'Applications 3', href: '/apps/3' },
75
+ { label: 'Applications 4', href: '/apps/4' },
76
+ { label: 'The last' },
77
+ ];
78
+
79
+ const user = userEvent.setup();
80
+
81
+ const { container } = renderWithTheme(
82
+ <Breadcrumbs
83
+ wrapMode="collapsible"
84
+ label="Main Collapsible Breadcrumb"
85
+ LinkComponent={ CustomLink }
86
+ links={ links }
87
+ />
88
+ );
89
+
90
+ // Should find the nav label
91
+ const navEl = screen.getByLabelText( 'Main Collapsible Breadcrumb' );
92
+
93
+ expect( navEl ).toBeInTheDocument();
94
+
95
+ // Contract should have
96
+ expect( navEl.querySelectorAll( 'li' ) ).toHaveLength( 3 );
97
+
98
+ const pressToShowButton = screen.getByRole( 'button', {
99
+ name: 'Press to show more breadcrumbs',
100
+ } );
101
+
102
+ // Should find all links
103
+ expect( screen.getByText( 'Home' ) ).toBeInTheDocument();
104
+ expect( pressToShowButton ).toBeInTheDocument();
105
+ expect( screen.getByText( 'The last' ) ).toHaveAttribute( 'aria-current', 'page' );
106
+
107
+ // Click to expand the breadcrumbs
108
+ await user.click( pressToShowButton );
109
+
110
+ expect( navEl.querySelectorAll( 'li' ) ).toHaveLength( 5 );
111
+
112
+ // Check for accessibility issues
113
+ expect( await axe( container ) ).toHaveNoViolations();
114
+ } );
115
+ } );
56
116
  } );
@@ -3,12 +3,13 @@
3
3
  import * as NavigationMenu from '@radix-ui/react-navigation-menu';
4
4
  import { useBreakpointIndex } from '@theme-ui/match-media';
5
5
  import classNames from 'classnames';
6
- import React, { Ref, forwardRef } from 'react';
6
+ import { useTranslate } from 'i18n-calypso';
7
+ import React, { forwardRef, useEffect, useRef, useState } from 'react';
7
8
  import { ThemeUIStyleObject } from 'theme-ui';
8
9
 
9
10
  export const VIP_BREACRUMBS = 'vip-breadcrumbs-component';
10
11
 
11
- import { smallestScreenItemStyles } from './styles';
12
+ import { collapsibleSeparatorStyles, smallestScreenItemStyles } from './styles';
12
13
  import { ItemBreadcrumb, NavItemProps, NavRawLink } from '../Nav/NavItem';
13
14
  import { navItemStyles, navMenuListStyles } from '../Nav/styles';
14
15
 
@@ -22,37 +23,39 @@ export type BreadcrumbsLinkProps = {
22
23
  export interface BreacrumbsProps extends NavigationMenu.NavigationMenuProps {
23
24
  className?: string;
24
25
  label?: string;
26
+ wrapMode?: 'collapsible' | 'lastItem';
25
27
  LinkComponent: NavItemProps[ 'as' ];
26
28
  links?: BreadcrumbsLinkProps[];
27
29
  }
28
30
 
29
- export const Breadcrumbs = forwardRef< HTMLElement, BreacrumbsProps >(
30
- (
31
- { className, links = [], label = 'Breadcrumbs', LinkComponent = NavRawLink }: BreacrumbsProps,
32
- ref: Ref< HTMLElement >
33
- ) => {
34
- // The breadcrumb shrinks on smaller screens (mobile) and we need to hide some links
35
- const bpIndex = useBreakpointIndex( { defaultIndex: 1 } );
36
-
37
- const isSmallestScreen = bpIndex < 3;
38
-
39
- let penultimateLink: BreadcrumbsLinkProps | null = null;
40
- let lastLink: BreadcrumbsLinkProps | null = null;
41
- let otherLinks: BreadcrumbsLinkProps[] = [];
42
-
43
- const totalLinks = links?.length || 0;
44
-
45
- if ( totalLinks === 0 ) {
46
- return null;
47
- }
31
+ const breadcrumbLinks = (
32
+ links: BreadcrumbsLinkProps[],
33
+ isSmallestScreen: boolean = false,
34
+ wrapMode: BreacrumbsProps[ 'wrapMode' ],
35
+ showAllItems: boolean = false
36
+ ): {
37
+ separatorLink: boolean;
38
+ lastLink: BreadcrumbsLinkProps | null;
39
+ otherLinks: BreadcrumbsLinkProps[];
40
+ } => {
41
+ let separatorLink: boolean = false;
42
+ let lastLink: BreadcrumbsLinkProps | null = null;
43
+ let otherLinks: BreadcrumbsLinkProps[] = [];
44
+
45
+ const totalLinks = links?.length;
46
+
47
+ if ( totalLinks === 1 ) {
48
+ lastLink = links?.[ 0 ];
49
+ otherLinks = [];
50
+ }
48
51
 
49
- if ( totalLinks === 1 ) {
50
- lastLink = links?.[ 0 ];
51
- otherLinks = [];
52
- }
52
+ if ( totalLinks > 1 ) {
53
+ const otherLinksRaw = links?.slice( 0, totalLinks - 1 );
54
+ lastLink = links?.[ totalLinks - 1 ];
53
55
 
54
- if ( totalLinks > 1 ) {
55
- penultimateLink = links?.[ totalLinks - 2 ];
56
+ if ( wrapMode === 'lastItem' ) {
57
+ const penultimateLink = links?.[ totalLinks - 2 ];
58
+ lastLink = isSmallestScreen ? null : links?.[ totalLinks - 1 ];
56
59
 
57
60
  otherLinks = isSmallestScreen
58
61
  ? [
@@ -62,11 +65,62 @@ export const Breadcrumbs = forwardRef< HTMLElement, BreacrumbsProps >(
62
65
  sx: smallestScreenItemStyles,
63
66
  },
64
67
  ]
65
- : links?.slice( 0, totalLinks - 1 );
68
+ : otherLinksRaw;
69
+ } else if ( wrapMode === 'collapsible' ) {
70
+ separatorLink = isSmallestScreen && ! showAllItems && totalLinks > 2;
71
+ otherLinks = isSmallestScreen && ! showAllItems ? [ links?.[ 0 ] ] : otherLinksRaw;
72
+ }
73
+ }
66
74
 
67
- lastLink = isSmallestScreen ? null : links?.[ totalLinks - 1 ];
75
+ return { separatorLink, lastLink, otherLinks };
76
+ };
77
+
78
+ export const Breadcrumbs = forwardRef< HTMLElement, BreacrumbsProps >(
79
+ (
80
+ {
81
+ className,
82
+ links = [],
83
+ label = 'Breadcrumbs',
84
+ LinkComponent = NavRawLink,
85
+ wrapMode = 'lastItem',
86
+ }: BreacrumbsProps,
87
+ ref
88
+ ) => {
89
+ const breadcrumbsListRef = useRef< HTMLOListElement >( null );
90
+ const [ showAllItems, setShowAllItems ] = useState( false );
91
+ const translate = useTranslate();
92
+
93
+ // Focus on the next link when the collapsible separator is true
94
+ useEffect( () => {
95
+ if ( wrapMode !== 'collapsible' ) {
96
+ return;
97
+ }
98
+
99
+ const breadcrumbList = breadcrumbsListRef?.current;
100
+
101
+ if ( showAllItems && breadcrumbList ) {
102
+ const nextActiveLink: HTMLAnchorElement | null =
103
+ breadcrumbList.querySelector( 'li:nth-child(2) a' );
104
+
105
+ nextActiveLink?.focus();
106
+ }
107
+ }, [ showAllItems, wrapMode ] );
108
+
109
+ // The breadcrumb shrinks on smaller screens (mobile) and we need to hide some links
110
+ const bpIndex = useBreakpointIndex( { defaultIndex: 1 } );
111
+ const isSmallestScreen = bpIndex < 3;
112
+
113
+ if ( links?.length === 0 ) {
114
+ return null;
68
115
  }
69
116
 
117
+ const { separatorLink, lastLink, otherLinks } = breadcrumbLinks(
118
+ links,
119
+ isSmallestScreen,
120
+ wrapMode,
121
+ showAllItems
122
+ );
123
+
70
124
  return (
71
125
  <NavigationMenu.Root
72
126
  aria-label={ label }
@@ -77,6 +131,7 @@ export const Breadcrumbs = forwardRef< HTMLElement, BreacrumbsProps >(
77
131
  <NavigationMenu.List
78
132
  className={ classNames( `${ VIP_BREACRUMBS }-list` ) }
79
133
  sx={ navMenuListStyles( 'horizontal' ) }
134
+ ref={ breadcrumbsListRef }
80
135
  asChild
81
136
  >
82
137
  <ol>
@@ -92,6 +147,22 @@ export const Breadcrumbs = forwardRef< HTMLElement, BreacrumbsProps >(
92
147
  </ItemBreadcrumb>
93
148
  ) ) }
94
149
 
150
+ { separatorLink && (
151
+ <li
152
+ sx={ {
153
+ ...navItemStyles( 'horizontal', 'breadcrumbs' ),
154
+ } }
155
+ >
156
+ <button
157
+ sx={ collapsibleSeparatorStyles }
158
+ aria-label={ translate( 'Press to show more breadcrumbs' ) }
159
+ onClick={ () => setShowAllItems( true ) }
160
+ >
161
+
162
+ </button>
163
+ </li>
164
+ ) }
165
+
95
166
  { lastLink && (
96
167
  <li
97
168
  sx={ {
@@ -1,3 +1,8 @@
1
+ import { ThemeUIStyleObject } from 'theme-ui';
2
+
3
+ import { linkUnderlineProperties } from '../Link/Link';
4
+ import { breadcrumbsLinkStyles } from '../Nav/styles/variants/breadcrumbs';
5
+
1
6
  export const smallestScreenItemStyles = {
2
7
  '&::before': {
3
8
  display: 'inline-block',
@@ -10,3 +15,9 @@ export const smallestScreenItemStyles = {
10
15
  content: "'←'",
11
16
  },
12
17
  };
18
+
19
+ export const collapsibleSeparatorStyles: ThemeUIStyleObject = {
20
+ all: 'unset',
21
+ ...breadcrumbsLinkStyles,
22
+ ...linkUnderlineProperties,
23
+ };
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import { fireEvent, render, screen } from '@testing-library/react';
5
5
  import { axe } from 'jest-axe';
6
+ import React from 'react';
6
7
 
7
8
  /**
8
9
  * Internal dependencies
@@ -28,6 +29,6 @@ describe( '<Dropdown />', () => {
28
29
  fireEvent.click( getButton() );
29
30
 
30
31
  // Check for accessibility issues
31
- await expect( await axe( container ) ).toHaveNoViolations();
32
+ expect( await axe( container ) ).toHaveNoViolations();
32
33
  } );
33
34
  } );
@@ -0,0 +1,72 @@
1
+ /** @jsxImportSource theme-ui */
2
+
3
+ import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
4
+ import React, { ReactNode } from 'react';
5
+
6
+ import { DropdownContent, DropdownContentProps } from './DropdownContent';
7
+
8
+ const DropdownMenu = DropdownMenuPrimitive.Root;
9
+ const DropdownTrigger = DropdownMenuPrimitive.Trigger;
10
+ const DropdownRadioGroup = DropdownMenuPrimitive.RadioGroup;
11
+ const DropdownItemIndicator = DropdownMenuPrimitive.DropdownMenuItemIndicator;
12
+ const DropdownLabel = DropdownMenuPrimitive.DropdownMenuLabel;
13
+ const DropdownSeparator = DropdownMenuPrimitive.DropdownMenuSeparator;
14
+ const DropdownSub = DropdownMenuPrimitive.DropdownMenuSub;
15
+ const DropdownSubTrigger = DropdownMenuPrimitive.DropdownMenuSubTrigger;
16
+ const DropdownSubContent = DropdownMenuPrimitive.DropdownMenuSubContent;
17
+
18
+ export interface DropdownProps {
19
+ trigger: ReactNode;
20
+ children: ReactNode;
21
+ open?: boolean;
22
+ defaultOpen?: boolean;
23
+ onOpenChange?: ( open: boolean ) => void;
24
+ modal?: boolean;
25
+ dir?: 'ltr' | 'rtl';
26
+ contentProps?: DropdownContentProps;
27
+ portalProps?: object;
28
+ className?: string;
29
+ }
30
+
31
+ export const Dropdown: React.FC< DropdownProps > = ( {
32
+ trigger,
33
+ children,
34
+ open = undefined,
35
+ defaultOpen = false,
36
+ onOpenChange = undefined,
37
+ modal = true,
38
+ dir = 'ltr',
39
+ contentProps = {},
40
+ portalProps = {},
41
+ } ) => (
42
+ <DropdownMenu
43
+ open={ open }
44
+ defaultOpen={ defaultOpen }
45
+ onOpenChange={ onOpenChange }
46
+ modal={ modal }
47
+ dir={ dir }
48
+ >
49
+ <DropdownTrigger className="vip-dropdown-trigger" asChild>
50
+ { trigger }
51
+ </DropdownTrigger>
52
+
53
+ <DropdownMenuPrimitive.Portal { ...portalProps }>
54
+ <DropdownContent { ...contentProps }>
55
+ { children }
56
+ <DropdownMenuPrimitive.Arrow sx={ { fill: 'background', boxShadow: 'high' } } />
57
+ </DropdownContent>
58
+ </DropdownMenuPrimitive.Portal>
59
+ </DropdownMenu>
60
+ );
61
+
62
+ // Exports
63
+ export {
64
+ DropdownTrigger,
65
+ DropdownRadioGroup,
66
+ DropdownItemIndicator,
67
+ DropdownLabel,
68
+ DropdownSeparator,
69
+ DropdownSub,
70
+ DropdownSubTrigger,
71
+ DropdownSubContent,
72
+ };
@@ -0,0 +1,46 @@
1
+ /** @jsxImportSource theme-ui */
2
+
3
+ import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
4
+ import classNames from 'classnames';
5
+ import React from 'react';
6
+
7
+ export interface DropdownContentProps {
8
+ className?: string;
9
+ }
10
+
11
+ export const styles = {
12
+ minWidth: 220,
13
+ borderRadius: 2,
14
+ backgroundColor: 'background',
15
+ boxShadow: 'high',
16
+ px: 2,
17
+ py: 1,
18
+ };
19
+
20
+ export const DropdownContent = React.forwardRef< HTMLDivElement, DropdownContentProps >(
21
+ ( { className, ...props }, forwardRef ) => (
22
+ <DropdownMenuPrimitive.DropdownMenuContent
23
+ className={ classNames( 'vip-dropdown-menu-content', className ) }
24
+ ref={ forwardRef }
25
+ sx={ styles }
26
+ { ...props }
27
+ />
28
+ )
29
+ );
30
+
31
+ DropdownContent.displayName = 'DropdownContent';
32
+
33
+ export const DropdownSubContent = React.forwardRef< HTMLDivElement, DropdownContentProps >(
34
+ ( { className, ...props }, forwardRef ) => (
35
+ <DropdownMenuPrimitive.Portal>
36
+ <DropdownMenuPrimitive.DropdownMenuSubContent
37
+ className={ classNames( 'vip-dropdown-menu-sub-content', className ) }
38
+ ref={ forwardRef }
39
+ sx={ styles }
40
+ { ...props }
41
+ />
42
+ </DropdownMenuPrimitive.Portal>
43
+ )
44
+ );
45
+
46
+ DropdownSubContent.displayName = 'DropdownSubContent';
@@ -0,0 +1,112 @@
1
+ /** @jsxImportSource theme-ui */
2
+
3
+ import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
4
+ import classNames from 'classnames';
5
+ import React from 'react';
6
+ import { ThemeUIStyleObject } from 'theme-ui';
7
+
8
+ export interface DropdownItemProps extends DropdownMenuPrimitive.MenuItemProps {
9
+ className?: string;
10
+ }
11
+
12
+ export interface DropdownRadioItemProps extends DropdownMenuPrimitive.MenuRadioItemProps {
13
+ className?: string;
14
+ }
15
+
16
+ export interface DropdownCheckboxItemProps extends DropdownMenuPrimitive.MenuCheckboxItemProps {
17
+ className?: string;
18
+ }
19
+
20
+ export interface DropdownSubTriggerItemProps
21
+ extends DropdownMenuPrimitive.DropdownMenuSubTriggerProps {
22
+ className?: string;
23
+ }
24
+
25
+ export const styles: ThemeUIStyleObject = {
26
+ unset: 'all',
27
+ cursor: 'pointer',
28
+ display: 'flex',
29
+ alignItems: 'center',
30
+ flexDirection: 'row',
31
+ textAlign: 'left',
32
+ height: 25,
33
+ textDecoration: 'none',
34
+ position: 'relative',
35
+ m: 0,
36
+ color: 'heading',
37
+ px: 2,
38
+ paddingLeft: 3,
39
+ py: 1,
40
+ '&:hover, &:focus': {
41
+ backgroundColor: 'hover',
42
+ textDecoration: 'none',
43
+ },
44
+ '&[data-disabled]': {
45
+ color: 'muted',
46
+ pointerEvents: 'none',
47
+ },
48
+ '&[data-highlighted]': {
49
+ backgroundColor: 'hover',
50
+ color: 'link',
51
+ },
52
+ };
53
+
54
+ export const DropdownItem = React.forwardRef< HTMLDivElement, DropdownItemProps >(
55
+ ( { className, ...props }, forwardRef ) => (
56
+ <DropdownMenuPrimitive.DropdownMenuItem
57
+ className={ classNames( 'vip-dropdown-menu-item', className ) }
58
+ ref={ forwardRef }
59
+ sx={ styles }
60
+ { ...props }
61
+ />
62
+ )
63
+ );
64
+
65
+ DropdownItem.displayName = 'DropdownItem';
66
+
67
+ export const DropdownCheckboxItem = React.forwardRef< HTMLDivElement, DropdownCheckboxItemProps >(
68
+ ( { className, ...props }, forwardRef ) => (
69
+ <DropdownMenuPrimitive.CheckboxItem
70
+ className={ classNames( 'vip-dropdown-checkbox-item', className ) }
71
+ ref={ forwardRef }
72
+ sx={ styles }
73
+ { ...props }
74
+ />
75
+ )
76
+ );
77
+
78
+ DropdownCheckboxItem.displayName = 'DropdownCheckboxItem';
79
+
80
+ export const DropdownRadioItem = React.forwardRef< HTMLDivElement, DropdownRadioItemProps >(
81
+ ( { className, ...props }, forwardRef ) => (
82
+ <DropdownMenuPrimitive.RadioItem
83
+ className={ classNames( 'vip-dropdown-radio-item', className ) }
84
+ ref={ forwardRef }
85
+ sx={ styles }
86
+ { ...props }
87
+ />
88
+ )
89
+ );
90
+
91
+ DropdownRadioItem.displayName = 'DropdownRadioItem';
92
+
93
+ export const DropdownSubTrigger = React.forwardRef< HTMLDivElement, DropdownSubTriggerItemProps >(
94
+ ( { className, ...props }, forwardRef ) => (
95
+ <DropdownMenuPrimitive.SubTrigger
96
+ className={ classNames( 'vip-dropdown-sub-trigger', className ) }
97
+ ref={ forwardRef }
98
+ sx={ {
99
+ ...styles,
100
+ ...{
101
+ '&[data-state="open"]': {
102
+ background: 'highlight',
103
+ color: 'primary',
104
+ },
105
+ },
106
+ } }
107
+ { ...props }
108
+ />
109
+ )
110
+ );
111
+
112
+ DropdownSubTrigger.displayName = 'DropdownSubTrigger';
@@ -0,0 +1,29 @@
1
+ /** @jsxImportSource theme-ui */
2
+
3
+ import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
4
+ import classNames from 'classnames';
5
+ import React from 'react';
6
+
7
+ export interface DropdownLabelProps {
8
+ className?: string;
9
+ }
10
+
11
+ export const styles = {
12
+ paddingLeft: 3,
13
+ fontSize: 12,
14
+ lineHeight: '25px',
15
+ color: 'muted',
16
+ };
17
+
18
+ export const DropdownLabel = React.forwardRef< HTMLDivElement, DropdownLabelProps >(
19
+ ( { className, ...props }, forwardRef ) => (
20
+ <DropdownMenuPrimitive.DropdownMenuLabel
21
+ className={ classNames( 'vip-dropdown-menu-label', className ) }
22
+ ref={ forwardRef }
23
+ sx={ styles }
24
+ { ...props }
25
+ />
26
+ )
27
+ );
28
+
29
+ DropdownLabel.displayName = 'DropdownLabel';