@ultraviolet/plus 0.14.4 → 0.15.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/index.d.ts +45 -10
- package/dist/src/components/Navigation/NavigationProvider.js +27 -8
- package/dist/src/components/Navigation/components/Group.js +2 -0
- package/dist/src/components/Navigation/components/Item.js +139 -53
- package/dist/src/components/Navigation/components/PinnedItems.js +112 -13
- package/dist/src/components/Navigation/constants.js +1 -2
- package/dist/src/components/Navigation/locales/en.js +3 -1
- package/package.json +9 -9
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { ReactNode, MouseEventHandler, ComponentProps, RefObject } from 'react';
|
|
2
|
+
import { ReactNode, MouseEventHandler, ComponentProps, RefObject, Dispatch } from 'react';
|
|
3
3
|
import * as _emotion_react_jsx_runtime from '@emotion/react/jsx-runtime';
|
|
4
4
|
import { langs } from '@uiw/codemirror-extensions-langs';
|
|
5
5
|
import CodeMirror from '@uiw/react-codemirror';
|
|
@@ -455,11 +455,12 @@ declare const Navigation: {
|
|
|
455
455
|
* some part of your logo
|
|
456
456
|
*/
|
|
457
457
|
}) => _emotion_react_jsx_runtime.JSX.Element | null;
|
|
458
|
-
Item: ({ children, categoryIcon, categoryIconVariant, label, subLabel, badgeText, badgeSentiment, href, onClick, toggle, active, noPinButton, type, hasParents, as, disabled, noExpand,
|
|
458
|
+
Item: ({ children, categoryIcon, categoryIconVariant, label, subLabel, badgeText, badgeSentiment, href, onClick, toggle, active, noPinButton, type, hasParents, as, disabled, noExpand, index, id, }: {
|
|
459
459
|
children?: ReactNode;
|
|
460
460
|
categoryIcon?: "security" | "console" | "database" | "pin" | "billing" | "storage" | "baremetal" | "webHosting" | "vpc" | "useCase" | "toolsServices" | "serverless" | "observability" | "network" | "managedServices" | "iot" | "documentation" | "dedicatedServer" | "datacenter" | "containers" | "compute" | "ai" | "labs" | "devTools" | "applicationIntegration" | undefined;
|
|
461
461
|
categoryIconVariant?: "neutral" | "primary" | undefined;
|
|
462
462
|
label: string;
|
|
463
|
+
id: string;
|
|
463
464
|
subLabel?: string | undefined;
|
|
464
465
|
badgeText?: string | undefined;
|
|
465
466
|
badgeSentiment?: ("neutral" | "danger" | "info" | "primary" | "secondary" | "success" | "warning") | undefined;
|
|
@@ -469,24 +470,34 @@ declare const Navigation: {
|
|
|
469
470
|
active?: boolean | undefined;
|
|
470
471
|
noPinButton?: boolean | undefined;
|
|
471
472
|
type?: ("default" | "pinned" | "pinnedGroup") | undefined;
|
|
472
|
-
toggleMenu?: (() => void) | undefined;
|
|
473
473
|
hasParents?: boolean | undefined;
|
|
474
|
+
index?: number | undefined;
|
|
474
475
|
as?: keyof react.JSX.IntrinsicElements | undefined;
|
|
475
476
|
noExpand?: boolean | undefined;
|
|
476
477
|
disabled?: boolean | undefined;
|
|
477
478
|
}) => _emotion_react_jsx_runtime.JSX.Element | null;
|
|
478
|
-
PinnedItems: (
|
|
479
|
+
PinnedItems: ({ toggle }: {
|
|
480
|
+
toggle?: boolean | undefined;
|
|
481
|
+
}) => _emotion_react_jsx_runtime.JSX.Element | null;
|
|
479
482
|
Separator: () => _emotion_react_jsx_runtime.JSX.Element;
|
|
480
483
|
};
|
|
481
484
|
|
|
482
485
|
declare const _default: {
|
|
483
486
|
readonly 'navigation.pin.tooltip': "Pin product";
|
|
484
487
|
readonly 'navigation.unpin.tooltip': "Unpin product";
|
|
488
|
+
readonly 'navigation.pin.limit': "You cannot pin more products";
|
|
485
489
|
readonly 'navigation.pinned.item.group.label': "Pinned items";
|
|
486
490
|
readonly 'navigation.expand.button': "Expand sidebar";
|
|
487
491
|
readonly 'navigation.collapse.button': "Collapse sidebar";
|
|
492
|
+
readonly 'navigation.pinned.item.group.empty': "You have no pinned items.";
|
|
488
493
|
};
|
|
489
494
|
|
|
495
|
+
type Item = {
|
|
496
|
+
label: string;
|
|
497
|
+
active?: boolean;
|
|
498
|
+
onClick?: (toggle?: true | false) => void;
|
|
499
|
+
};
|
|
500
|
+
type Items = Record<string, Item>;
|
|
490
501
|
type ContextProps = {
|
|
491
502
|
expanded: boolean;
|
|
492
503
|
toggleExpand: (toggle?: boolean) => void;
|
|
@@ -501,6 +512,21 @@ type ContextProps = {
|
|
|
501
512
|
locales: typeof _default;
|
|
502
513
|
width: number;
|
|
503
514
|
setWidth: (width: number) => void;
|
|
515
|
+
/**
|
|
516
|
+
* This function will reorder the pinned items based on the initial index and
|
|
517
|
+
* the end index.
|
|
518
|
+
*/
|
|
519
|
+
reorderItems: (
|
|
520
|
+
/**
|
|
521
|
+
* The initial index of the item
|
|
522
|
+
*/
|
|
523
|
+
initialIndex: number,
|
|
524
|
+
/**
|
|
525
|
+
* The end index of the item
|
|
526
|
+
*/
|
|
527
|
+
endIndex: number) => void;
|
|
528
|
+
items: Items;
|
|
529
|
+
registerItem: Dispatch<Items>;
|
|
504
530
|
};
|
|
505
531
|
declare const useNavigation: () => ContextProps;
|
|
506
532
|
type NavigationProviderProps = {
|
|
@@ -526,11 +552,6 @@ type NavigationProviderProps = {
|
|
|
526
552
|
* navigation will be expanded by default otherwise it will be collapsed
|
|
527
553
|
*/
|
|
528
554
|
initialExpanded?: boolean;
|
|
529
|
-
/**
|
|
530
|
-
* This function is triggered when the user click on the pin/unpin button
|
|
531
|
-
* of an item
|
|
532
|
-
*/
|
|
533
|
-
onClickPinUnpin?: (pinned: string[]) => void;
|
|
534
555
|
locales?: typeof _default;
|
|
535
556
|
/**
|
|
536
557
|
* This function is triggered when user click on expand button on the footer
|
|
@@ -538,8 +559,22 @@ type NavigationProviderProps = {
|
|
|
538
559
|
* and it automatically collapse / expand.
|
|
539
560
|
*/
|
|
540
561
|
onClickExpand?: (expanded: boolean) => void;
|
|
562
|
+
/**
|
|
563
|
+
* This function is triggered when the user click on the pin/unpin button
|
|
564
|
+
* of an item. To access all pinned item you can use the `useNavigation` hook
|
|
565
|
+
* and access the `pinnedItems` property.
|
|
566
|
+
*/
|
|
567
|
+
onClickPinUnpin?: (
|
|
568
|
+
/**
|
|
569
|
+
* The state of the item after the click
|
|
570
|
+
*/
|
|
571
|
+
state: 'pin' | 'unpin',
|
|
572
|
+
/**
|
|
573
|
+
* The current items that has been pinned on click
|
|
574
|
+
*/
|
|
575
|
+
pinned: string) => void;
|
|
541
576
|
};
|
|
542
|
-
declare const NavigationProvider: ({ children, pinnedFeature,
|
|
577
|
+
declare const NavigationProvider: ({ children, pinnedFeature, initialPinned, initialExpanded, locales, pinLimit, onClickExpand, initialWidth, onClickPinUnpin, }: NavigationProviderProps) => _emotion_react_jsx_runtime.JSX.Element;
|
|
543
578
|
|
|
544
579
|
type FAQProps = {
|
|
545
580
|
description: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useContext, useState, useRef, useCallback, useMemo, createContext } from 'react';
|
|
1
|
+
import { useContext, useState, useReducer, useRef, useCallback, useMemo, createContext } from 'react';
|
|
2
2
|
import { ANIMATION_DURATION, NAVIGATION_WIDTH } from './constants.js';
|
|
3
3
|
import NavigationLocales from './locales/en.js';
|
|
4
4
|
import { jsx } from '@emotion/react/jsx-runtime';
|
|
@@ -20,24 +20,34 @@ const NavigationContext = /*#__PURE__*/createContext({
|
|
|
20
20
|
current: null
|
|
21
21
|
},
|
|
22
22
|
width: NAVIGATION_WIDTH,
|
|
23
|
-
setWidth: () => {}
|
|
23
|
+
setWidth: () => {},
|
|
24
|
+
reorderItems: () => {},
|
|
25
|
+
items: {},
|
|
26
|
+
registerItem: () => {}
|
|
24
27
|
});
|
|
25
28
|
const useNavigation = () => useContext(NavigationContext);
|
|
26
29
|
const NavigationProvider = ({
|
|
27
30
|
children,
|
|
28
31
|
pinnedFeature = false,
|
|
29
|
-
onClickPinUnpin,
|
|
30
32
|
initialPinned,
|
|
31
33
|
initialExpanded = true,
|
|
32
34
|
locales = NavigationLocales,
|
|
33
35
|
pinLimit = 7,
|
|
34
36
|
onClickExpand,
|
|
35
|
-
initialWidth = NAVIGATION_WIDTH
|
|
37
|
+
initialWidth = NAVIGATION_WIDTH,
|
|
38
|
+
onClickPinUnpin
|
|
36
39
|
}) => {
|
|
37
40
|
const [expanded, setExpanded] = useState(initialExpanded);
|
|
38
41
|
const [pinnedItems, setPinnedItems] = useState(initialPinned ?? []);
|
|
39
42
|
const [animation, setAnimation] = useState(false);
|
|
40
43
|
const [width, setWidth] = useState(initialWidth);
|
|
44
|
+
|
|
45
|
+
// This is used to store the items that are registered in the navigation
|
|
46
|
+
// This way we can retrieve items with their active state in pinned feature
|
|
47
|
+
const [items, registerItem] = useReducer((oldState, newState) => ({
|
|
48
|
+
...oldState,
|
|
49
|
+
...newState
|
|
50
|
+
}), {});
|
|
41
51
|
const navigationRef = useRef(null);
|
|
42
52
|
|
|
43
53
|
// This function will be triggered when expand/collapse button is clicked
|
|
@@ -60,12 +70,18 @@ const NavigationProvider = ({
|
|
|
60
70
|
}, [expanded, onClickExpand, setAnimation, setExpanded]);
|
|
61
71
|
const pinItem = useCallback(item => {
|
|
62
72
|
setPinnedItems([...pinnedItems, item]);
|
|
63
|
-
onClickPinUnpin?.(
|
|
73
|
+
onClickPinUnpin?.('pin', item);
|
|
64
74
|
}, [onClickPinUnpin, pinnedItems]);
|
|
65
75
|
const unpinItem = useCallback(item => {
|
|
66
76
|
setPinnedItems(pinnedItems.filter(localItem => localItem !== item));
|
|
67
|
-
onClickPinUnpin?.(
|
|
77
|
+
onClickPinUnpin?.('unpin', item);
|
|
68
78
|
}, [onClickPinUnpin, pinnedItems]);
|
|
79
|
+
const reorderItems = useCallback((initialIndex, endIndex) => {
|
|
80
|
+
const newPinnedItems = [...pinnedItems];
|
|
81
|
+
const [removed] = newPinnedItems.splice(initialIndex, 1);
|
|
82
|
+
newPinnedItems.splice(endIndex, 0, removed);
|
|
83
|
+
setPinnedItems(newPinnedItems);
|
|
84
|
+
}, [pinnedItems]);
|
|
69
85
|
const value = useMemo(() => ({
|
|
70
86
|
expanded,
|
|
71
87
|
toggleExpand,
|
|
@@ -79,8 +95,11 @@ const NavigationProvider = ({
|
|
|
79
95
|
setAnimation,
|
|
80
96
|
navigationRef,
|
|
81
97
|
width,
|
|
82
|
-
setWidth
|
|
83
|
-
|
|
98
|
+
setWidth,
|
|
99
|
+
reorderItems,
|
|
100
|
+
registerItem,
|
|
101
|
+
items
|
|
102
|
+
}), [expanded, toggleExpand, pinnedItems, pinItem, unpinItem, pinnedFeature, locales, pinLimit, animation, width, reorderItems, items]);
|
|
84
103
|
return jsx(NavigationContext.Provider, {
|
|
85
104
|
value: value,
|
|
86
105
|
children: children
|
|
@@ -9,6 +9,8 @@ const StyledText = /*#__PURE__*/_styled(Text, {
|
|
|
9
9
|
target: "eh4zgrv2"
|
|
10
10
|
})("padding-bottom:", ({
|
|
11
11
|
theme
|
|
12
|
+
}) => theme.space['1'], ";padding-left:", ({
|
|
13
|
+
theme
|
|
12
14
|
}) => theme.space['1'], ";transition:opacity ", ANIMATION_DURATION, "ms ease-in-out,height ", ANIMATION_DURATION, "ms ease-in-out;height:", ({
|
|
13
15
|
theme
|
|
14
16
|
}) => `calc(${theme.typography.bodySmallStrong.lineHeight} + ${theme.space['1']})`, ";");
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import _styled from '@emotion/styled/base';
|
|
2
2
|
import '@emotion/react';
|
|
3
|
-
import { MenuV2, Stack, Tooltip, Expandable,
|
|
4
|
-
import { useState, useCallback,
|
|
3
|
+
import { Button, MenuV2, Stack, Tooltip, Expandable, Badge, Text, fadeIn } from '@ultraviolet/ui';
|
|
4
|
+
import { useEffect, useState, useCallback, useMemo, Children, isValidElement, cloneElement } from 'react';
|
|
5
5
|
import { useNavigation } from '../NavigationProvider.js';
|
|
6
|
-
import { ANIMATION_DURATION,
|
|
6
|
+
import { ANIMATION_DURATION, shrinkHeight } from '../constants.js';
|
|
7
7
|
import { jsxs, Fragment, jsx } from '@emotion/react/jsx-runtime';
|
|
8
8
|
import { CategoryIcon } from '../../../../@ultraviolet/icons/dist/components/CategoryIcon/index.js';
|
|
9
9
|
import { Icon } from '../../../../@ultraviolet/icons/dist/components/Icon/index.js';
|
|
@@ -17,17 +17,46 @@ const NeutralButtonLink = process.env.NODE_ENV === "production" ? {
|
|
|
17
17
|
styles: "color:inherit;text-decoration:none;background-color:inherit;border:none;text-align:left",
|
|
18
18
|
toString: _EMOTION_STRINGIFIED_CSS_ERROR__
|
|
19
19
|
};
|
|
20
|
+
|
|
21
|
+
// Pin button when the navigation is expanded
|
|
20
22
|
const ExpandedPinnedButton = /*#__PURE__*/_styled(Button, {
|
|
23
|
+
target: "e134hokc12"
|
|
24
|
+
})(process.env.NODE_ENV === "production" ? {
|
|
25
|
+
name: "9hkyle",
|
|
26
|
+
styles: "opacity:0;right:0;position:absolute;left:-24px;top:0;bottom:0;margin:auto;&:hover,&:focus,&:active{opacity:1;}"
|
|
27
|
+
} : {
|
|
28
|
+
name: "9hkyle",
|
|
29
|
+
styles: "opacity:0;right:0;position:absolute;left:-24px;top:0;bottom:0;margin:auto;&:hover,&:focus,&:active{opacity:1;}",
|
|
30
|
+
toString: _EMOTION_STRINGIFIED_CSS_ERROR__
|
|
31
|
+
});
|
|
32
|
+
const GrabIcon = /*#__PURE__*/_styled(Icon, {
|
|
21
33
|
target: "e134hokc11"
|
|
22
|
-
})("opacity:0;
|
|
34
|
+
})("opacity:0;margin:0 ", ({
|
|
35
|
+
theme
|
|
36
|
+
}) => theme.space['0.25'], ";cursor:grab;");
|
|
37
|
+
|
|
38
|
+
// Pin button when the navigation is collapsed
|
|
23
39
|
const CollapsedPinnedButton = /*#__PURE__*/_styled(Button, {
|
|
24
40
|
target: "e134hokc10"
|
|
25
|
-
})("
|
|
26
|
-
|
|
41
|
+
})(process.env.NODE_ENV === "production" ? {
|
|
42
|
+
name: "i8vgdw",
|
|
43
|
+
styles: "position:absolute;opacity:0;right:0;margin:auto;top:0;bottom:0;&:hover{opacity:1;}"
|
|
44
|
+
} : {
|
|
45
|
+
name: "i8vgdw",
|
|
46
|
+
styles: "position:absolute;opacity:0;right:0;margin:auto;top:0;bottom:0;&:hover{opacity:1;}",
|
|
47
|
+
toString: _EMOTION_STRINGIFIED_CSS_ERROR__
|
|
48
|
+
});
|
|
49
|
+
const StyledBadge = /*#__PURE__*/_styled(Badge, {
|
|
27
50
|
target: "e134hokc9"
|
|
28
|
-
})(
|
|
29
|
-
const
|
|
51
|
+
})();
|
|
52
|
+
const StyledMenuItem = /*#__PURE__*/_styled(MenuV2.Item, {
|
|
53
|
+
shouldForwardProp: prop => !['isPinnable'].includes(prop),
|
|
30
54
|
target: "e134hokc8"
|
|
55
|
+
})("text-align:left;&:hover,&:focus,&:active{", CollapsedPinnedButton, "{opacity:1;}", StyledBadge, "{opacity:", ({
|
|
56
|
+
isPinnable
|
|
57
|
+
}) => isPinnable ? 0 : 1, ";}}");
|
|
58
|
+
const StyledMenu = /*#__PURE__*/_styled(MenuV2, {
|
|
59
|
+
target: "e134hokc7"
|
|
31
60
|
})(process.env.NODE_ENV === "production" ? {
|
|
32
61
|
name: "educr3",
|
|
33
62
|
styles: "width:180px"
|
|
@@ -36,9 +65,6 @@ const StyledMenu = /*#__PURE__*/_styled(MenuV2, {
|
|
|
36
65
|
styles: "width:180px",
|
|
37
66
|
toString: _EMOTION_STRINGIFIED_CSS_ERROR__
|
|
38
67
|
});
|
|
39
|
-
const StyledBadge = /*#__PURE__*/_styled(Badge, {
|
|
40
|
-
target: "e134hokc7"
|
|
41
|
-
})("transition:", PIN_BUTTON_OPACITY_TRANSITION, ";transition-delay:0.3s;");
|
|
42
68
|
const PaddingStack = /*#__PURE__*/_styled(Stack, {
|
|
43
69
|
target: "e134hokc6"
|
|
44
70
|
})(process.env.NODE_ENV === "production" ? {
|
|
@@ -80,11 +106,13 @@ const StyledContainer = /*#__PURE__*/_styled(Stack, {
|
|
|
80
106
|
theme
|
|
81
107
|
}) => `calc(${theme.space['0.25']} + ${theme.space['0.5']}) ${theme.space['1']}`, ";&[data-has-sub-label='true']{padding:", ({
|
|
82
108
|
theme
|
|
83
|
-
}) => `${theme.space['0.5']} ${theme.space['1']}`, ";}width:100%;&:hover[data-has-no-expand='false']:not([disabled]):not(\n [data-is-active='true']\n ),&:focus[data-has-no-expand='false']:not([disabled]):not(\n [data-is-active='true']\n )
|
|
109
|
+
}) => `${theme.space['0.5']} ${theme.space['1']}`, ";}width:100%;&:hover[data-has-no-expand='false']:not([disabled]):not(\n [data-is-active='true']\n ),&:focus[data-has-no-expand='false']:not([disabled]):not(\n [data-is-active='true']\n ){background-color:", ({
|
|
84
110
|
theme
|
|
85
|
-
}) => theme.colors.neutral.
|
|
111
|
+
}) => theme.colors.neutral.backgroundWeak, ";}&[data-has-active-children='true'][data-has-no-expand='false']:not(\n [disabled][data-is-active='true']\n ){background-color:", ({
|
|
86
112
|
theme
|
|
87
|
-
}) => theme.colors.neutral.
|
|
113
|
+
}) => theme.colors.neutral.backgroundWeakHover, ";", WrapText, "{color:", ({
|
|
114
|
+
theme
|
|
115
|
+
}) => theme.colors.neutral.textWeakHover, ";}", ExpandedPinnedButton, ",", CollapsedPinnedButton, "{opacity:1;}&[data-is-pinnable='true']{", StyledBadge, "{opacity:0;}}}&[data-has-no-expand='false']:not([disabled]){&:hover,&:focus,&:active{", ExpandedPinnedButton, ",", CollapsedPinnedButton, ",", GrabIcon, "{opacity:1;}", StyledBadge, "{opacity:0;}}}&:hover[data-has-children='false'][data-is-active='false']:not([disabled]){", WrapText, "{color:", ({
|
|
88
116
|
theme
|
|
89
117
|
}) => theme.colors.neutral.textWeakHover, ";}}&:active[data-has-no-expand='false']:not([disabled]):not(\n [data-is-active='true']\n ){background-color:", ({
|
|
90
118
|
theme
|
|
@@ -130,7 +158,8 @@ const Item = ({
|
|
|
130
158
|
as,
|
|
131
159
|
disabled,
|
|
132
160
|
noExpand = false,
|
|
133
|
-
|
|
161
|
+
index,
|
|
162
|
+
id
|
|
134
163
|
}) => {
|
|
135
164
|
const context = useNavigation();
|
|
136
165
|
if (!context) {
|
|
@@ -144,8 +173,22 @@ const Item = ({
|
|
|
144
173
|
unpinItem,
|
|
145
174
|
pinnedItems,
|
|
146
175
|
pinLimit,
|
|
147
|
-
animation
|
|
176
|
+
animation,
|
|
177
|
+
registerItem
|
|
148
178
|
} = context;
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
if (type !== 'pinnedGroup') {
|
|
181
|
+
registerItem({
|
|
182
|
+
[id]: {
|
|
183
|
+
label,
|
|
184
|
+
active,
|
|
185
|
+
onClick
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
191
|
+
[active, id, label, registerItem]);
|
|
149
192
|
const [internalToggle, setToggle] = useState(toggle !== false);
|
|
150
193
|
const triggerToggle = useCallback(value => {
|
|
151
194
|
setToggle(value);
|
|
@@ -161,21 +204,20 @@ const Item = ({
|
|
|
161
204
|
}, 1);
|
|
162
205
|
}
|
|
163
206
|
}, [animation, toggle]);
|
|
164
|
-
const PaddedStack = noExpand ? Stack : PaddingStack;
|
|
207
|
+
const PaddedStack = noExpand || type === 'pinnedGroup' ? Stack : PaddingStack;
|
|
165
208
|
const hasHrefAndNoChildren = href && !children;
|
|
166
209
|
const hasPinnedFeatureAndNoChildren = pinnedFeature && !children && !noPinButton;
|
|
167
|
-
const isItemPinned = pinnedItems.includes(
|
|
210
|
+
const isItemPinned = pinnedItems.includes(id);
|
|
168
211
|
const shouldShowPinnedButton = useMemo(() => {
|
|
169
212
|
if (href || disabled) return false;
|
|
170
|
-
if (pinnedItems.length >= pinLimit && type === 'default') return false;
|
|
171
213
|
if (hasPinnedFeatureAndNoChildren && type !== 'default') {
|
|
172
214
|
return true;
|
|
173
215
|
}
|
|
174
|
-
if (hasPinnedFeatureAndNoChildren
|
|
216
|
+
if (hasPinnedFeatureAndNoChildren) {
|
|
175
217
|
return true;
|
|
176
218
|
}
|
|
177
219
|
return false;
|
|
178
|
-
}, [disabled, hasPinnedFeatureAndNoChildren, href,
|
|
220
|
+
}, [disabled, hasPinnedFeatureAndNoChildren, href, type]);
|
|
179
221
|
const hasActiveChildren = useMemo(() => {
|
|
180
222
|
if (!children) return false;
|
|
181
223
|
return Children.map(children, child => /*#__PURE__*/isValidElement(child) ? child.props?.active : false).includes(true);
|
|
@@ -193,7 +235,7 @@ const Item = ({
|
|
|
193
235
|
return 'button';
|
|
194
236
|
}, [as, hasHrefAndNoChildren, noExpand]);
|
|
195
237
|
const Container = StyledContainer.withComponent(containerTag, {
|
|
196
|
-
target: "
|
|
238
|
+
target: "e134hokc13"
|
|
197
239
|
});
|
|
198
240
|
const ariaExpanded = useMemo(() => {
|
|
199
241
|
if (hasHrefAndNoChildren && internalToggle) {
|
|
@@ -204,6 +246,28 @@ const Item = ({
|
|
|
204
246
|
}
|
|
205
247
|
return undefined;
|
|
206
248
|
}, [hasHrefAndNoChildren, internalToggle]);
|
|
249
|
+
const isPinDisabled = pinnedItems.length >= pinLimit;
|
|
250
|
+
const pinTooltipLocale = useMemo(() => {
|
|
251
|
+
if (isPinDisabled) {
|
|
252
|
+
return locales['navigation.pin.limit'];
|
|
253
|
+
}
|
|
254
|
+
if (isItemPinned) {
|
|
255
|
+
return locales['navigation.unpin.tooltip'];
|
|
256
|
+
}
|
|
257
|
+
return locales['navigation.pin.tooltip'];
|
|
258
|
+
}, [isItemPinned, isPinDisabled, locales]);
|
|
259
|
+
const onDragStartTrigger = event => {
|
|
260
|
+
event.dataTransfer.setData('text/plain', JSON.stringify({
|
|
261
|
+
label,
|
|
262
|
+
index
|
|
263
|
+
}));
|
|
264
|
+
// eslint-disable-next-line no-param-reassign
|
|
265
|
+
event.currentTarget.style.opacity = '0.5';
|
|
266
|
+
};
|
|
267
|
+
const onDragStopTrigger = event => {
|
|
268
|
+
// eslint-disable-next-line no-param-reassign
|
|
269
|
+
event.currentTarget.style.opacity = '1';
|
|
270
|
+
};
|
|
207
271
|
|
|
208
272
|
// This content is when the navigation is expanded
|
|
209
273
|
if (expanded || !expanded && animation === 'expand') {
|
|
@@ -233,6 +297,10 @@ const Item = ({
|
|
|
233
297
|
"data-has-active-children": hasActiveChildren,
|
|
234
298
|
"data-has-no-expand": noExpand,
|
|
235
299
|
disabled: disabled,
|
|
300
|
+
draggable: type === 'pinned' && expanded,
|
|
301
|
+
onDragStart: event => expanded ? onDragStartTrigger(event) : undefined,
|
|
302
|
+
onDragEnd: event => expanded ? onDragStopTrigger(event) : undefined,
|
|
303
|
+
id: id,
|
|
236
304
|
children: [jsxs(Stack, {
|
|
237
305
|
direction: "row",
|
|
238
306
|
gap: 1,
|
|
@@ -246,6 +314,12 @@ const Item = ({
|
|
|
246
314
|
variant: active ? 'primary' : categoryIconVariant,
|
|
247
315
|
disabled: disabled
|
|
248
316
|
})
|
|
317
|
+
}) : null, type === 'pinned' && expanded ? jsx(GrabIcon, {
|
|
318
|
+
name: "drag-vertical",
|
|
319
|
+
sentiment: "neutral",
|
|
320
|
+
prominence: "weak",
|
|
321
|
+
size: "small",
|
|
322
|
+
disabled: disabled
|
|
249
323
|
}) : null, jsxs(Stack, {
|
|
250
324
|
children: [jsx(WrapText, {
|
|
251
325
|
as: "span",
|
|
@@ -278,7 +352,7 @@ const Item = ({
|
|
|
278
352
|
disabled: disabled,
|
|
279
353
|
children: badgeText
|
|
280
354
|
}) : null, shouldShowPinnedButton ? jsx(Tooltip, {
|
|
281
|
-
text: isItemPinned ? locales['navigation.unpin.tooltip'] :
|
|
355
|
+
text: isItemPinned ? locales['navigation.unpin.tooltip'] : pinTooltipLocale,
|
|
282
356
|
placement: "right",
|
|
283
357
|
children: jsx("div", {
|
|
284
358
|
style: {
|
|
@@ -288,8 +362,17 @@ const Item = ({
|
|
|
288
362
|
size: "xsmall",
|
|
289
363
|
variant: "ghost",
|
|
290
364
|
sentiment: active ? 'primary' : 'neutral',
|
|
291
|
-
onClick:
|
|
292
|
-
|
|
365
|
+
onClick: event => {
|
|
366
|
+
if (isItemPinned) {
|
|
367
|
+
unpinItem(id);
|
|
368
|
+
} else {
|
|
369
|
+
pinItem(id);
|
|
370
|
+
}
|
|
371
|
+
event.stopPropagation(); // This is to avoid click spread to the parent and change the routing
|
|
372
|
+
},
|
|
373
|
+
icon: isItemPinned ? 'unpin' : 'pin',
|
|
374
|
+
iconVariant: isItemPinned ? 'filled' : 'outlined',
|
|
375
|
+
disabled: isItemPinned ? false : isPinDisabled
|
|
293
376
|
})
|
|
294
377
|
})
|
|
295
378
|
}) : null]
|
|
@@ -298,21 +381,15 @@ const Item = ({
|
|
|
298
381
|
sentiment: "neutral",
|
|
299
382
|
prominence: "default",
|
|
300
383
|
disabled: disabled
|
|
301
|
-
}) : null, children ?
|
|
384
|
+
}) : null, children ? jsx(Stack, {
|
|
302
385
|
gap: 1,
|
|
303
386
|
direction: "row",
|
|
304
387
|
alignItems: "center",
|
|
305
|
-
children:
|
|
306
|
-
as: "span",
|
|
307
|
-
variant: "caption",
|
|
308
|
-
sentiment: "neutral",
|
|
309
|
-
prominence: "weak",
|
|
310
|
-
children: [pinnedItems.length, "/", pinLimit]
|
|
311
|
-
}) : null, !animation && !noExpand ? jsx(AnimatedIcon, {
|
|
388
|
+
children: !animation && !noExpand ? jsx(AnimatedIcon, {
|
|
312
389
|
name: internalToggle ? 'arrow-down' : 'arrow-right',
|
|
313
390
|
sentiment: "neutral",
|
|
314
391
|
prominence: "weak"
|
|
315
|
-
}) : null
|
|
392
|
+
}) : null
|
|
316
393
|
}) : null]
|
|
317
394
|
})]
|
|
318
395
|
}), children ? jsx(Fragment, {
|
|
@@ -336,7 +413,9 @@ const Item = ({
|
|
|
336
413
|
alignItems: "start",
|
|
337
414
|
justifyContent: "start",
|
|
338
415
|
children: Children.count(children) > 0 ? jsx(StyledMenu, {
|
|
339
|
-
triggerMethod: "
|
|
416
|
+
triggerMethod: "click",
|
|
417
|
+
dynamicDomRendering: false // As we parse the children we don't need dynamic rendering
|
|
418
|
+
,
|
|
340
419
|
disclosure: jsx(Button, {
|
|
341
420
|
sentiment: "neutral",
|
|
342
421
|
variant: hasActiveChildren ? 'filled' : 'ghost',
|
|
@@ -354,11 +433,8 @@ const Item = ({
|
|
|
354
433
|
}) : null
|
|
355
434
|
}),
|
|
356
435
|
placement: "right",
|
|
357
|
-
children: ({
|
|
358
|
-
|
|
359
|
-
}) => Children.map(children, child => /*#__PURE__*/isValidElement(child) ? /*#__PURE__*/cloneElement(child, {
|
|
360
|
-
hasParents: true,
|
|
361
|
-
toggleMenu: toggleMenuLocal
|
|
436
|
+
children: Children.map(children, child => /*#__PURE__*/isValidElement(child) ? /*#__PURE__*/cloneElement(child, {
|
|
437
|
+
hasParents: true
|
|
362
438
|
}) : child)
|
|
363
439
|
}) : jsx(Tooltip, {
|
|
364
440
|
text: label,
|
|
@@ -390,17 +466,19 @@ const Item = ({
|
|
|
390
466
|
href: href,
|
|
391
467
|
onClick: () => {
|
|
392
468
|
onClick?.();
|
|
393
|
-
toggleMenu?.();
|
|
394
469
|
},
|
|
395
470
|
borderless: true,
|
|
396
471
|
active: active,
|
|
472
|
+
disabled: disabled,
|
|
397
473
|
sentiment: active ? 'primary' : 'neutral',
|
|
474
|
+
isPinnable: shouldShowPinnedButton,
|
|
398
475
|
children: jsxs(Stack, {
|
|
399
476
|
gap: 1,
|
|
400
477
|
direction: "row",
|
|
401
478
|
alignItems: "center",
|
|
402
479
|
justifyContent: "space-between",
|
|
403
480
|
flex: 1,
|
|
481
|
+
width: "100%",
|
|
404
482
|
children: [jsx(WrapText, {
|
|
405
483
|
as: "span",
|
|
406
484
|
variant: "bodySmall",
|
|
@@ -417,20 +495,28 @@ const Item = ({
|
|
|
417
495
|
prominence: "weak",
|
|
418
496
|
disabled: disabled
|
|
419
497
|
}) : null, shouldShowPinnedButton ? jsx(Tooltip, {
|
|
420
|
-
text: isItemPinned ? locales['navigation.unpin.tooltip'] :
|
|
498
|
+
text: isItemPinned ? locales['navigation.unpin.tooltip'] : pinTooltipLocale,
|
|
421
499
|
placement: "right",
|
|
422
|
-
children: jsx(
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
sentiment: active ? 'primary' : 'neutral',
|
|
426
|
-
onClick: () => {
|
|
427
|
-
if (isItemPinned) {
|
|
428
|
-
unpinItem(label);
|
|
429
|
-
} else {
|
|
430
|
-
pinItem(label);
|
|
431
|
-
}
|
|
500
|
+
children: jsx("div", {
|
|
501
|
+
style: {
|
|
502
|
+
position: 'relative'
|
|
432
503
|
},
|
|
433
|
-
|
|
504
|
+
children: jsx(CollapsedPinnedButton, {
|
|
505
|
+
size: "xsmall",
|
|
506
|
+
variant: "ghost",
|
|
507
|
+
sentiment: active ? 'primary' : 'neutral',
|
|
508
|
+
onClick: event => {
|
|
509
|
+
if (isItemPinned) {
|
|
510
|
+
unpinItem(id);
|
|
511
|
+
} else {
|
|
512
|
+
pinItem(id);
|
|
513
|
+
}
|
|
514
|
+
event.stopPropagation(); // This is to avoid click spread to the parent and change the routing
|
|
515
|
+
},
|
|
516
|
+
icon: isItemPinned ? 'unpin' : 'pin',
|
|
517
|
+
iconVariant: isItemPinned ? 'filled' : 'outlined',
|
|
518
|
+
disabled: isItemPinned ? false : isPinDisabled
|
|
519
|
+
})
|
|
434
520
|
})
|
|
435
521
|
}) : null]
|
|
436
522
|
})
|
|
@@ -1,8 +1,41 @@
|
|
|
1
|
+
import _styled from '@emotion/styled/base';
|
|
2
|
+
import { useTheme } from '@emotion/react';
|
|
3
|
+
import { Text } from '@ultraviolet/ui';
|
|
4
|
+
import { useCallback } from 'react';
|
|
1
5
|
import { useNavigation } from '../NavigationProvider.js';
|
|
2
6
|
import { Item } from './Item.js';
|
|
3
|
-
import { jsx } from '@emotion/react/jsx-runtime';
|
|
7
|
+
import { jsx, jsxs } from '@emotion/react/jsx-runtime';
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
function _EMOTION_STRINGIFIED_CSS_ERROR__() { return "You have tried to stringify object returned from `css` function. It isn't supposed to be used directly (e.g. as value of the `className` prop), but rather handed to emotion so it can handle it (e.g. as value of `css` prop)."; }
|
|
10
|
+
const DropableArea = /*#__PURE__*/_styled("div", {
|
|
11
|
+
target: "e5ys0ny2"
|
|
12
|
+
})("position:absolute;right:0;left:0;top:0;height:2px;border-top:2px solid;border-color:transparent;padding:", ({
|
|
13
|
+
theme
|
|
14
|
+
}) => theme.space['0.5'], " 0;&::before{content:'';position:absolute;left:0;top:-4px;height:0px;width:0px;border:3px solid;border-color:inherit;border-radius:", ({
|
|
15
|
+
theme
|
|
16
|
+
}) => theme.radii.circle, ";}");
|
|
17
|
+
const RelativeDiv = /*#__PURE__*/_styled("div", {
|
|
18
|
+
target: "e5ys0ny1"
|
|
19
|
+
})(process.env.NODE_ENV === "production" ? {
|
|
20
|
+
name: "bjn8wh",
|
|
21
|
+
styles: "position:relative"
|
|
22
|
+
} : {
|
|
23
|
+
name: "bjn8wh",
|
|
24
|
+
styles: "position:relative",
|
|
25
|
+
toString: _EMOTION_STRINGIFIED_CSS_ERROR__
|
|
26
|
+
});
|
|
27
|
+
const TextContainer = /*#__PURE__*/_styled("div", {
|
|
28
|
+
target: "e5ys0ny0"
|
|
29
|
+
})("padding:", ({
|
|
30
|
+
theme
|
|
31
|
+
}) => theme.space['1'], " 0;padding-left:", ({
|
|
32
|
+
theme
|
|
33
|
+
}) => theme.space['4'], ";margin-left:", ({
|
|
34
|
+
theme
|
|
35
|
+
}) => theme.space['0.5'], ";");
|
|
36
|
+
const PinnedItems = ({
|
|
37
|
+
toggle = true
|
|
38
|
+
}) => {
|
|
6
39
|
const context = useNavigation();
|
|
7
40
|
if (!context) {
|
|
8
41
|
throw new Error('Navigation.PinnedItems can only be used inside a NavigationProvider.');
|
|
@@ -10,18 +43,84 @@ const PinnedItems = () => {
|
|
|
10
43
|
const {
|
|
11
44
|
locales,
|
|
12
45
|
pinnedItems,
|
|
13
|
-
pinnedFeature
|
|
46
|
+
pinnedFeature,
|
|
47
|
+
reorderItems,
|
|
48
|
+
expanded,
|
|
49
|
+
items
|
|
14
50
|
} = context;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
51
|
+
const theme = useTheme();
|
|
52
|
+
const onDrop = useCallback((event, index) => {
|
|
53
|
+
event.preventDefault();
|
|
54
|
+
if (event?.dataTransfer) {
|
|
55
|
+
// eslint-disable-next-line no-param-reassign
|
|
56
|
+
event.currentTarget.style.borderColor = 'transparent';
|
|
57
|
+
const data = JSON.parse(event.dataTransfer.getData('text'));
|
|
58
|
+
if (data.index === index - 1 || index > data.index) {
|
|
59
|
+
reorderItems(data.index, index - 1);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
reorderItems(data.index, index);
|
|
63
|
+
}
|
|
64
|
+
}, [reorderItems]);
|
|
65
|
+
const onDragOver = useCallback(event => {
|
|
66
|
+
event.preventDefault();
|
|
67
|
+
// eslint-disable-next-line no-param-reassign
|
|
68
|
+
event.currentTarget.style.borderColor = theme.colors.primary.border;
|
|
69
|
+
}, [theme.colors.primary.border]);
|
|
70
|
+
const onDragLeave = useCallback(event => {
|
|
71
|
+
event.preventDefault();
|
|
72
|
+
// eslint-disable-next-line no-param-reassign
|
|
73
|
+
event.currentTarget.style.borderColor = 'transparent';
|
|
74
|
+
}, []);
|
|
75
|
+
if (Object.keys(items).length === 0) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
if (pinnedFeature) {
|
|
79
|
+
return jsx("div", {
|
|
80
|
+
children: jsxs(Item, {
|
|
81
|
+
label: locales['navigation.pinned.item.group.label'],
|
|
82
|
+
categoryIcon: "pin",
|
|
83
|
+
categoryIconVariant: "neutral",
|
|
84
|
+
toggle: toggle,
|
|
85
|
+
type: "pinnedGroup",
|
|
86
|
+
id: "pinned-group",
|
|
87
|
+
children: [pinnedItems.length > 0 ? pinnedItems.map((itemId, index) => jsxs("div", {
|
|
88
|
+
style: {
|
|
89
|
+
position: expanded ? 'relative' : undefined
|
|
90
|
+
},
|
|
91
|
+
children: [expanded ? jsx(DropableArea, {
|
|
92
|
+
onDragOver: onDragOver,
|
|
93
|
+
onDragLeave: onDragLeave,
|
|
94
|
+
onDrop: event => onDrop(event, index)
|
|
95
|
+
}) : null, jsx(Item, {
|
|
96
|
+
label: items[itemId]?.label ?? 'Unknown navigation item',
|
|
97
|
+
type: "pinned",
|
|
98
|
+
index: index,
|
|
99
|
+
toggle: toggle,
|
|
100
|
+
id: itemId,
|
|
101
|
+
active: items[itemId]?.active ?? false,
|
|
102
|
+
onClick: items[itemId]?.onClick ?? undefined,
|
|
103
|
+
hasParents: true
|
|
104
|
+
})]
|
|
105
|
+
}, itemId)) : jsx(TextContainer, {
|
|
106
|
+
children: jsx(Text, {
|
|
107
|
+
as: "p",
|
|
108
|
+
variant: "caption",
|
|
109
|
+
prominence: "weak",
|
|
110
|
+
sentiment: "neutral",
|
|
111
|
+
children: locales['navigation.pinned.item.group.empty']
|
|
112
|
+
})
|
|
113
|
+
}), expanded ? jsx(RelativeDiv, {
|
|
114
|
+
style: {
|
|
115
|
+
position: 'relative'
|
|
116
|
+
},
|
|
117
|
+
children: jsx(DropableArea, {
|
|
118
|
+
onDragOver: onDragOver,
|
|
119
|
+
onDragLeave: onDragLeave,
|
|
120
|
+
onDrop: event => onDrop(event, pinnedItems.length)
|
|
121
|
+
})
|
|
122
|
+
}) : null]
|
|
123
|
+
})
|
|
25
124
|
});
|
|
26
125
|
}
|
|
27
126
|
return null;
|
|
@@ -8,7 +8,6 @@ const NAVIGATION_MAX_WIDTH = 320; // in px
|
|
|
8
8
|
/**
|
|
9
9
|
* ANIMATIONS
|
|
10
10
|
*/
|
|
11
|
-
const PIN_BUTTON_OPACITY_TRANSITION = 'opacity 250ms ease-in-out'; // this is the transition animation when hovering pin button
|
|
12
11
|
const ANIMATION_DURATION = 700; // collapse and expand animation duration of the whole nav in ms
|
|
13
12
|
|
|
14
13
|
const shrinkHeight = keyframes`
|
|
@@ -34,4 +33,4 @@ const groupAnimation = keyframes`
|
|
|
34
33
|
}
|
|
35
34
|
`;
|
|
36
35
|
|
|
37
|
-
export { ANIMATION_DURATION, NAVIGATION_COLLASPED_WIDTH, NAVIGATION_MAX_WIDTH, NAVIGATION_MIN_WIDTH, NAVIGATION_WIDTH,
|
|
36
|
+
export { ANIMATION_DURATION, NAVIGATION_COLLASPED_WIDTH, NAVIGATION_MAX_WIDTH, NAVIGATION_MIN_WIDTH, NAVIGATION_WIDTH, groupAnimation, shrinkHeight };
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
var NavigationLocales = {
|
|
2
2
|
'navigation.pin.tooltip': 'Pin product',
|
|
3
3
|
'navigation.unpin.tooltip': 'Unpin product',
|
|
4
|
+
'navigation.pin.limit': 'You cannot pin more products',
|
|
4
5
|
'navigation.pinned.item.group.label': 'Pinned items',
|
|
5
6
|
'navigation.expand.button': 'Expand sidebar',
|
|
6
|
-
'navigation.collapse.button': 'Collapse sidebar'
|
|
7
|
+
'navigation.collapse.button': 'Collapse sidebar',
|
|
8
|
+
'navigation.pinned.item.group.empty': 'You have no pinned items.'
|
|
7
9
|
};
|
|
8
10
|
|
|
9
11
|
export { NavigationLocales as default };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultraviolet/plus",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Ultraviolet Plus",
|
|
5
5
|
"homepage": "https://github.com/scaleway/ultraviolet#readme",
|
|
6
6
|
"repository": {
|
|
@@ -35,18 +35,18 @@
|
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"@emotion/react": "11.11.4",
|
|
37
37
|
"@emotion/styled": "11.11.5",
|
|
38
|
-
"react": "18.
|
|
39
|
-
"react-dom": "18.
|
|
38
|
+
"react": "18.3.1",
|
|
39
|
+
"react-dom": "18.3.1"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@babel/core": "7.24.
|
|
42
|
+
"@babel/core": "7.24.5",
|
|
43
43
|
"@emotion/babel-plugin": "11.11.0",
|
|
44
44
|
"@emotion/react": "11.11.4",
|
|
45
45
|
"@emotion/styled": "11.11.5",
|
|
46
|
-
"@types/react": "18.
|
|
47
|
-
"@types/react-dom": "18.
|
|
48
|
-
"react": "18.
|
|
49
|
-
"react-dom": "18.
|
|
46
|
+
"@types/react": "18.3.1",
|
|
47
|
+
"@types/react-dom": "18.3.0",
|
|
48
|
+
"react": "18.3.1",
|
|
49
|
+
"react-dom": "18.3.1",
|
|
50
50
|
"@ultraviolet/icons": "2.12.5",
|
|
51
51
|
"@ultraviolet/illustrations": "1.7.1"
|
|
52
52
|
},
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"@uiw/react-codemirror": "4.21.25",
|
|
57
57
|
"react-intersection-observer": "9.8.2",
|
|
58
58
|
"@ultraviolet/themes": "1.10.0",
|
|
59
|
-
"@ultraviolet/ui": "1.51.
|
|
59
|
+
"@ultraviolet/ui": "1.51.1"
|
|
60
60
|
},
|
|
61
61
|
"scripts": {
|
|
62
62
|
"build": "rollup -c ../../rollup.config.mjs",
|