@stack-spot/portal-components 0.0.17 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/BannerWarning.d.ts +6 -0
- package/dist/components/BannerWarning.d.ts.map +1 -1
- package/dist/components/BannerWarning.js +6 -0
- package/dist/components/BannerWarning.js.map +1 -1
- package/dist/components/Breadcrumb/index.d.ts +11 -2
- package/dist/components/Breadcrumb/index.d.ts.map +1 -1
- package/dist/components/Breadcrumb/index.js +7 -5
- package/dist/components/Breadcrumb/index.js.map +1 -1
- package/dist/components/ChatBot.d.ts +7 -0
- package/dist/components/ChatBot.d.ts.map +1 -1
- package/dist/components/ChatBot.js +13 -6
- package/dist/components/ChatBot.js.map +1 -1
- package/dist/components/ErrorFeedback.d.ts +23 -0
- package/dist/components/ErrorFeedback.d.ts.map +1 -0
- package/dist/components/ErrorFeedback.js +73 -0
- package/dist/components/ErrorFeedback.js.map +1 -0
- package/dist/components/SelectionList.d.ts +123 -0
- package/dist/components/SelectionList.d.ts.map +1 -0
- package/dist/components/SelectionList.js +149 -0
- package/dist/components/SelectionList.js.map +1 -0
- package/dist/components/{src/components/tour → Tour}/StepContainer.d.ts +16 -0
- package/dist/components/Tour/StepContainer.d.ts.map +1 -0
- package/dist/components/{tour → Tour}/StepContainer.js +4 -0
- package/dist/components/Tour/StepContainer.js.map +1 -0
- package/dist/components/Tour/StepNavigation.d.ts +29 -0
- package/dist/components/Tour/StepNavigation.d.ts.map +1 -0
- package/dist/components/{tour → Tour}/StepNavigation.js +4 -0
- package/dist/components/Tour/StepNavigation.js.map +1 -0
- package/dist/components/Tour/StepTitle.d.ts +17 -0
- package/dist/components/Tour/StepTitle.d.ts.map +1 -0
- package/dist/components/{tour → Tour}/StepTitle.js +4 -0
- package/dist/components/Tour/StepTitle.js.map +1 -0
- package/dist/components/{src/components/tour → Tour}/context.d.ts +11 -0
- package/dist/components/Tour/context.d.ts.map +1 -0
- package/dist/components/{src/components/tour → Tour}/context.js +11 -0
- package/dist/components/Tour/context.js.map +1 -0
- package/dist/components/{tour → Tour}/index.d.ts +1 -1
- package/dist/components/Tour/index.d.ts.map +1 -0
- package/dist/components/{src/components/tour → Tour}/index.js +1 -1
- package/dist/components/Tour/index.js.map +1 -0
- package/dist/components/Tour/utils.d.ts +50 -0
- package/dist/components/Tour/utils.d.ts.map +1 -0
- package/dist/components/Tour/utils.js +54 -0
- package/dist/components/Tour/utils.js.map +1 -0
- package/dist/context/anchor.d.ts +28 -0
- package/dist/context/anchor.d.ts.map +1 -0
- package/dist/context/anchor.js +23 -0
- package/dist/context/anchor.js.map +1 -0
- package/dist/hooks/keyboard.d.ts +33 -0
- package/dist/hooks/keyboard.d.ts.map +1 -0
- package/dist/hooks/keyboard.js +59 -0
- package/dist/hooks/keyboard.js.map +1 -0
- package/dist/hooks/service-now.d.ts +30 -1
- package/dist/hooks/service-now.d.ts.map +1 -1
- package/dist/hooks/service-now.js +54 -15
- package/dist/hooks/service-now.js.map +1 -1
- package/dist/hooks/text.d.ts +10 -0
- package/dist/hooks/text.d.ts.map +1 -0
- package/dist/hooks/text.js +24 -0
- package/dist/hooks/text.js.map +1 -0
- package/dist/hooks/title.d.ts +13 -0
- package/dist/hooks/title.d.ts.map +1 -1
- package/dist/hooks/title.js +13 -0
- package/dist/hooks/title.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/svg/AI.d.ts +9 -0
- package/dist/svg/AI.d.ts.map +1 -0
- package/dist/svg/AI.js +12 -0
- package/dist/svg/AI.js.map +1 -0
- package/dist/svg/EDP.d.ts +9 -0
- package/dist/svg/EDP.d.ts.map +1 -0
- package/dist/svg/EDP.js +8 -0
- package/dist/svg/EDP.js.map +1 -0
- package/dist/{components/MiniLogo.d.ts → svg/Forbidden.d.ts} +2 -2
- package/dist/svg/Forbidden.d.ts.map +1 -0
- package/dist/svg/Forbidden.js +4 -0
- package/dist/svg/Forbidden.js.map +1 -0
- package/dist/svg/HUB.d.ts +9 -0
- package/dist/svg/HUB.d.ts.map +1 -0
- package/dist/svg/HUB.js +8 -0
- package/dist/svg/HUB.js.map +1 -0
- package/dist/svg/Logo.d.ts +5 -0
- package/dist/svg/Logo.d.ts.map +1 -0
- package/dist/svg/Logo.js +7 -0
- package/dist/svg/Logo.js.map +1 -0
- package/dist/{MiniLogo.d.ts → svg/MiniLogo.d.ts} +3 -0
- package/dist/svg/MiniLogo.d.ts.map +1 -0
- package/dist/{MiniLogo.js → svg/MiniLogo.js} +3 -0
- package/dist/svg/MiniLogo.js.map +1 -0
- package/dist/{components/src/components/MiniLogo.d.ts → svg/NotFound.d.ts} +2 -2
- package/dist/svg/NotFound.d.ts.map +1 -0
- package/dist/svg/NotFound.js +4 -0
- package/dist/svg/NotFound.js.map +1 -0
- package/dist/svg/ServerError.d.ts +6 -0
- package/dist/svg/ServerError.d.ts.map +1 -0
- package/dist/svg/ServerError.js +4 -0
- package/dist/svg/ServerError.js.map +1 -0
- package/dist/svg/Unauthenticated.d.ts +6 -0
- package/dist/svg/Unauthenticated.d.ts.map +1 -0
- package/dist/svg/Unauthenticated.js +4 -0
- package/dist/svg/Unauthenticated.js.map +1 -0
- package/dist/svg/index.d.ts +10 -0
- package/dist/svg/index.d.ts.map +1 -0
- package/dist/svg/index.js +10 -0
- package/dist/svg/index.js.map +1 -0
- package/dist/utils/accessibility.d.ts +73 -0
- package/dist/utils/accessibility.d.ts.map +1 -0
- package/dist/utils/accessibility.js +131 -0
- package/dist/utils/accessibility.js.map +1 -0
- package/dist/utils/cookie.d.ts +33 -0
- package/dist/utils/cookie.d.ts.map +1 -0
- package/dist/utils/cookie.js +62 -0
- package/dist/utils/cookie.js.map +1 -0
- package/package.json +24 -14
- package/readme.md +58 -13
- package/src/components/BannerWarning.tsx +6 -0
- package/src/components/Breadcrumb/index.tsx +20 -5
- package/src/components/ChatBot.tsx +15 -7
- package/src/components/ErrorFeedback.tsx +135 -0
- package/src/components/SelectionList.tsx +361 -0
- package/src/components/{tour → Tour}/StepContainer.tsx +16 -0
- package/src/components/{tour → Tour}/StepNavigation.tsx +26 -2
- package/src/components/{tour → Tour}/StepTitle.tsx +11 -1
- package/src/components/{tour → Tour}/context.tsx +11 -0
- package/src/components/{tour → Tour}/index.ts +1 -1
- package/src/components/Tour/utils.tsx +93 -0
- package/src/context/anchor.tsx +37 -0
- package/src/hooks/keyboard.tsx +80 -0
- package/src/hooks/service-now.tsx +60 -16
- package/src/hooks/text.tsx +30 -0
- package/src/hooks/title.tsx +13 -0
- package/src/index.ts +4 -3
- package/src/svg/AI.tsx +41 -0
- package/src/svg/EDP.tsx +39 -0
- package/src/svg/Forbidden.tsx +22 -0
- package/src/svg/HUB.tsx +39 -0
- package/src/svg/Logo.tsx +38 -0
- package/src/{components → svg}/MiniLogo.tsx +3 -0
- package/src/svg/NotFound.tsx +16 -0
- package/src/svg/ServerError.tsx +33 -0
- package/src/svg/Unauthenticated.tsx +16 -0
- package/src/svg/index.ts +9 -0
- package/src/utils/accessibility.ts +141 -0
- package/src/utils/cookie.ts +63 -0
- package/dist/BannerWarning.d.ts +0 -4
- package/dist/BannerWarning.d.ts.map +0 -1
- package/dist/BannerWarning.js +0 -6
- package/dist/BannerWarning.js.map +0 -1
- package/dist/Login.d.ts +0 -25
- package/dist/Login.d.ts.map +0 -1
- package/dist/Login.js +0 -104
- package/dist/Login.js.map +0 -1
- package/dist/MiniLogo.d.ts.map +0 -1
- package/dist/MiniLogo.js.map +0 -1
- package/dist/components/Login.d.ts +0 -26
- package/dist/components/Login.d.ts.map +0 -1
- package/dist/components/Login.js +0 -100
- package/dist/components/Login.js.map +0 -1
- package/dist/components/MiniLogo.d.ts.map +0 -1
- package/dist/components/MiniLogo.js +0 -4
- package/dist/components/MiniLogo.js.map +0 -1
- package/dist/components/src/components/BannerWarning.d.ts +0 -4
- package/dist/components/src/components/BannerWarning.d.ts.map +0 -1
- package/dist/components/src/components/BannerWarning.js +0 -6
- package/dist/components/src/components/BannerWarning.js.map +0 -1
- package/dist/components/src/components/Breadcrumb/index.d.ts +0 -42
- package/dist/components/src/components/Breadcrumb/index.d.ts.map +0 -1
- package/dist/components/src/components/Breadcrumb/index.js +0 -27
- package/dist/components/src/components/Breadcrumb/index.js.map +0 -1
- package/dist/components/src/components/Breadcrumb/styled.d.ts +0 -3
- package/dist/components/src/components/Breadcrumb/styled.d.ts.map +0 -1
- package/dist/components/src/components/Breadcrumb/styled.js +0 -36
- package/dist/components/src/components/Breadcrumb/styled.js.map +0 -1
- package/dist/components/src/components/ChatBot.d.ts +0 -2
- package/dist/components/src/components/ChatBot.d.ts.map +0 -1
- package/dist/components/src/components/ChatBot.js +0 -61
- package/dist/components/src/components/ChatBot.js.map +0 -1
- package/dist/components/src/components/Login.d.ts +0 -26
- package/dist/components/src/components/Login.d.ts.map +0 -1
- package/dist/components/src/components/Login.js +0 -100
- package/dist/components/src/components/Login.js.map +0 -1
- package/dist/components/src/components/MiniLogo.d.ts.map +0 -1
- package/dist/components/src/components/MiniLogo.js +0 -4
- package/dist/components/src/components/MiniLogo.js.map +0 -1
- package/dist/components/src/components/tour/StepContainer.d.ts.map +0 -1
- package/dist/components/src/components/tour/StepContainer.js +0 -48
- package/dist/components/src/components/tour/StepContainer.js.map +0 -1
- package/dist/components/src/components/tour/StepNavigation.d.ts +0 -13
- package/dist/components/src/components/tour/StepNavigation.d.ts.map +0 -1
- package/dist/components/src/components/tour/StepNavigation.js +0 -20
- package/dist/components/src/components/tour/StepNavigation.js.map +0 -1
- package/dist/components/src/components/tour/StepTitle.d.ts +0 -7
- package/dist/components/src/components/tour/StepTitle.d.ts.map +0 -1
- package/dist/components/src/components/tour/StepTitle.js +0 -5
- package/dist/components/src/components/tour/StepTitle.js.map +0 -1
- package/dist/components/src/components/tour/context.d.ts.map +0 -1
- package/dist/components/src/components/tour/context.js.map +0 -1
- package/dist/components/src/components/tour/index.d.ts +0 -4
- package/dist/components/src/components/tour/index.d.ts.map +0 -1
- package/dist/components/src/components/tour/index.js.map +0 -1
- package/dist/components/src/components/tour/utils.d.ts +0 -13
- package/dist/components/src/components/tour/utils.d.ts.map +0 -1
- package/dist/components/src/components/tour/utils.js +0 -43
- package/dist/components/src/components/tour/utils.js.map +0 -1
- package/dist/components/src/hooks/service-now.d.ts +0 -24
- package/dist/components/src/hooks/service-now.d.ts.map +0 -1
- package/dist/components/src/hooks/service-now.js +0 -161
- package/dist/components/src/hooks/service-now.js.map +0 -1
- package/dist/components/src/hooks/title.d.ts +0 -3
- package/dist/components/src/hooks/title.d.ts.map +0 -1
- package/dist/components/src/hooks/title.js +0 -13
- package/dist/components/src/hooks/title.js.map +0 -1
- package/dist/components/src/hooks/use-effect-once.d.ts +0 -12
- package/dist/components/src/hooks/use-effect-once.d.ts.map +0 -1
- package/dist/components/src/hooks/use-effect-once.js +0 -40
- package/dist/components/src/hooks/use-effect-once.js.map +0 -1
- package/dist/components/src/index.d.ts +0 -9
- package/dist/components/src/index.d.ts.map +0 -1
- package/dist/components/src/index.js +0 -9
- package/dist/components/src/index.js.map +0 -1
- package/dist/components/tour/StepContainer.d.ts +0 -13
- package/dist/components/tour/StepContainer.d.ts.map +0 -1
- package/dist/components/tour/StepContainer.js.map +0 -1
- package/dist/components/tour/StepNavigation.d.ts +0 -13
- package/dist/components/tour/StepNavigation.d.ts.map +0 -1
- package/dist/components/tour/StepNavigation.js.map +0 -1
- package/dist/components/tour/StepTitle.d.ts +0 -7
- package/dist/components/tour/StepTitle.d.ts.map +0 -1
- package/dist/components/tour/StepTitle.js.map +0 -1
- package/dist/components/tour/context.d.ts +0 -17
- package/dist/components/tour/context.d.ts.map +0 -1
- package/dist/components/tour/context.js +0 -48
- package/dist/components/tour/context.js.map +0 -1
- package/dist/components/tour/index.d.ts.map +0 -1
- package/dist/components/tour/index.js +0 -4
- package/dist/components/tour/index.js.map +0 -1
- package/dist/components/tour/utils.d.ts +0 -13
- package/dist/components/tour/utils.d.ts.map +0 -1
- package/dist/components/tour/utils.js +0 -43
- package/dist/components/tour/utils.js.map +0 -1
- package/dist/layout/src/components/tour/PortalSwitcherStep.d.ts +0 -2
- package/dist/layout/src/components/tour/PortalSwitcherStep.d.ts.map +0 -1
- package/dist/layout/src/components/tour/PortalSwitcherStep.js +0 -30
- package/dist/layout/src/components/tour/PortalSwitcherStep.js.map +0 -1
- package/src/components/Login.tsx +0 -157
- package/src/components/tour/utils.tsx +0 -65
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { Flex, IconBox, Text } from '@citric/core'
|
|
2
|
+
import { ArrowLeft, Check, ChevronRight } from '@citric/icons'
|
|
3
|
+
import { IconButton } from '@citric/ui'
|
|
4
|
+
import { WithStyle, listToClass, theme } from '@stack-spot/portal-theme'
|
|
5
|
+
import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
|
|
6
|
+
import { ReactElement, useCallback, useEffect, useMemo, useState } from 'react'
|
|
7
|
+
import { styled } from 'styled-components'
|
|
8
|
+
import { AnchorComponent, useAnchorTag } from '../context/anchor'
|
|
9
|
+
import { useKeyboardControls } from '../hooks/keyboard'
|
|
10
|
+
|
|
11
|
+
interface CustomLabel {
|
|
12
|
+
/**
|
|
13
|
+
* A unique identifier for this label. This is also used to read this label to screen readers.
|
|
14
|
+
*/
|
|
15
|
+
id: string,
|
|
16
|
+
/**
|
|
17
|
+
* A custom label that can be made up of any React component.
|
|
18
|
+
*/
|
|
19
|
+
element: React.ReactNode,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface Action {
|
|
23
|
+
/**
|
|
24
|
+
* The label of the action.
|
|
25
|
+
*/
|
|
26
|
+
label: string | CustomLabel,
|
|
27
|
+
/**
|
|
28
|
+
* Function to run on a click.
|
|
29
|
+
*/
|
|
30
|
+
onClick?: () => void,
|
|
31
|
+
/**
|
|
32
|
+
* URL to open on a click.
|
|
33
|
+
*/
|
|
34
|
+
href?: string,
|
|
35
|
+
/**
|
|
36
|
+
* Target of the URL to open.
|
|
37
|
+
*/
|
|
38
|
+
target?: React.AnchorHTMLAttributes<HTMLAnchorElement>['target'],
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface ItemWithIcon {
|
|
42
|
+
/**
|
|
43
|
+
* An Icon to appear at the left of the item.
|
|
44
|
+
*/
|
|
45
|
+
icon?: React.ReactElement,
|
|
46
|
+
/**
|
|
47
|
+
* An Icon to appear at the right of the item.
|
|
48
|
+
*/
|
|
49
|
+
iconRight?: React.ReactElement,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ListAction extends ItemWithIcon, Action {
|
|
53
|
+
/**
|
|
54
|
+
* Whether or not this option is currently active.
|
|
55
|
+
*/
|
|
56
|
+
active?: boolean,
|
|
57
|
+
/**
|
|
58
|
+
* Icon to render when this option is active.
|
|
59
|
+
*/
|
|
60
|
+
iconActive?: React.ReactElement,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface ListGroup {
|
|
64
|
+
/**
|
|
65
|
+
* If this group is rendered as a section with its items right below it or a collapsible, which requires a click to open a submenu.
|
|
66
|
+
*/
|
|
67
|
+
type?: 'section' | 'collapsible',
|
|
68
|
+
/**
|
|
69
|
+
* The items of this group.
|
|
70
|
+
*/
|
|
71
|
+
children: ListItem[],
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface ListSection extends ListGroup {
|
|
75
|
+
type: 'section',
|
|
76
|
+
/**
|
|
77
|
+
* The section's title.
|
|
78
|
+
*/
|
|
79
|
+
label?: string,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface ListCollapsible extends ListGroup, ItemWithIcon {
|
|
83
|
+
type?: 'collapsible',
|
|
84
|
+
/**
|
|
85
|
+
* The title of the collapsible menu.
|
|
86
|
+
*/
|
|
87
|
+
label: string,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
type ListItem = ListSection | ListCollapsible | ListAction
|
|
91
|
+
|
|
92
|
+
interface CurrentItemList {
|
|
93
|
+
items: ListItem[],
|
|
94
|
+
label?: string,
|
|
95
|
+
parent?: CurrentItemList,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const ANIMATION_DURATION_MS = 300
|
|
99
|
+
const MAX_HEIGHT_TRANSITION = `max-height ease-in ${ANIMATION_DURATION_MS / 1000}s`
|
|
100
|
+
|
|
101
|
+
export interface SelectionListProps extends WithStyle {
|
|
102
|
+
/**
|
|
103
|
+
* The id of this selection list. This is important for accessibility. Be sure to link it to the aria-controls tag of the element who
|
|
104
|
+
* controls the visibility of this selection list.
|
|
105
|
+
*/
|
|
106
|
+
id: string,
|
|
107
|
+
/**
|
|
108
|
+
* Whether or not the selection list is visible.
|
|
109
|
+
*/
|
|
110
|
+
visible?: boolean,
|
|
111
|
+
/**
|
|
112
|
+
* The options in the selection list.
|
|
113
|
+
*/
|
|
114
|
+
items: ListItem[],
|
|
115
|
+
/**
|
|
116
|
+
* Function to run when the selection list is hidden/closed.
|
|
117
|
+
*/
|
|
118
|
+
onHide?: () => void,
|
|
119
|
+
/**
|
|
120
|
+
* The maximum height for the selection list.
|
|
121
|
+
* @default "300px"
|
|
122
|
+
*/
|
|
123
|
+
maxHeight?: string,
|
|
124
|
+
/**
|
|
125
|
+
* A React element to render right before the items.
|
|
126
|
+
*/
|
|
127
|
+
before?: ReactElement,
|
|
128
|
+
/**
|
|
129
|
+
* A React element to render right after the items.
|
|
130
|
+
*/
|
|
131
|
+
after?: ReactElement,
|
|
132
|
+
/**
|
|
133
|
+
* Whether or not this list should be scrollable.
|
|
134
|
+
*/
|
|
135
|
+
scroll?: boolean,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
interface RenderOptions {
|
|
139
|
+
setCurrent: (current: CurrentItemList) => void,
|
|
140
|
+
controllerId?: string,
|
|
141
|
+
onClose?: () => void,
|
|
142
|
+
Link: AnchorComponent,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const SelectionBox = styled.div<{ $maxHeight: string, $scroll?: boolean }>`
|
|
146
|
+
max-height: 0;
|
|
147
|
+
overflow-y: ${({ $scroll }) => $scroll ? 'auto' : 'hidden'};
|
|
148
|
+
overflow-x: hidden;
|
|
149
|
+
transition: ${MAX_HEIGHT_TRANSITION}, visibility 0s ${ANIMATION_DURATION_MS / 1000}s;
|
|
150
|
+
z-index: 1;
|
|
151
|
+
box-shadow: 4px 4px 48px #000;
|
|
152
|
+
border-radius: 0.5rem;
|
|
153
|
+
visibility: hidden;
|
|
154
|
+
|
|
155
|
+
.selection-list-content {
|
|
156
|
+
display: flex;
|
|
157
|
+
flex-direction: column;
|
|
158
|
+
background: ${theme.color.light['500']};
|
|
159
|
+
border-radius: 0.5rem;
|
|
160
|
+
border: 1px solid ${theme.color.light['600']};
|
|
161
|
+
background-color: ${theme.color.light['300']};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.section-title, li > a {
|
|
165
|
+
height: 40px;
|
|
166
|
+
padding: 0 8px;
|
|
167
|
+
display: flex;
|
|
168
|
+
flex-direction: row;
|
|
169
|
+
align-items: center;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
li > a {
|
|
173
|
+
gap: 4px;
|
|
174
|
+
transition: background-color 0.2s;
|
|
175
|
+
&:hover, &:focus {
|
|
176
|
+
background: ${theme.color.light['400']};
|
|
177
|
+
}
|
|
178
|
+
.label {
|
|
179
|
+
flex: 1;
|
|
180
|
+
white-space: nowrap;
|
|
181
|
+
overflow: hidden;
|
|
182
|
+
text-overflow: ellipsis;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
li.section {
|
|
187
|
+
border-bottom: 2px solid ${theme.color.light['600']};
|
|
188
|
+
&:last-child {
|
|
189
|
+
border-bottom: none;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
&.visible {
|
|
194
|
+
max-height: ${({ $maxHeight }) => $maxHeight};
|
|
195
|
+
visibility: visible;
|
|
196
|
+
transition: ${MAX_HEIGHT_TRANSITION};
|
|
197
|
+
}
|
|
198
|
+
`
|
|
199
|
+
|
|
200
|
+
function renderAction({
|
|
201
|
+
label, href, onClick, icon, iconRight, active, target, iconActive = <Check />,
|
|
202
|
+
}: ListAction, { onClose, Link }: RenderOptions) {
|
|
203
|
+
function handleClick() {
|
|
204
|
+
onClick?.()
|
|
205
|
+
onClose?.()
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const isTextLabel = typeof label === 'string'
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
<li key={isTextLabel ? label : label.id} className="action">
|
|
212
|
+
<Link href={href} onClick={handleClick} target={target} tabIndex={0} aria-selected={active}>
|
|
213
|
+
{icon && <IconBox>{icon}</IconBox>}
|
|
214
|
+
{isTextLabel ? <Text appearance="body2" className="label">{label}</Text> : label.element}
|
|
215
|
+
{iconRight && <IconBox>{iconRight}</IconBox>}
|
|
216
|
+
{active && <IconBox>{iconActive}</IconBox>}
|
|
217
|
+
</Link>
|
|
218
|
+
</li>
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function renderCollapsible({ label, icon, iconRight, children }: ListCollapsible, { setCurrent, controllerId, Link }: RenderOptions) {
|
|
223
|
+
function handleClick(ev: React.MouseEvent) {
|
|
224
|
+
// accessibility: this will tell the screen reader the section was expanded before this link is removed from the DOM.
|
|
225
|
+
(ev.target as HTMLElement)?.setAttribute?.('aria-expanded', 'true')
|
|
226
|
+
setCurrent({ items: children, label })
|
|
227
|
+
}
|
|
228
|
+
return (
|
|
229
|
+
<li key={label} className="collapsible">
|
|
230
|
+
<Link onClick={handleClick} tabIndex={0} aria-expanded={false} aria-controls={controllerId}>
|
|
231
|
+
{icon && <IconBox>{icon}</IconBox>}
|
|
232
|
+
<Text appearance="body2" className="label">{label}</Text>
|
|
233
|
+
{iconRight && <IconBox>{iconRight}</IconBox>}
|
|
234
|
+
<IconBox><ChevronRight /></IconBox>
|
|
235
|
+
</Link>
|
|
236
|
+
</li>
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function renderSection({ label, children }: ListSection, options: RenderOptions) {
|
|
241
|
+
return (
|
|
242
|
+
<li key={label ?? children.map(c => c.label).join('-')} className="section">
|
|
243
|
+
{label && <Text appearance="overheader2" colorScheme="primary" className="section-title">{label}</Text>}
|
|
244
|
+
<ul>{children.map(i => renderItem(i, options))}</ul>
|
|
245
|
+
</li>
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function renderItem(item: ListItem, options: RenderOptions) {
|
|
250
|
+
if ('children' in item) {
|
|
251
|
+
return item.type === 'section' ? renderSection(item, options) : renderCollapsible(item, options)
|
|
252
|
+
}
|
|
253
|
+
return renderAction(item, options)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Renders a component that allows the selection of one item. The list is show/hidden according to the prop `visible`.
|
|
258
|
+
*
|
|
259
|
+
* The items in this list can be grouped into multiple sections. Sections may be displayed on a column (section) or might replace the
|
|
260
|
+
* current list of options with another list of options (collapsible).
|
|
261
|
+
*
|
|
262
|
+
* This component implements keyboard controls and accessibility features.
|
|
263
|
+
* @param props the component's props {@link SelectionListProps}.
|
|
264
|
+
*/
|
|
265
|
+
export const SelectionList = ({
|
|
266
|
+
id, items, className, style, visible = true, maxHeight = '300px', onHide, before, after, scroll,
|
|
267
|
+
}: SelectionListProps) => {
|
|
268
|
+
const Link = useAnchorTag()
|
|
269
|
+
const t = useTranslate(dictionary)
|
|
270
|
+
const [current, setCurrent] = useState<CurrentItemList>({ items })
|
|
271
|
+
const { keyboardControlledElement: wrapper, attachKeyboardListeners, detachKeyboardListeners } = useKeyboardControls(
|
|
272
|
+
{ onPressEscape: onHide, querySelectors: 'li.action a, li.collapsible a, button' },
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
const listItems = useMemo(
|
|
276
|
+
() => current.items.map(i => renderItem(
|
|
277
|
+
i,
|
|
278
|
+
{
|
|
279
|
+
setCurrent: (next: CurrentItemList) => setCurrent({ ...next, parent: current }),
|
|
280
|
+
onClose: onHide,
|
|
281
|
+
controllerId: id,
|
|
282
|
+
Link,
|
|
283
|
+
},
|
|
284
|
+
)),
|
|
285
|
+
[current],
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
const hide = useCallback((event: Event) => {
|
|
289
|
+
const target = (event.target as HTMLElement | null)
|
|
290
|
+
// if the element is not in the DOM anymore, we'll consider the click was inside the selection list
|
|
291
|
+
const isClickInsideSelectionList = !target?.isConnected || wrapper.current?.contains(target)
|
|
292
|
+
const isAction = target?.classList?.contains('action') || !!target?.closest('.action')
|
|
293
|
+
if (!isClickInsideSelectionList || isAction) onHide?.()
|
|
294
|
+
}, [])
|
|
295
|
+
|
|
296
|
+
useEffect(() => {
|
|
297
|
+
if (visible) {
|
|
298
|
+
setCurrent({ items })
|
|
299
|
+
attachKeyboardListeners()
|
|
300
|
+
if (onHide) setTimeout(() => document.addEventListener('click', hide), 50)
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
detachKeyboardListeners()
|
|
304
|
+
document.removeEventListener('click', hide)
|
|
305
|
+
}
|
|
306
|
+
}, [visible])
|
|
307
|
+
|
|
308
|
+
return (
|
|
309
|
+
<SelectionBox
|
|
310
|
+
id={id}
|
|
311
|
+
ref={wrapper}
|
|
312
|
+
$maxHeight={maxHeight}
|
|
313
|
+
style={style}
|
|
314
|
+
className={listToClass(['selection-list', visible ? 'visible' : undefined, className])}
|
|
315
|
+
$scroll={scroll}
|
|
316
|
+
aria-hidden={!visible}
|
|
317
|
+
>
|
|
318
|
+
<div className="selection-list-content">
|
|
319
|
+
{before}
|
|
320
|
+
{current.parent
|
|
321
|
+
? (
|
|
322
|
+
<Flex mt={5} mb={1} alignItems="center">
|
|
323
|
+
<IconButton
|
|
324
|
+
onClick={(ev) => {
|
|
325
|
+
// accessibility: this will tell the screen reader the section was collapsed before this button is removed from the DOM.
|
|
326
|
+
(ev.target as HTMLElement)?.setAttribute?.('aria-expanded', 'false')
|
|
327
|
+
setCurrent(current.parent ?? { items })
|
|
328
|
+
}}
|
|
329
|
+
sx={{ mr: 3 }}
|
|
330
|
+
title={t.back}
|
|
331
|
+
aria-controls={id}
|
|
332
|
+
aria-expanded={true}
|
|
333
|
+
>
|
|
334
|
+
<ArrowLeft />
|
|
335
|
+
</IconButton>
|
|
336
|
+
<Text appearance="microtext1">{current.label}</Text>
|
|
337
|
+
</Flex>
|
|
338
|
+
)
|
|
339
|
+
: undefined
|
|
340
|
+
}
|
|
341
|
+
<ul>
|
|
342
|
+
{listItems}
|
|
343
|
+
{after &&
|
|
344
|
+
<li className="action">
|
|
345
|
+
{after}
|
|
346
|
+
</li>
|
|
347
|
+
}
|
|
348
|
+
</ul>
|
|
349
|
+
</div>
|
|
350
|
+
</SelectionBox>
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const dictionary = {
|
|
355
|
+
en: {
|
|
356
|
+
back: 'Go back',
|
|
357
|
+
},
|
|
358
|
+
pt: {
|
|
359
|
+
back: 'Voltar',
|
|
360
|
+
},
|
|
361
|
+
} satisfies Dictionary
|
|
@@ -7,12 +7,28 @@ import { useTour } from './context'
|
|
|
7
7
|
|
|
8
8
|
interface StepContainerProps {
|
|
9
9
|
children: ReactNode,
|
|
10
|
+
/**
|
|
11
|
+
* The unique identifier for the step.
|
|
12
|
+
*/
|
|
10
13
|
stepKey: string,
|
|
14
|
+
/**
|
|
15
|
+
* The title for the step.
|
|
16
|
+
*/
|
|
11
17
|
title: string,
|
|
18
|
+
/**
|
|
19
|
+
* The position of the tour overlay related to the content being explained.
|
|
20
|
+
*/
|
|
12
21
|
position: PointingArrowPosition,
|
|
22
|
+
/**
|
|
23
|
+
* A customizable set of buttons for navigating the tour steps.
|
|
24
|
+
*/
|
|
13
25
|
customNavigation?: Omit<NavigationProps, 'stepKey'>,
|
|
14
26
|
}
|
|
15
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Tutorial: the overlay component for showing a step on React Tour.
|
|
30
|
+
* @param props the react props for the component {@link StepContainerProps}.
|
|
31
|
+
*/
|
|
16
32
|
export const StepContainer = ({ title, stepKey, customNavigation, position, children }: StepContainerProps) => {
|
|
17
33
|
const { finishStep } = useTour()
|
|
18
34
|
return <BoxWithPointingArrow $position={position}>
|
|
@@ -4,9 +4,33 @@ import { useTranslate } from '@stack-spot/portal-translate'
|
|
|
4
4
|
import { useTour } from './context'
|
|
5
5
|
import { finishTourStep } from './utils'
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
interface CustomNavigationButton {
|
|
8
|
+
/**
|
|
9
|
+
* The text content to render.
|
|
10
|
+
*/
|
|
11
|
+
text: string,
|
|
12
|
+
onClick?: () => void,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface NavigationProps {
|
|
16
|
+
/**
|
|
17
|
+
* The unique identifier of the step.
|
|
18
|
+
*/
|
|
19
|
+
stepKey: string,
|
|
20
|
+
/**
|
|
21
|
+
* The text and click handler for the button "next".
|
|
22
|
+
*/
|
|
23
|
+
nextButton?: CustomNavigationButton,
|
|
24
|
+
/**
|
|
25
|
+
* The text and click handler for the button "previous".
|
|
26
|
+
*/
|
|
27
|
+
prevButton?: CustomNavigationButton,
|
|
28
|
+
}
|
|
9
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Tutorial: the component in a React Tour overlay that shows the next and previous buttons (for step navigation).
|
|
32
|
+
* @param props the react props for the component {@link NavigationProps}.
|
|
33
|
+
*/
|
|
10
34
|
export const StepNavigation = ({ stepKey, nextButton, prevButton }: NavigationProps) => {
|
|
11
35
|
const { currentStep, steps, nextStep, prevStep } = useTour()
|
|
12
36
|
const t = useTranslate({ en: { of: 'of', back: 'back' }, pt: { of: 'de', back: 'voltar' } })
|
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import { Button, Flex, IconBox, Text } from '@citric/core'
|
|
2
2
|
import { TimesMini } from '@citric/icons'
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
interface StepTitleProps {
|
|
5
|
+
/**
|
|
6
|
+
* The step's title.
|
|
7
|
+
*/
|
|
5
8
|
title: string,
|
|
9
|
+
/**
|
|
10
|
+
* A function to run once the step is closed.
|
|
11
|
+
*/
|
|
6
12
|
onClose?: () => void,
|
|
7
13
|
}
|
|
8
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Tutorial: the component in a React Tour overlay that renders the title.
|
|
17
|
+
* @param props the react props for the component {@link StepTitleProps}.
|
|
18
|
+
*/
|
|
9
19
|
export const StepTitle = ({ title, onClose }: StepTitleProps) =>
|
|
10
20
|
<Flex w={12} pl={5} py={3} flexWrap="nowrap" justifyContent="space-between" alignItems="center">
|
|
11
21
|
<Text appearance="body2" colorScheme="inverse.contrastText" weight="medium"> {title} </Text>
|
|
@@ -4,6 +4,9 @@ import { finishTourStep } from './utils'
|
|
|
4
4
|
|
|
5
5
|
type TourConfig = Omit<ReactourProps, 'children'>
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Tutorial: the default configuration for a React Tour.
|
|
9
|
+
*/
|
|
7
10
|
export const defaultTourConfig: TourConfig = Object.freeze({
|
|
8
11
|
steps: [],
|
|
9
12
|
isOpen: true,
|
|
@@ -21,6 +24,10 @@ const TourContext = createContext<{ tourConfig: TourConfig, currentStep: number
|
|
|
21
24
|
currentStep: 0,
|
|
22
25
|
})
|
|
23
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Tutorial: provides the configuration for React Tour.
|
|
29
|
+
* @param props the configuration for the tour and the content to render.
|
|
30
|
+
*/
|
|
24
31
|
export const TourProvider = ({ config, children }: { config: TourConfig, children: ReactNode }) => {
|
|
25
32
|
const [currentStep, setCurrentStep] = useState<number>(0)
|
|
26
33
|
const tourConfig: TourConfig = {
|
|
@@ -41,6 +48,10 @@ export const TourProvider = ({ config, children }: { config: TourConfig, childre
|
|
|
41
48
|
</TourContext.Provider>
|
|
42
49
|
}
|
|
43
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Tutorial: retrieves data related to the current step of React Tour.
|
|
53
|
+
* @returns an object with all the information related to the steps of React Tour.
|
|
54
|
+
*/
|
|
44
55
|
export const useTour = () => {
|
|
45
56
|
const { currentStep, tourConfig: { nextStep, prevStep, steps } } = useContext(TourContext)
|
|
46
57
|
return {
|
|
@@ -2,6 +2,6 @@ import { ReactourProps, ReactourStep } from 'reactour'
|
|
|
2
2
|
|
|
3
3
|
export { StepContainer } from './StepContainer'
|
|
4
4
|
export { TourProvider, defaultTourConfig, useTour } from './context'
|
|
5
|
-
export { isNewTourStep, tourStepBuilder } from './utils'
|
|
5
|
+
export { hasFinishedTourStep, isNewTourStep, tourStepBuilder } from './utils'
|
|
6
6
|
export { ReactourStep as TourStep }
|
|
7
7
|
export type TourProps = Partial<Omit<ReactourProps, 'children'>> & { steps: ReactourStep[] }
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { theme } from '@stack-spot/portal-theme'
|
|
2
|
+
import { ReactNode } from 'react'
|
|
3
|
+
import { ReactourStep } from 'reactour'
|
|
4
|
+
import { getCookie, setCookie } from '../../utils/cookie'
|
|
5
|
+
import { PointingArrowPosition, StepContainer } from './StepContainer'
|
|
6
|
+
import { NavigationProps } from './StepNavigation'
|
|
7
|
+
|
|
8
|
+
const TOUR_COOKIE = 'guided-tour-global'
|
|
9
|
+
|
|
10
|
+
const getTourCookie = () => {
|
|
11
|
+
const currentTourObject = getCookie(TOUR_COOKIE)
|
|
12
|
+
return currentTourObject ? currentTourObject.split(',') : []
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Tutorial: marks the tour step as finished. This sets a cookie, preventing the tour from showing again.
|
|
17
|
+
* @param key the identifier for the step to mark as finished.
|
|
18
|
+
*/
|
|
19
|
+
export const finishTourStep = (key: string) => {
|
|
20
|
+
const finishedTours: string[] = getTourCookie()
|
|
21
|
+
if (!finishedTours.includes(key)) finishedTours.push(key)
|
|
22
|
+
setCookie(TOUR_COOKIE, finishedTours.toString())
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Tutorial: verifies if the React Tour step has not finished yet.
|
|
27
|
+
*
|
|
28
|
+
* A step has not finished if the array stored as a cookie doesn't include the string value of `step.selector`.
|
|
29
|
+
* @param step the step config.
|
|
30
|
+
* @returns true if the step has not yet been marked as finished. False otherwise.
|
|
31
|
+
*/
|
|
32
|
+
export const isNewTourStep = (step: ReactourStep) => !hasFinishedTourStep(`${step.selector}`)
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Tutorial: verifies if the key passed as parameter refers to a React Tour step that has already finished.
|
|
36
|
+
*
|
|
37
|
+
* The key refers to a finished step if the array stored as a cookie includes it.
|
|
38
|
+
* @param key the step's identifier to check.
|
|
39
|
+
* @returns true if the key refers to a finished step. False otherwise.
|
|
40
|
+
*/
|
|
41
|
+
export const hasFinishedTourStep = (key: string) => getTourCookie().includes(key)
|
|
42
|
+
|
|
43
|
+
interface StackspotTourStep extends ReactourStep {
|
|
44
|
+
/**
|
|
45
|
+
* The step's title.
|
|
46
|
+
*/
|
|
47
|
+
title: string,
|
|
48
|
+
/**
|
|
49
|
+
* The unique identifier for the step (key).
|
|
50
|
+
*/
|
|
51
|
+
selector: string,
|
|
52
|
+
/**
|
|
53
|
+
* The step's content.
|
|
54
|
+
*/
|
|
55
|
+
content: ReactNode,
|
|
56
|
+
/**
|
|
57
|
+
* A set of properties for customizing the next and previous buttons.
|
|
58
|
+
*/
|
|
59
|
+
customNavigation?: NavigationProps,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Tutorial: utility for building a React Tour step. This already includes some default configuration for tours in Stackspot.
|
|
64
|
+
* @param options the options for building the step: {@link StackspotTourStep}.
|
|
65
|
+
* @returns the React Tour step.
|
|
66
|
+
*/
|
|
67
|
+
export const tourStepBuilder = ({
|
|
68
|
+
selector,
|
|
69
|
+
position,
|
|
70
|
+
title,
|
|
71
|
+
content,
|
|
72
|
+
style,
|
|
73
|
+
customNavigation,
|
|
74
|
+
...rest
|
|
75
|
+
}: StackspotTourStep): ReactourStep => ({
|
|
76
|
+
selector,
|
|
77
|
+
content: (<StepContainer
|
|
78
|
+
stepKey={selector}
|
|
79
|
+
position={position as PointingArrowPosition}
|
|
80
|
+
title={title}
|
|
81
|
+
customNavigation={customNavigation}>
|
|
82
|
+
{content}
|
|
83
|
+
</StepContainer>),
|
|
84
|
+
position,
|
|
85
|
+
style: {
|
|
86
|
+
backgroundColor: theme.color.inverse[500],
|
|
87
|
+
width: '256px',
|
|
88
|
+
padding: 0,
|
|
89
|
+
top: ['right', 'left'].includes(position as PointingArrowPosition) ? '-3px' : '0',
|
|
90
|
+
...(style || {}),
|
|
91
|
+
},
|
|
92
|
+
...(rest || {}),
|
|
93
|
+
})
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react'
|
|
2
|
+
|
|
3
|
+
export type AnchorComponent = (props: React.AnchorHTMLAttributes<HTMLAnchorElement>) => React.ReactElement
|
|
4
|
+
|
|
5
|
+
interface AnchorContext {
|
|
6
|
+
/**
|
|
7
|
+
* The component to render by a layout component a link is needed.
|
|
8
|
+
* @default <a>
|
|
9
|
+
*/
|
|
10
|
+
anchorTag?: AnchorComponent,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const context = createContext<AnchorContext>({})
|
|
14
|
+
|
|
15
|
+
const Anchor: AnchorComponent = props => <a {...props} />
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Some components need to create HTML anchors (links) to other pages in the website. These links, sometimes, must to be managed by a
|
|
19
|
+
* navigator for React. Since we don't want to couple this library with any specific navigator, you can provide your own component for
|
|
20
|
+
* creating links, it must follow the same interface of the HTML's `a` tag.
|
|
21
|
+
*
|
|
22
|
+
* If this is not used, the tag `<a>` is used by default.
|
|
23
|
+
* @param props the anchor component (anchorTag) and the content to render (children).
|
|
24
|
+
*/
|
|
25
|
+
export const AnchorProvider = ({ children, ...props }: Required<AnchorContext> & { children: React.ReactNode }) => (
|
|
26
|
+
<context.Provider value={props}>{children}</context.Provider>
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A React hook for retrieving the Link (anchor) component.
|
|
31
|
+
* @returns the link component declared at {@link AnchorProvider} or a component that renders the tag <a> from HTML if no link component was
|
|
32
|
+
* provided.
|
|
33
|
+
*/
|
|
34
|
+
export function useAnchorTag(): AnchorComponent {
|
|
35
|
+
const { anchorTag } = useContext(context)
|
|
36
|
+
return anchorTag ?? Anchor
|
|
37
|
+
}
|