@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.
- package/lib/ResourceBreadcrumb/ResourceBreadcrumb.js +43 -6
- package/lib/index.css +58 -0
- package/package.json +2 -2
- package/src/ResourceBreadcrumb/ResourceBreadcrumb.scss +28 -0
- package/src/ResourceBreadcrumb/ResourceBreadcrumb.spec.tsx +70 -3
- package/src/ResourceBreadcrumb/ResourceBreadcrumb.tsx +47 -6
- package/src/ResourceBreadcrumb/sample-hierarchy.json +16 -2
- package/src/index.scss +1 -0
@@ -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 =
|
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
|
-
|
10
|
-
|
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) },
|
17
|
-
|
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.
|
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": "
|
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.
|
38
|
+
expect(screen.getByRole('button', { name: 'HandyHomes website' })).toBeTruthy();
|
39
39
|
|
40
40
|
expect(screen.getByText('Section 1')).toBeTruthy();
|
41
|
-
expect(screen.
|
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('
|
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
|
16
|
-
|
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
|
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 &&
|
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