@squiz/resource-browser 1.32.1-alpha.13 → 1.32.1-alpha.14

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.
@@ -1,20 +1,57 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
2
25
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
27
  };
5
28
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const react_1 = __importDefault(require("react"));
29
+ const react_1 = __importStar(require("react"));
7
30
  const Icon_1 = __importDefault(require("../Icons/Icon"));
8
31
  const ResourceBreadcrumb = function ({ hierarchy, onBreadcrumbSelect, onReturnToRoot }) {
9
- return (react_1.default.createElement("nav", { "aria-label": "Resource breadcrumb", className: "text-sm text-gray-600 px-7 my-5" },
10
- react_1.default.createElement("ol", { className: "flex items-center" },
32
+ // Track expanded state
33
+ const [expandedHierarchy, setExpandedHierarchy] = (0, react_1.useState)(false);
34
+ // On update (new page) reset the expanded state
35
+ (0, react_1.useEffect)(() => {
36
+ setExpandedHierarchy(false);
37
+ }, [hierarchy]);
38
+ // Handle toggling the state to expanded
39
+ const handleExpandHierarchy = (0, react_1.useCallback)(() => {
40
+ setExpandedHierarchy(true);
41
+ }, []);
42
+ return (react_1.default.createElement("nav", { "aria-label": "Resource breadcrumb", className: `text-sm text-gray-600 px-7 my-5 resource-breadcrumb ${expandedHierarchy ? 'resource-breadcrumb--expanded' : 'resource-breadcrumb--collapsed'}` },
43
+ react_1.default.createElement("ol", { className: "flex flex-wrap items-center" },
11
44
  react_1.default.createElement("li", { className: "flex items-center mr-3" },
12
45
  react_1.default.createElement("button", { type: "button", onClick: onReturnToRoot },
13
46
  react_1.default.createElement(Icon_1.default, { icon: 'root', "aria-label": "Return to source list", className: "" }))),
14
47
  hierarchy.map(({ id, label }, index) => {
15
- return (react_1.default.createElement("li", { key: `${id.source}-${id.id}`, className: "mr-2 before:content-['/'] before:mr-2" },
16
- index !== hierarchy.length - 1 && (react_1.default.createElement("button", { type: "button", onClick: () => onBreadcrumbSelect(id) }, label)),
17
- index === hierarchy.length - 1 && react_1.default.createElement("span", { className: "font-semibold" }, label)));
48
+ return (react_1.default.createElement("li", { key: `${id.source}-${id.id}`, className: "resource-breadcrumb__item max-md:hidden flex items-center mr-2 before:content-['/'] before:mr-2" },
49
+ index !== hierarchy.length - 1 && (react_1.default.createElement("button", { type: "button", onClick: () => onBreadcrumbSelect(id) },
50
+ react_1.default.createElement("div", { className: `resource-breadcrumb__label`, title: label }, label))),
51
+ index === hierarchy.length - 1 && (react_1.default.createElement("div", { className: `resource-breadcrumb__label md:font-semibold`, title: label }, label)),
52
+ hierarchy.length > 3 && index === 0 && (react_1.default.createElement("div", { className: "resource-breadcrumb__expander flex" },
53
+ react_1.default.createElement("div", { className: "before:content-['/'] mx-2" }),
54
+ react_1.default.createElement("button", { title: "Expand breadcrumb", "aria-label": "Expand breadcrumb", onClick: handleExpandHierarchy, className: "flex items-center justify-center" }, "...")))));
18
55
  }))));
19
56
  };
20
57
  exports.default = ResourceBreadcrumb;
package/lib/index.css CHANGED
@@ -354,6 +354,9 @@
354
354
  white-space: nowrap;
355
355
  border-width: 0;
356
356
  }
357
+ .squiz-rb-scope .collapse {
358
+ visibility: collapse;
359
+ }
357
360
  .squiz-rb-scope .fixed {
358
361
  position: fixed;
359
362
  }
@@ -408,6 +411,10 @@
408
411
  .squiz-rb-scope .m-5 {
409
412
  margin: 1.25rem;
410
413
  }
414
+ .squiz-rb-scope .mx-2 {
415
+ margin-left: 0.5rem;
416
+ margin-right: 0.5rem;
417
+ }
411
418
  .squiz-rb-scope .mx-20 {
412
419
  margin-left: 5rem;
413
420
  margin-right: 5rem;
@@ -603,6 +610,9 @@
603
610
  .squiz-rb-scope .flex-col {
604
611
  flex-direction: column;
605
612
  }
613
+ .squiz-rb-scope .flex-wrap {
614
+ flex-wrap: wrap;
615
+ }
606
616
  .squiz-rb-scope .items-center {
607
617
  align-items: center;
608
618
  }
@@ -828,6 +838,44 @@
828
838
  height: 3rem;
829
839
  width: 3rem;
830
840
  }
841
+ .squiz-rb-scope .resource-breadcrumb--collapsed .resource-breadcrumb__label {
842
+ max-width: 250px;
843
+ cursor: pointer;
844
+ overflow: hidden;
845
+ text-overflow: ellipsis;
846
+ white-space: nowrap;
847
+ }
848
+ .squiz-rb-scope .resource-breadcrumb--collapsed .resource-breadcrumb__label:hover {
849
+ text-decoration-line: underline;
850
+ }
851
+ .squiz-rb-scope .resource-breadcrumb--collapsed .resource-breadcrumb__label:focus {
852
+ text-decoration-line: underline;
853
+ }
854
+ @media (min-width: 768px) {
855
+ .squiz-rb-scope .resource-breadcrumb--collapsed .resource-breadcrumb__label {
856
+ max-width: 90px;
857
+ }
858
+ }
859
+ .squiz-rb-scope .resource-breadcrumb--collapsed .resource-breadcrumb__item:nth-child(n+3) {
860
+ display: none;
861
+ }
862
+ .squiz-rb-scope .resource-breadcrumb--collapsed .resource-breadcrumb__item:nth-last-child(-n+2) {
863
+ display: flex;
864
+ }
865
+ @media not all and (min-width: 768px) {
866
+ .squiz-rb-scope .resource-breadcrumb--collapsed .resource-breadcrumb__item:nth-last-child(-n+2) {
867
+ display: none;
868
+ }
869
+ }
870
+ .squiz-rb-scope .resource-breadcrumb--collapsed .resource-breadcrumb__item:last-child {
871
+ display: flex;
872
+ }
873
+ .squiz-rb-scope .resource-breadcrumb--expanded .resource-breadcrumb__expander {
874
+ display: none;
875
+ }
876
+ .squiz-rb-scope .resource-breadcrumb--expanded .resource-breadcrumb__item:last-child {
877
+ display: flex;
878
+ }
831
879
  .squiz-rb-scope .before\:fixed::before {
832
880
  content: var(--tw-content);
833
881
  position: fixed;
@@ -945,6 +993,11 @@
945
993
  --tw-bg-opacity: 1;
946
994
  background-color: rgb(245 245 245 / var(--tw-bg-opacity));
947
995
  }
996
+ @media not all and (min-width: 768px) {
997
+ .squiz-rb-scope .max-md\:hidden {
998
+ display: none;
999
+ }
1000
+ }
948
1001
  @media not all and (min-width: 640px) {
949
1002
  .squiz-rb-scope .max-sm\:hidden {
950
1003
  display: none;
@@ -961,6 +1014,11 @@
961
1014
  overflow-y: scroll;
962
1015
  }
963
1016
  }
1017
+ @media (min-width: 768px) {
1018
+ .squiz-rb-scope .md\:font-semibold {
1019
+ font-weight: 600;
1020
+ }
1021
+ }
964
1022
  @media (min-width: 1024px) {
965
1023
  .squiz-rb-scope .lg\:h-\[calc\(100vh-3\.5rem\)\] {
966
1024
  height: calc(100vh - 3.5rem);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squiz/resource-browser",
3
- "version": "1.32.1-alpha.13",
3
+ "version": "1.32.1-alpha.14",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "scripts": {
@@ -70,5 +70,5 @@
70
70
  "volta": {
71
71
  "node": "18.15.0"
72
72
  },
73
- "gitHead": "ccd515fa2e004166b6341041f3ff1d9b8514b50c"
73
+ "gitHead": "e4c2143502807400276f09fe018456d49fd64923"
74
74
  }
@@ -0,0 +1,28 @@
1
+ .resource-breadcrumb--collapsed {
2
+ .resource-breadcrumb__label {
3
+ @apply max-w-[250px] md:max-w-[90px] truncate hover:underline focus:underline cursor-pointer;
4
+ }
5
+
6
+ .resource-breadcrumb__item:nth-child(n + 3) {
7
+ display: none;
8
+ }
9
+
10
+ .resource-breadcrumb__item:nth-last-child(-n + 2) {
11
+ display: flex;
12
+ @apply max-md:hidden;
13
+ }
14
+
15
+ .resource-breadcrumb__item:last-child {
16
+ display: flex;
17
+ }
18
+ }
19
+
20
+ .resource-breadcrumb--expanded {
21
+ .resource-breadcrumb__expander {
22
+ display: none;
23
+ }
24
+
25
+ .resource-breadcrumb__item:last-child {
26
+ display: flex;
27
+ }
28
+ }
@@ -35,14 +35,14 @@ describe('ResourceBreadcrumb', () => {
35
35
 
36
36
  await waitFor(() => {
37
37
  expect(screen.getByText('HandyHomes website')).toBeTruthy();
38
- expect(screen.getByText('HandyHomes website').tagName.toLocaleLowerCase()).toEqual('button');
38
+ expect(screen.getByRole('button', { name: 'HandyHomes website' })).toBeTruthy();
39
39
 
40
40
  expect(screen.getByText('Section 1')).toBeTruthy();
41
- expect(screen.getByText('Section 1').tagName.toLocaleLowerCase()).toEqual('button');
41
+ expect(screen.getByRole('button', { name: 'Section 1' })).toBeTruthy();
42
42
 
43
43
  // Last item isn't a button
44
44
  expect(screen.getByText('Pages')).toBeTruthy();
45
- expect(screen.getByText('Pages').tagName.toLocaleLowerCase()).toEqual('span');
45
+ expect(screen.getByText('Pages').tagName.toLocaleLowerCase()).toEqual('div');
46
46
  });
47
47
  });
48
48
 
@@ -73,4 +73,71 @@ describe('ResourceBreadcrumb', () => {
73
73
  expect(onReturnToRoot).toHaveBeenCalled();
74
74
  });
75
75
  });
76
+
77
+ it('Truncates long hierarchy correctly', async () => {
78
+ const { container } = render(
79
+ <ResourceBreadcrumb
80
+ hierarchy={[
81
+ ...hierarchy,
82
+ {
83
+ id: {
84
+ id: '4',
85
+ source: '1',
86
+ },
87
+ label: 'Item 4',
88
+ },
89
+ {
90
+ id: {
91
+ id: '5',
92
+ source: '1',
93
+ },
94
+ label: 'Item 5',
95
+ },
96
+ ]}
97
+ onBreadcrumbSelect={() => {}}
98
+ onReturnToRoot={() => {}}
99
+ />,
100
+ );
101
+
102
+ await waitFor(() => {
103
+ // This is handled by css, so check the collapse class is on the root
104
+ expect(container.querySelector('.resource-breadcrumb--collapsed')).toBeTruthy();
105
+
106
+ expect(screen.getByText('HandyHomes website')).toBeTruthy();
107
+ expect(screen.getByRole('button', { name: 'HandyHomes website' })).toBeTruthy();
108
+
109
+ // Expand button is rendered
110
+ expect(screen.getByRole('button', { name: 'Expand breadcrumb' })).toBeTruthy();
111
+
112
+ // Middle buttons are still rendered as they are hidden by CSS
113
+ expect(screen.queryByRole('button', { name: 'Section 1' })).toBeTruthy();
114
+ expect(screen.queryByRole('button', { name: 'Pages' })).toBeTruthy();
115
+
116
+ expect(screen.getByRole('button', { name: 'Item 4' })).toBeTruthy();
117
+ expect(screen.getByText('Item 5')).toBeTruthy();
118
+ });
119
+
120
+ // Click expander
121
+ const user = userEvent.setup();
122
+ user.click(screen.getByRole('button', { name: 'Expand breadcrumb' }));
123
+
124
+ // Expect css class to change
125
+ await waitFor(() => {
126
+ expect(container.querySelector('.resource-breadcrumb--expanded')).toBeTruthy();
127
+ });
128
+ });
129
+
130
+ // Check that the breadcrumb has a title attribute that is the full name of the item
131
+ it('Breadcrumb has title attribute', async () => {
132
+ render(<ResourceBreadcrumb hierarchy={hierarchy} onBreadcrumbSelect={() => {}} onReturnToRoot={() => {}} />);
133
+
134
+ await waitFor(() => {
135
+ expect(screen.getByRole('button', { name: 'HandyHomes website' }).firstChild).toHaveAttribute(
136
+ 'title',
137
+ 'HandyHomes website',
138
+ );
139
+ expect(screen.getByRole('button', { name: 'Section 1' }).firstChild).toHaveAttribute('title', 'Section 1');
140
+ expect(screen.getByText('Pages')).toHaveAttribute('title', 'Pages');
141
+ });
142
+ });
76
143
  });
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useState, useCallback, useEffect } from 'react';
2
2
 
