@stack-spot/portal-layout 0.0.62 → 0.0.64
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/dist/Layout.d.ts +3 -3
- package/dist/Layout.d.ts.map +1 -1
- package/dist/Layout.js +9 -6
- package/dist/Layout.js.map +1 -1
- package/dist/LayoutOverlayManager.d.ts +9 -0
- package/dist/LayoutOverlayManager.d.ts.map +1 -1
- package/dist/LayoutOverlayManager.js +21 -1
- package/dist/LayoutOverlayManager.js.map +1 -1
- package/dist/components/error/ErrorDescriptor.d.ts +12 -0
- package/dist/components/error/ErrorDescriptor.d.ts.map +1 -0
- package/dist/components/error/ErrorDescriptor.js +17 -0
- package/dist/components/error/ErrorDescriptor.js.map +1 -0
- package/dist/components/menu/MenuContent.d.ts +545 -2
- package/dist/components/menu/MenuContent.d.ts.map +1 -1
- package/dist/components/menu/useCheckTextOverflow.d.ts +6 -0
- package/dist/components/menu/useCheckTextOverflow.d.ts.map +1 -0
- package/dist/components/menu/useCheckTextOverflow.js +20 -0
- package/dist/components/menu/useCheckTextOverflow.js.map +1 -0
- package/dist/components/tour/PortalSwitcherStep.d.ts +3 -0
- package/dist/components/tour/PortalSwitcherStep.d.ts.map +1 -0
- package/dist/components/tour/PortalSwitcherStep.js +29 -0
- package/dist/components/tour/PortalSwitcherStep.js.map +1 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/utils.d.ts +27 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +50 -0
- package/dist/utils.js.map +1 -1
- package/package.json +2 -2
- package/src/Layout.tsx +11 -8
- package/src/LayoutOverlayManager.tsx +17 -1
- package/src/components/tour/PortalSwitcherStep.tsx +36 -0
- package/src/index.ts +0 -1
- package/src/utils.ts +48 -0
- package/src/components/tour/Navigation.tsx +0 -29
- package/src/components/tour/config.tsx +0 -23
- package/src/components/tour/steps/PortalSwitcherStep.tsx +0 -47
- package/src/components/tour/utils.tsx +0 -58
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Image, Text } from '@citric/core';
|
|
3
|
+
import { tourStepBuilder } from '@stack-spot/portal-components';
|
|
4
|
+
import { useTranslate } from '@stack-spot/portal-translate';
|
|
5
|
+
export const usePortalSwitcherStep = () => {
|
|
6
|
+
const t = useTranslate(translations);
|
|
7
|
+
return tourStepBuilder({
|
|
8
|
+
selector: '.portal-switcher',
|
|
9
|
+
title: t.title,
|
|
10
|
+
content: _jsxs(_Fragment, { children: [_jsx(Image, { src: "https://marketing.stackspot.com/switch-v2.gif", alt: t.imageAlt }), _jsx(Box, { px: 5, py: 3, children: _jsx(Text, { appearance: "microtext1", colorScheme: "inverse.contrastText", children: t.description }) })] }),
|
|
11
|
+
position: 'right',
|
|
12
|
+
style: {
|
|
13
|
+
width: '300px',
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
const translations = {
|
|
18
|
+
en: {
|
|
19
|
+
title: 'Expand Your Horizons with Stackspot',
|
|
20
|
+
description: 'Easily switch between EDP and AI to enhance your projects. Access a wide range of resources with just one click and take your projects to a new level of efficiency. Start your journey now!',
|
|
21
|
+
imageAlt: 'GIF describing how to use product switcher and navigate between AI and EDP portals',
|
|
22
|
+
},
|
|
23
|
+
pt: {
|
|
24
|
+
title: 'Expanda Seus Horizontes com Stackspot',
|
|
25
|
+
description: 'Troque facilmente entre EDP e AI para aprimorar seus projetos. Acesse uma ampla gama de recursos com apenas um clique e leve seus projetos para um novo nível de eficiência. Comece sua jornada agora!',
|
|
26
|
+
imageAlt: 'GIF mostrando como usar o alternador de produtos e navegar entre os portais AI e EDP',
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=PortalSwitcherStep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PortalSwitcherStep.js","sourceRoot":"","sources":["../../../src/components/tour/PortalSwitcherStep.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAY,eAAe,EAAE,MAAM,+BAA+B,CAAA;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAA;AAC3D,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAa,EAAE;IAClD,MAAM,CAAC,GAAG,YAAY,CAAC,YAAY,CAAC,CAAA;IACpC,OAAO,eAAe,CAAC;QACrB,QAAQ,EAAE,kBAAkB;QAC5B,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,OAAO,EAAE,8BACP,KAAC,KAAK,IAAC,GAAG,EAAC,+CAA+C,EAAC,GAAG,EAAE,CAAC,CAAC,QAAQ,GAAI,EAC9E,KAAC,GAAG,IAAC,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,YACf,KAAC,IAAI,IAAC,UAAU,EAAC,YAAY,EAAC,WAAW,EAAC,sBAAsB,YAAE,CAAC,CAAC,WAAW,GAAQ,GACnF,IACL;QACH,QAAQ,EAAE,OAAO;QACjB,KAAK,EAAE;YACL,KAAK,EAAE,OAAO;SACf;KACF,CAAC,CAAA;AACJ,CAAC,CAAA;AAKD,MAAM,YAAY,GAAG;IACnB,EAAE,EAAE;QACF,KAAK,EAAE,qCAAqC;QAC5C,WAAW,EAAE,8LAA8L;QAC3M,QAAQ,EAAE,oFAAoF;KAC/F;IACD,EAAE,EAAE;QACF,KAAK,EAAE,uCAAuC;QAC9C,WAAW,EAAE,wMAAwM;QACrN,QAAQ,EAAE,sFAAsF;KACjG;CACF,CAAA"}
|
package/dist/index.d.ts
CHANGED
|
@@ -9,7 +9,6 @@ export { Toaster } from './components/Toaster.js';
|
|
|
9
9
|
export { ActionItem, MenuContent, MenuGroup, Title } from './components/menu/MenuContent.js';
|
|
10
10
|
export { MenuSections } from './components/menu/MenuSections.js';
|
|
11
11
|
export * from './components/menu/types.js';
|
|
12
|
-
export { TextWithPointingArrow } from './components/tour/utils.js';
|
|
13
12
|
export * from './components/types.js';
|
|
14
13
|
export * from './elements.js';
|
|
15
14
|
export * from './errors.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAA;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAA;AAC1F,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,+BAA+B,CAAA;AACzF,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AAC7D,cAAc,yBAAyB,CAAA;AACvC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAA;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAA;AAC1F,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,+BAA+B,CAAA;AACzF,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AAC7D,cAAc,yBAAyB,CAAA;AACvC,cAAc,oBAAoB,CAAA;AAClC,cAAc,YAAY,CAAA;AAC1B,cAAc,UAAU,CAAA;AACxB,OAAO,EAAE,IAAI,IAAI,aAAa,EAAE,MAAM,YAAY,CAAA;AAClD,cAAc,SAAS,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,6 @@ export { Toaster } from './components/Toaster.js';
|
|
|
9
9
|
export { ActionItem, MenuContent, MenuGroup, Title } from './components/menu/MenuContent.js';
|
|
10
10
|
export { MenuSections } from './components/menu/MenuSections.js';
|
|
11
11
|
export * from './components/menu/types.js';
|
|
12
|
-
export { TextWithPointingArrow } from './components/tour/utils.js';
|
|
13
12
|
export * from './components/types.js';
|
|
14
13
|
export * from './elements.js';
|
|
15
14
|
export * from './errors.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAA;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAA;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAc,aAAa,EAAsB,MAAM,4BAA4B,CAAA;AAC1F,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,+BAA+B,CAAA;AACzF,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AAC7D,cAAc,yBAAyB,CAAA;AACvC,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAA;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAA;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAc,aAAa,EAAsB,MAAM,4BAA4B,CAAA;AAC1F,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,+BAA+B,CAAA;AACzF,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AAC7D,cAAc,yBAAyB,CAAA;AACvC,cAAc,oBAAoB,CAAA;AAClC,cAAc,YAAY,CAAA;AAC1B,cAAc,UAAU,CAAA;AACxB,OAAO,EAAE,IAAI,IAAI,aAAa,EAAE,MAAM,YAAY,CAAA;AAClD,cAAc,SAAS,CAAA"}
|
package/dist/utils.d.ts
CHANGED
|
@@ -22,6 +22,33 @@ interface FocusOptions {
|
|
|
22
22
|
*/
|
|
23
23
|
ignore?: string;
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Checks if an element can receive focus.
|
|
27
|
+
*
|
|
28
|
+
* Elements can receive focus only if:
|
|
29
|
+
* - they exist;
|
|
30
|
+
* - they're visible;
|
|
31
|
+
* - they're not disabled;
|
|
32
|
+
* - they are a focusable tag name or have a positive tab index;
|
|
33
|
+
* - they don't have a negative tab index.
|
|
34
|
+
* @param element the element to check.
|
|
35
|
+
* @returns true if the element is focusable, false otherwise.
|
|
36
|
+
*/
|
|
37
|
+
export declare function isFocusable(element?: Element | null): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Focus the element passed as parameter if it can receive focus. Otherwise, decides on another element to focus based on the HTML tree
|
|
40
|
+
* and accessibility attributes.
|
|
41
|
+
*
|
|
42
|
+
* Decision making:
|
|
43
|
+
* - If the element is focusable, focus it.
|
|
44
|
+
* - If the element is not focusable, check if it has an id and if another element controls it (aria-controls).
|
|
45
|
+
* - If the element is controlled by another, try to focus the controller, based in this same algorithm.
|
|
46
|
+
* - Otherwise, try to focus its parent.
|
|
47
|
+
*
|
|
48
|
+
* If no focusable element is found. Nothing happens.
|
|
49
|
+
* @param element the element to focus.
|
|
50
|
+
*/
|
|
51
|
+
export declare function focusAccessibleElement(element?: Element | null): void;
|
|
25
52
|
/**
|
|
26
53
|
* Focus the first focusable child of the element provided. If the element has no focusable child, nothing happens.
|
|
27
54
|
*
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAGA,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAIxD;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,IAAI,QAWrE;AAED,KAAK,WAAW,GAAG,GAAG,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,CAAA;AAC7E,KAAK,kBAAkB,GAAG,WAAW,GAAG,WAAW,EAAE,CAAA;AAErD,UAAU,YAAY;IACpB;;OAEG;IACH,QAAQ,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAChC;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAWD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,WAAW,GAAG,QAAQ,GAAG,IAAI,GAAG,SAAS,EAAE,EAAE,QAAa,EAAE,MAAM,EAAE,GAAE,YAAiB,QA0B/H;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,QAKpC"}
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAGA,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAIxD;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,IAAI,QAWrE;AAED,KAAK,WAAW,GAAG,GAAG,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,CAAA;AAC7E,KAAK,kBAAkB,GAAG,WAAW,GAAG,WAAW,EAAE,CAAA;AAErD,UAAU,YAAY;IACpB;;OAEG;IACH,QAAQ,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAChC;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAWD;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,WAYnD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,QAO9D;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,WAAW,GAAG,QAAQ,GAAG,IAAI,GAAG,SAAS,EAAE,EAAE,QAAa,EAAE,MAAM,EAAE,GAAE,YAAiB,QA0B/H;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,QAKpC"}
|
package/dist/utils.js
CHANGED
|
@@ -38,6 +38,56 @@ const selectors = {
|
|
|
38
38
|
textarea: 'select:not(:disabled)',
|
|
39
39
|
other: '[tabindex]:not([tabindex="-1"])',
|
|
40
40
|
};
|
|
41
|
+
/**
|
|
42
|
+
* Checks if an element can receive focus.
|
|
43
|
+
*
|
|
44
|
+
* Elements can receive focus only if:
|
|
45
|
+
* - they exist;
|
|
46
|
+
* - they're visible;
|
|
47
|
+
* - they're not disabled;
|
|
48
|
+
* - they are a focusable tag name or have a positive tab index;
|
|
49
|
+
* - they don't have a negative tab index.
|
|
50
|
+
* @param element the element to check.
|
|
51
|
+
* @returns true if the element is focusable, false otherwise.
|
|
52
|
+
*/
|
|
53
|
+
export function isFocusable(element) {
|
|
54
|
+
if (!element)
|
|
55
|
+
return false;
|
|
56
|
+
// is disabled: return false
|
|
57
|
+
if (element.ariaDisabled || element.getAttribute('disabled') !== null)
|
|
58
|
+
return false;
|
|
59
|
+
// is invisible: return false
|
|
60
|
+
if (!element.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true }))
|
|
61
|
+
return false;
|
|
62
|
+
// has tab index: return false if negative, true otherwise
|
|
63
|
+
const tabIndexStr = element.getAttribute('tabindex');
|
|
64
|
+
const tabIndex = tabIndexStr ? parseInt(tabIndexStr) : undefined;
|
|
65
|
+
if (tabIndex !== undefined)
|
|
66
|
+
return tabIndex >= 0;
|
|
67
|
+
// check the tag name
|
|
68
|
+
return ['a', 'button', 'input', 'iframe', 'select', 'textarea'].includes(element.tagName.toLowerCase() ?? '');
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Focus the element passed as parameter if it can receive focus. Otherwise, decides on another element to focus based on the HTML tree
|
|
72
|
+
* and accessibility attributes.
|
|
73
|
+
*
|
|
74
|
+
* Decision making:
|
|
75
|
+
* - If the element is focusable, focus it.
|
|
76
|
+
* - If the element is not focusable, check if it has an id and if another element controls it (aria-controls).
|
|
77
|
+
* - If the element is controlled by another, try to focus the controller, based in this same algorithm.
|
|
78
|
+
* - Otherwise, try to focus its parent.
|
|
79
|
+
*
|
|
80
|
+
* If no focusable element is found. Nothing happens.
|
|
81
|
+
* @param element the element to focus.
|
|
82
|
+
*/
|
|
83
|
+
export function focusAccessibleElement(element) {
|
|
84
|
+
let elementToFocus = element;
|
|
85
|
+
while (elementToFocus && !isFocusable(elementToFocus)) {
|
|
86
|
+
const controlledBy = elementToFocus.id ? document.querySelector(`[aria-controls=${elementToFocus.id}]`) : undefined;
|
|
87
|
+
elementToFocus = (controlledBy ?? elementToFocus.parentElement);
|
|
88
|
+
}
|
|
89
|
+
elementToFocus?.focus?.();
|
|
90
|
+
}
|
|
41
91
|
/**
|
|
42
92
|
* Focus the first focusable child of the element provided. If the element has no focusable child, nothing happens.
|
|
43
93
|
*
|
package/dist/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stack-spot/portal-layout",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.64",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
"@citric/core": ">=6.0.0",
|
|
9
9
|
"@citric/icons": ">=5.7.1",
|
|
10
10
|
"@citric/ui": ">=5.7.2",
|
|
11
|
-
"@reactour/tour": ">=3.6.2",
|
|
12
11
|
"@stack-spot/portal-theme": ">=0.0.11",
|
|
13
12
|
"@stack-spot/portal-translate": ">=0.0.6",
|
|
13
|
+
"@stack-spot/portal-components": ">=0.0.17",
|
|
14
14
|
"react": ">=18.2.0",
|
|
15
15
|
"react-dom": ">=18.2.0",
|
|
16
16
|
"styled-components": ">=6.1.1"
|
package/src/Layout.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TourProps, TourProvider, defaultTourConfig, isNewTourStep } from '@stack-spot/portal-components'
|
|
2
2
|
import { CSSToCitricAdapter, WithStyle, listToClass } from '@stack-spot/portal-theme'
|
|
3
3
|
import '@stack-spot/portal-theme/dist/theme.css'
|
|
4
4
|
import { ReactElement, ReactNode, useEffect } from 'react'
|
|
@@ -11,8 +11,7 @@ import { SilentErrorBoundary } from './components/error/SilentErrorBoundary'
|
|
|
11
11
|
import { MenuContent } from './components/menu/MenuContent'
|
|
12
12
|
import { MenuSections } from './components/menu/MenuSections'
|
|
13
13
|
import { MenuProps } from './components/menu/types'
|
|
14
|
-
import {
|
|
15
|
-
import { portalSwitcherStep } from './components/tour/steps/PortalSwitcherStep'
|
|
14
|
+
import { usePortalSwitcherStep } from './components/tour/PortalSwitcherStep'
|
|
16
15
|
import { elementIds, getLayoutElements } from './elements'
|
|
17
16
|
import { LayoutContext, LayoutProvider } from './layout-context'
|
|
18
17
|
import './layout.css'
|
|
@@ -24,7 +23,7 @@ interface Props extends WithStyle, LayoutContext {
|
|
|
24
23
|
extra?: ReactNode,
|
|
25
24
|
errorDescriptor?: DescriptionFn,
|
|
26
25
|
onError?: ErrorHandler,
|
|
27
|
-
tourProps?:
|
|
26
|
+
tourProps?: TourProps,
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
interface RawProps extends WithStyle, LayoutContext {
|
|
@@ -35,7 +34,7 @@ interface RawProps extends WithStyle, LayoutContext {
|
|
|
35
34
|
extra?: ReactNode,
|
|
36
35
|
errorDescriptor?: DescriptionFn,
|
|
37
36
|
onError?: ErrorHandler,
|
|
38
|
-
tourProps?:
|
|
37
|
+
tourProps?: TourProps,
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
export const RawLayout = (
|
|
@@ -47,6 +46,7 @@ export const RawLayout = (
|
|
|
47
46
|
const { bottomDialog, modal, rightPanel } = overlay.useOverlays()
|
|
48
47
|
const { layout } = getLayoutElements()
|
|
49
48
|
const isCompactedOnlyIcons = layout?.classList.contains('menu-compact')
|
|
49
|
+
const portalSwitcherStep = usePortalSwitcherStep()
|
|
50
50
|
|
|
51
51
|
const classes = [
|
|
52
52
|
menuContent && !isCompactedOnlyIcons ? 'menu-content-visible' : undefined,
|
|
@@ -55,8 +55,11 @@ export const RawLayout = (
|
|
|
55
55
|
isCompactedOnlyIcons ? 'menu-compact' : undefined,
|
|
56
56
|
]
|
|
57
57
|
|
|
58
|
-
const tourPropsWithDefaults = { ...defaultTourConfig
|
|
59
|
-
tourPropsWithDefaults.steps
|
|
58
|
+
const tourPropsWithDefaults = { ...defaultTourConfig, ...(tourProps || {}) }
|
|
59
|
+
tourPropsWithDefaults.steps = [
|
|
60
|
+
portalSwitcherStep,
|
|
61
|
+
...tourPropsWithDefaults.steps,
|
|
62
|
+
].filter(isNewTourStep)
|
|
60
63
|
|
|
61
64
|
useEffect(() => {
|
|
62
65
|
if (errorDescriptor) ErrorManager.setDescriptionFunction(errorDescriptor)
|
|
@@ -66,7 +69,7 @@ export const RawLayout = (
|
|
|
66
69
|
return (
|
|
67
70
|
<CSSToCitricAdapter>
|
|
68
71
|
<LayoutProvider anchorTag={anchorTag}>
|
|
69
|
-
<TourProvider {
|
|
72
|
+
<TourProvider config={tourPropsWithDefaults} >
|
|
70
73
|
<div id={elementIds.layout} className={listToClass(classes)} style={style}>
|
|
71
74
|
{children && <div id={elementIds.page}>
|
|
72
75
|
<article id={elementIds.content}><ErrorBoundary>{children}</ErrorBoundary></article>
|
|
@@ -8,7 +8,7 @@ import { getDictionary } from './dictionary'
|
|
|
8
8
|
import { LayoutElements, elementIds, getLayoutElements } from './elements'
|
|
9
9
|
import { ElementNotFound, LayoutError } from './errors'
|
|
10
10
|
import { showToaster as showReactToaster } from './toaster'
|
|
11
|
-
import { focusFirstChild, valueOfLayoutVar } from './utils'
|
|
11
|
+
import { focusAccessibleElement, focusFirstChild, valueOfLayoutVar } from './utils'
|
|
12
12
|
|
|
13
13
|
interface AlertOptions extends Omit<DialogOptions, 'cancel'> {
|
|
14
14
|
showButton?: boolean,
|
|
@@ -49,6 +49,10 @@ class LayoutOverlayManager {
|
|
|
49
49
|
private setContent: OverlayContentSetter = {}
|
|
50
50
|
private elements?: LayoutElements
|
|
51
51
|
private onModalClose?: () => void
|
|
52
|
+
/**
|
|
53
|
+
* Last element with focus before an overlay is shown.
|
|
54
|
+
*/
|
|
55
|
+
private lastActiveElement: Element | null = null
|
|
52
56
|
|
|
53
57
|
private setupElements() {
|
|
54
58
|
this.elements = getLayoutElements()
|
|
@@ -102,6 +106,7 @@ class LayoutOverlayManager {
|
|
|
102
106
|
}
|
|
103
107
|
|
|
104
108
|
private showOverlay(element: HTMLElement | null | undefined, extraClasses: string[] = [], blockMainContent = true) {
|
|
109
|
+
this.lastActiveElement = document.activeElement
|
|
105
110
|
element?.classList.add('visible', ...extraClasses)
|
|
106
111
|
this.setInteractivity(element, true)
|
|
107
112
|
if (blockMainContent) this.setMainContentInteractivity(false)
|
|
@@ -207,6 +212,15 @@ class LayoutOverlayManager {
|
|
|
207
212
|
)
|
|
208
213
|
}
|
|
209
214
|
|
|
215
|
+
/**
|
|
216
|
+
* Focus the element that had focus before the last overlay was opened. If the element is not visible anymore, another one that makes
|
|
217
|
+
* sense (accessibility-wise) is focused.
|
|
218
|
+
*/
|
|
219
|
+
private focusLastActiveElement() {
|
|
220
|
+
focusAccessibleElement(this.lastActiveElement)
|
|
221
|
+
this.lastActiveElement = null
|
|
222
|
+
}
|
|
223
|
+
|
|
210
224
|
closeModal(runCloseListener = true) {
|
|
211
225
|
this.elements?.modal?.classList.remove('visible')
|
|
212
226
|
this.elements?.backdrop?.setAttribute('class', '')
|
|
@@ -226,6 +240,7 @@ class LayoutOverlayManager {
|
|
|
226
240
|
}
|
|
227
241
|
if (this.setContent.modal) this.setContent.modal(undefined)
|
|
228
242
|
this.hideOverlay(this.elements?.modal)
|
|
243
|
+
this.focusLastActiveElement()
|
|
229
244
|
},
|
|
230
245
|
animationMS,
|
|
231
246
|
)
|
|
@@ -250,6 +265,7 @@ class LayoutOverlayManager {
|
|
|
250
265
|
}
|
|
251
266
|
if (this.setContent.rightPanel) this.setContent.rightPanel(undefined)
|
|
252
267
|
this.hideOverlay(this.elements?.rightPanel)
|
|
268
|
+
this.focusLastActiveElement()
|
|
253
269
|
},
|
|
254
270
|
animationMS,
|
|
255
271
|
)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Box, Image, Text } from '@citric/core'
|
|
2
|
+
import { TourStep, tourStepBuilder } from '@stack-spot/portal-components'
|
|
3
|
+
import { useTranslate } from '@stack-spot/portal-translate'
|
|
4
|
+
export const usePortalSwitcherStep = (): TourStep => {
|
|
5
|
+
const t = useTranslate(translations)
|
|
6
|
+
return tourStepBuilder({
|
|
7
|
+
selector: '.portal-switcher',
|
|
8
|
+
title: t.title,
|
|
9
|
+
content: <>
|
|
10
|
+
<Image src="https://marketing.stackspot.com/switch-v2.gif" alt={t.imageAlt} />
|
|
11
|
+
<Box px={5} py={3}>
|
|
12
|
+
<Text appearance="microtext1" colorScheme="inverse.contrastText">{t.description}</Text>
|
|
13
|
+
</Box>
|
|
14
|
+
</>,
|
|
15
|
+
position: 'right',
|
|
16
|
+
style: {
|
|
17
|
+
width: '300px',
|
|
18
|
+
},
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
const translations = {
|
|
26
|
+
en: {
|
|
27
|
+
title: 'Expand Your Horizons with Stackspot',
|
|
28
|
+
description: 'Easily switch between EDP and AI to enhance your projects. Access a wide range of resources with just one click and take your projects to a new level of efficiency. Start your journey now!',
|
|
29
|
+
imageAlt: 'GIF describing how to use product switcher and navigate between AI and EDP portals',
|
|
30
|
+
},
|
|
31
|
+
pt: {
|
|
32
|
+
title: 'Expanda Seus Horizontes com Stackspot',
|
|
33
|
+
description: 'Troque facilmente entre EDP e AI para aprimorar seus projetos. Acesse uma ampla gama de recursos com apenas um clique e leve seus projetos para um novo nível de eficiência. Comece sua jornada agora!',
|
|
34
|
+
imageAlt: 'GIF mostrando como usar o alternador de produtos e navegar entre os portais AI e EDP',
|
|
35
|
+
},
|
|
36
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -9,7 +9,6 @@ export { Toaster } from './components/Toaster'
|
|
|
9
9
|
export { ActionItem, MenuContent, MenuGroup, Title } from './components/menu/MenuContent'
|
|
10
10
|
export { MenuSections } from './components/menu/MenuSections'
|
|
11
11
|
export * from './components/menu/types'
|
|
12
|
-
export { TextWithPointingArrow } from './components/tour/utils'
|
|
13
12
|
export * from './components/types'
|
|
14
13
|
export * from './elements'
|
|
15
14
|
export * from './errors'
|
package/src/utils.ts
CHANGED
|
@@ -53,6 +53,54 @@ const selectors: Record<TagPriority, string> = {
|
|
|
53
53
|
other: '[tabindex]:not([tabindex="-1"])',
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Checks if an element can receive focus.
|
|
58
|
+
*
|
|
59
|
+
* Elements can receive focus only if:
|
|
60
|
+
* - they exist;
|
|
61
|
+
* - they're visible;
|
|
62
|
+
* - they're not disabled;
|
|
63
|
+
* - they are a focusable tag name or have a positive tab index;
|
|
64
|
+
* - they don't have a negative tab index.
|
|
65
|
+
* @param element the element to check.
|
|
66
|
+
* @returns true if the element is focusable, false otherwise.
|
|
67
|
+
*/
|
|
68
|
+
export function isFocusable(element?: Element | null) {
|
|
69
|
+
if (!element) return false
|
|
70
|
+
// is disabled: return false
|
|
71
|
+
if (element.ariaDisabled || element.getAttribute('disabled') !== null) return false
|
|
72
|
+
// is invisible: return false
|
|
73
|
+
if (!element.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true })) return false
|
|
74
|
+
// has tab index: return false if negative, true otherwise
|
|
75
|
+
const tabIndexStr = element.getAttribute('tabindex')
|
|
76
|
+
const tabIndex = tabIndexStr ? parseInt(tabIndexStr) : undefined
|
|
77
|
+
if (tabIndex !== undefined) return tabIndex >= 0
|
|
78
|
+
// check the tag name
|
|
79
|
+
return ['a', 'button', 'input', 'iframe', 'select', 'textarea'].includes(element.tagName.toLowerCase() ?? '')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Focus the element passed as parameter if it can receive focus. Otherwise, decides on another element to focus based on the HTML tree
|
|
84
|
+
* and accessibility attributes.
|
|
85
|
+
*
|
|
86
|
+
* Decision making:
|
|
87
|
+
* - If the element is focusable, focus it.
|
|
88
|
+
* - If the element is not focusable, check if it has an id and if another element controls it (aria-controls).
|
|
89
|
+
* - If the element is controlled by another, try to focus the controller, based in this same algorithm.
|
|
90
|
+
* - Otherwise, try to focus its parent.
|
|
91
|
+
*
|
|
92
|
+
* If no focusable element is found. Nothing happens.
|
|
93
|
+
* @param element the element to focus.
|
|
94
|
+
*/
|
|
95
|
+
export function focusAccessibleElement(element?: Element | null) {
|
|
96
|
+
let elementToFocus = element as HTMLElement | null | undefined
|
|
97
|
+
while (elementToFocus && !isFocusable(elementToFocus)) {
|
|
98
|
+
const controlledBy = elementToFocus.id ? document.querySelector(`[aria-controls=${elementToFocus.id}]`) : undefined
|
|
99
|
+
elementToFocus = (controlledBy ?? elementToFocus.parentElement) as HTMLElement | null | undefined
|
|
100
|
+
}
|
|
101
|
+
elementToFocus?.focus?.()
|
|
102
|
+
}
|
|
103
|
+
|
|
56
104
|
/**
|
|
57
105
|
* Focus the first focusable child of the element provided. If the element has no focusable child, nothing happens.
|
|
58
106
|
*
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { Button, Flex, Text } from '@citric/core'
|
|
2
|
-
import { ProviderProps } from '@reactour/tour'
|
|
3
|
-
import '@stack-spot/portal-theme/dist/theme.css'
|
|
4
|
-
import { useTranslate } from '@stack-spot/portal-translate'
|
|
5
|
-
import React from 'react'
|
|
6
|
-
import { finishTour } from './utils'
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
type ComponentsDict = Exclude<ProviderProps['components'], undefined>
|
|
10
|
-
type NavigationProps = React.ComponentProps<Exclude<ComponentsDict['Navigation'], undefined>>
|
|
11
|
-
|
|
12
|
-
export const CustomNavigation = ({ currentStep, steps, setCurrentStep }: NavigationProps) => {
|
|
13
|
-
const t = useTranslate({ en: { of: 'of', back: 'back' }, pt: { of: 'de', back: 'voltar' } })
|
|
14
|
-
return <Flex w={12} px={5} py={2} mt="-1px" bg="inverse.500" justifyContent="space-between">
|
|
15
|
-
<Text appearance="microtext1" colorScheme="inverse.contrastText">{currentStep + 1} {t.of} {steps.length}</Text>
|
|
16
|
-
<Flex sx={{ gap: '8px' }}>
|
|
17
|
-
{currentStep > 1 &&
|
|
18
|
-
<Button sx={{ paddingInline: '20px' }} onClick={() => setCurrentStep(currentStep - 1)} size="sm" colorScheme="light">
|
|
19
|
-
{t.back}
|
|
20
|
-
</Button>}
|
|
21
|
-
<Button sx={{ paddingInline: '20px' }} onClick={() => {
|
|
22
|
-
if (currentStep + 1 == steps.length) finishTour()
|
|
23
|
-
setCurrentStep(currentStep + 1)
|
|
24
|
-
}} size="sm" colorScheme="light">
|
|
25
|
-
ok
|
|
26
|
-
</Button>
|
|
27
|
-
</Flex>
|
|
28
|
-
</Flex>
|
|
29
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { ProviderProps } from '@reactour/tour'
|
|
2
|
-
import { CustomNavigation } from './Navigation'
|
|
3
|
-
import { hasFinishedTour } from './utils'
|
|
4
|
-
|
|
5
|
-
export const defaultTourConfig: () => Omit<ProviderProps, 'children'> = () => ({
|
|
6
|
-
steps: [],
|
|
7
|
-
defaultOpen: !hasFinishedTour(),
|
|
8
|
-
showBadge: false,
|
|
9
|
-
showDots: false,
|
|
10
|
-
showPrevNextButtons: false,
|
|
11
|
-
showCloseButton: false,
|
|
12
|
-
styles: {
|
|
13
|
-
popover: (props) => ({
|
|
14
|
-
...props,
|
|
15
|
-
boxShadow: 'unset',
|
|
16
|
-
backgroundColor: 'transparent',
|
|
17
|
-
padding: '0px 14px', // TODO: find a way to make paddings on y axis so it's possible to use the the pointing arrow.
|
|
18
|
-
}),
|
|
19
|
-
},
|
|
20
|
-
components: {
|
|
21
|
-
Navigation: CustomNavigation,
|
|
22
|
-
},
|
|
23
|
-
})
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { Box, Button, Flex, IconBox, Image, Text } from '@citric/core'
|
|
2
|
-
import { TimesMini } from '@citric/icons'
|
|
3
|
-
import { StepType, useTour } from '@reactour/tour'
|
|
4
|
-
import { useTranslate } from '@stack-spot/portal-translate'
|
|
5
|
-
import { TextWithPointingArrow, finishTour } from '../utils'
|
|
6
|
-
|
|
7
|
-
const PortalSwitcherStep = () => {
|
|
8
|
-
const t = useTranslate(translations)
|
|
9
|
-
const { setIsOpen } = useTour()
|
|
10
|
-
return <Box bg="inverse.500">
|
|
11
|
-
<TextWithPointingArrow $position="right">
|
|
12
|
-
<Flex w={12} pl={5} py={3} flexWrap="nowrap" justifyContent="space-between" alignItems="center">
|
|
13
|
-
<Text appearance="body2" colorScheme="inverse.contrastText" weight="medium"> {t.title} </Text>
|
|
14
|
-
<Button appearance="text" size="sm" onClick={() => {
|
|
15
|
-
finishTour()
|
|
16
|
-
setIsOpen(false)
|
|
17
|
-
}}>
|
|
18
|
-
<IconBox size="xs" colorIcon="inverse.contrastText">
|
|
19
|
-
<TimesMini />
|
|
20
|
-
</IconBox>
|
|
21
|
-
</Button>
|
|
22
|
-
</Flex>
|
|
23
|
-
</TextWithPointingArrow>
|
|
24
|
-
<Image src="https://marketing.stackspot.com/switch-v2.gif"
|
|
25
|
-
alt="GIF showing how to move from one portal to another" />
|
|
26
|
-
<Box px={5} py={3}>
|
|
27
|
-
<Text appearance="microtext1" colorScheme="inverse.contrastText">{t.description}</Text>
|
|
28
|
-
</Box>
|
|
29
|
-
</Box >
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export const portalSwitcherStep: StepType = {
|
|
33
|
-
selector: '.portal-switcher',
|
|
34
|
-
content: PortalSwitcherStep,
|
|
35
|
-
position: 'right',
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const translations = {
|
|
39
|
-
en: {
|
|
40
|
-
title: 'Expand Your Horizons with Stackspot',
|
|
41
|
-
description: 'Easily switch between EDP and AI to enhance your projects. Access a wide range of resources with just one click and take your projects to a new level of efficiency. Start your journey now!',
|
|
42
|
-
},
|
|
43
|
-
pt: {
|
|
44
|
-
title: 'Expanda Seus Horizontes com Stackspot',
|
|
45
|
-
description: 'Troque facilmente entre EDP e AI para aprimorar seus projetos. Acesse uma ampla gama de recursos com apenas um clique e leve seus projetos para um novo nível de eficiência. Comece sua jornada agora!',
|
|
46
|
-
},
|
|
47
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { theme } from '@stack-spot/portal-theme'
|
|
2
|
-
import styled from 'styled-components'
|
|
3
|
-
|
|
4
|
-
export const TextWithPointingArrow = styled.div < {
|
|
5
|
-
$position: 'right' | 'top' | 'left',
|
|
6
|
-
$top?: string,
|
|
7
|
-
$right?: string,
|
|
8
|
-
} > `
|
|
9
|
-
position: relative;
|
|
10
|
-
width: 100%;
|
|
11
|
-
&::after {
|
|
12
|
-
content: '';
|
|
13
|
-
position: absolute;
|
|
14
|
-
border-width: 10px;
|
|
15
|
-
border-style: solid;
|
|
16
|
-
border-color: transparent;
|
|
17
|
-
margin-top: -5px;
|
|
18
|
-
border-right-color: ${theme.color.inverse[500]};
|
|
19
|
-
${({ $position, $top }) => $position === 'right' ?
|
|
20
|
-
`
|
|
21
|
-
top: ${$top || '16px'};
|
|
22
|
-
left: -18px;
|
|
23
|
-
` : ''}
|
|
24
|
-
${({ $position, $right }) => $position === 'top' ?
|
|
25
|
-
`
|
|
26
|
-
bottom: 96%;
|
|
27
|
-
right: ${$right || '16px'};
|
|
28
|
-
transform: rotate(90deg);
|
|
29
|
-
` : ''}
|
|
30
|
-
${({ $position, $top }) => $position === 'left' ?
|
|
31
|
-
`
|
|
32
|
-
top: ${$top || '16px'};
|
|
33
|
-
right: -18px;
|
|
34
|
-
transform: rotate(180deg);
|
|
35
|
-
` : ''}
|
|
36
|
-
}
|
|
37
|
-
`
|
|
38
|
-
|
|
39
|
-
const DOMAIN_REGEX = new RegExp(/(\.*(prd|stg|dev)*.stackspot.com)|localhost/)
|
|
40
|
-
const portalUrl = new URL(location.href)
|
|
41
|
-
const cookieDomain = DOMAIN_REGEX.exec(portalUrl.host)?.[0]
|
|
42
|
-
const defaultCookieAttributes = `domain=${cookieDomain}; SameSite=Strict;`
|
|
43
|
-
|
|
44
|
-
const getCookies = (): Record<string, string> => document.cookie.split('; ').reduce((prev, current) => {
|
|
45
|
-
const [name, ...value] = current.split('=')
|
|
46
|
-
prev[name] = value.join('=')
|
|
47
|
-
return prev
|
|
48
|
-
}, {} as Record<string, string>)
|
|
49
|
-
|
|
50
|
-
const setCookie = (key: string, value: string) => {
|
|
51
|
-
document.cookie = `${key}=${value}; ${defaultCookieAttributes}`
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const getCookie = (key: string) => getCookies()[key]
|
|
55
|
-
|
|
56
|
-
export const finishTour = () => setCookie('welcomeTour', 'done')
|
|
57
|
-
|
|
58
|
-
export const hasFinishedTour = () => !!getCookie('welcomeTour')
|