3
3
  import Icon, { IconOptions } from '../Icons/Icon';
4
4
 
@@ -11,9 +11,27 @@ export interface ResourceBreadcrumbProps {
11
11
  }
12
12
 
13
13
  const ResourceBreadcrumb = function ({ hierarchy, onBreadcrumbSelect, onReturnToRoot }: ResourceBreadcrumbProps) {
14
+ // Track expanded state
15
+ const [expandedHierarchy, setExpandedHierarchy] = useState(false);
16
+
17
+ // On update (new page) reset the expanded state
18
+ useEffect(() => {
19
+ setExpandedHierarchy(false);
20
+ }, [hierarchy]);
21
+
22
+ // Handle toggling the state to expanded
23
+ const handleExpandHierarchy = useCallback(() => {
24
+ setExpandedHierarchy(true);
25
+ }, []);
26
+
14
27
  return (
15
- <nav aria-label="Resource breadcrumb" className="text-sm text-gray-600 px-7 my-5">
16
- <ol className="flex items-center">
28
+ <nav
29
+ aria-label="Resource breadcrumb"
30
+ className={`text-sm text-gray-600 px-7 my-5 resource-breadcrumb ${
31
+ expandedHierarchy ? 'resource-breadcrumb--expanded' : 'resource-breadcrumb--collapsed'
32
+ }`}
33
+ >
34
+ <ol className="flex flex-wrap items-center">
17
35
  <li className="flex items-center mr-3">
18
36
  <button type="button" onClick={onReturnToRoot}>
19
37
  <Icon icon={'root' as IconOptions} aria-label="Return to source list" className="" />
@@ -21,13 +39,36 @@ const ResourceBreadcrumb = function ({ hierarchy, onBreadcrumbSelect, onReturnTo
21
39
  </li>
22
40
  {hierarchy.map(({ id, label }, index) => {
23
41
  return (
24
- <li key={`${id.source}-${id.id}`} className="mr-2 before:content-['/'] before:mr-2">
42
+ <li
43
+ key={`${id.source}-${id.id}`}
44
+ className="resource-breadcrumb__item max-md:hidden flex items-center mr-2 before:content-['/'] before:mr-2"
45
+ >
25
46
  {index !== hierarchy.length - 1 && (
26
47
  <button type="button" onClick={() => onBreadcrumbSelect(id)}>
27
- {label}
48
+ <div className={`resource-breadcrumb__label`} title={label}>
49
+ {label}
50
+ </div>
28
51
  </button>
29
52
  )}
30
- {index === hierarchy.length - 1 && <span className="font-semibold">{label}</span>}
53
+ {index === hierarchy.length - 1 && (
54
+ <div className={`resource-breadcrumb__label md:font-semibold`} title={label}>
55
+ {label}
56
+ </div>
57
+ )}
58
+
59
+ {hierarchy.length > 3 && index === 0 && (
60
+ <div className="resource-breadcrumb__expander flex">
61
+ <div className="before:content-['/'] mx-2" />
62
+ <button
63
+ title="Expand breadcrumb"
64
+ aria-label="Expand breadcrumb"
65
+ onClick={handleExpandHierarchy}
66
+ className="flex items-center justify-center"
67
+ >
68
+ ...
69
+ </button>
70
+ </div>
71
+ )}
31
72
  </li>
32
73
  );
33
74
  })}
@@ -11,13 +11,27 @@
11
11
  "id": "2",
12
12
  "source": "1"
13
13
  },
14
- "label": "Section 1"
14
+ "label": "Section 1 very long name"
15
+ },
16
+ {
17
+ "id": {
18
+ "id": "4",
19
+ "source": "1"
20
+ },
21
+ "label": "Page 1 very long name"
22
+ },
23
+ {
24
+ "id": {
25
+ "id": "5",
26
+ "source": "1"
27
+ },
28
+ "label": "Page 71 very long name"
15
29
  },
16
30
  {
17
31
  "id": {
18
32
  "id": "3",
19
33
  "source": "1"
20
34
  },
21
- "label": "Pages"
35
+ "label": "Pages very long name"
22
36
  }
23
37
  ]
package/src/index.scss CHANGED
@@ -5,3 +5,4 @@
5
5
 
6
6
  // Components
7
7
  @import './Spinner/spinner';
8
+ @import './ResourceBreadcrumb/ResourceBreadcrumb';