@vector-im/compound-web 9.0.1 → 9.2.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/ActivityMarker/Pill.cjs +1 -1
- package/dist/components/ActivityMarker/Unread.cjs +1 -1
- package/dist/components/ActivityMarker/UnreadCounter.cjs +1 -1
- package/dist/components/Alert/Alert.cjs +6 -6
- package/dist/components/Alert/Alert.module.cjs +5 -11
- package/dist/components/Alert/Alert.module.js +5 -11
- package/dist/components/Avatar/Avatar.cjs +2 -2
- package/dist/components/Avatar/Avatar.module.cjs +5 -5
- package/dist/components/Avatar/Avatar.module.cjs.map +1 -1
- package/dist/components/Avatar/Avatar.module.js +5 -5
- package/dist/components/Avatar/Avatar.module.js.map +1 -1
- package/dist/components/Avatar/AvatarStack.cjs +2 -2
- package/dist/components/Badge/Badge.cjs +10 -6
- package/dist/components/Badge/Badge.cjs.map +1 -1
- package/dist/components/Badge/Badge.d.ts +5 -1
- package/dist/components/Badge/Badge.d.ts.map +1 -1
- package/dist/components/Badge/Badge.js +9 -5
- package/dist/components/Badge/Badge.js.map +1 -1
- package/dist/components/Badge/Badge.module.cjs +4 -1
- package/dist/components/Badge/Badge.module.cjs.map +1 -1
- package/dist/components/Badge/Badge.module.js +4 -1
- package/dist/components/Badge/Badge.module.js.map +1 -1
- package/dist/components/Breadcrumb/Breadcrumb.cjs +3 -3
- package/dist/components/Button/Button.cjs +2 -2
- package/dist/components/Button/Button.module.cjs +2 -5
- package/dist/components/Button/Button.module.js +2 -5
- package/dist/components/Button/IconButton/IconButton.cjs +4 -3
- package/dist/components/Button/IconButton/IconButton.cjs.map +1 -1
- package/dist/components/Button/IconButton/IconButton.d.ts +5 -0
- package/dist/components/Button/IconButton/IconButton.d.ts.map +1 -1
- package/dist/components/Button/IconButton/IconButton.js +2 -1
- package/dist/components/Button/IconButton/IconButton.js.map +1 -1
- package/dist/components/Button/IconButton/IconButton.module.cjs +1 -3
- package/dist/components/Button/IconButton/IconButton.module.js +1 -3
- package/dist/components/Button/UnstyledButton.cjs +1 -1
- package/dist/components/ChatFilter/ChatFilter.cjs +1 -1
- package/dist/components/Dropdown/Dropdown.cjs +5 -5
- package/dist/components/Dropdown/Dropdown.module.cjs +7 -7
- package/dist/components/Dropdown/Dropdown.module.cjs.map +1 -1
- package/dist/components/Dropdown/Dropdown.module.js +7 -7
- package/dist/components/Dropdown/Dropdown.module.js.map +1 -1
- package/dist/components/Form/Controls/Action/Action.cjs +2 -2
- package/dist/components/Form/Controls/Checkbox/Checkbox.cjs +3 -3
- package/dist/components/Form/Controls/EditInPlace/EditInPlace.cjs +3 -3
- package/dist/components/Form/Controls/MFA/MFA.cjs +2 -2
- package/dist/components/Form/Controls/MFA/MFA.module.cjs +3 -3
- package/dist/components/Form/Controls/MFA/MFA.module.cjs.map +1 -1
- package/dist/components/Form/Controls/MFA/MFA.module.js +3 -3
- package/dist/components/Form/Controls/MFA/MFA.module.js.map +1 -1
- package/dist/components/Form/Controls/Password/Password.cjs +3 -3
- package/dist/components/Form/Controls/Radio/Radio.cjs +2 -2
- package/dist/components/Form/Controls/SettingsToggle/SettingsToggle.cjs +1 -1
- package/dist/components/Form/Controls/Text/Text.cjs +2 -2
- package/dist/components/Form/Controls/Text/Text.module.cjs +2 -2
- package/dist/components/Form/Controls/Text/Text.module.cjs.map +1 -1
- package/dist/components/Form/Controls/Text/Text.module.js +2 -2
- package/dist/components/Form/Controls/Text/Text.module.js.map +1 -1
- package/dist/components/Form/Controls/Toggle/Toggle.cjs +2 -2
- package/dist/components/Form/Field.cjs +2 -2
- package/dist/components/Form/InlineField.cjs +2 -2
- package/dist/components/Form/Label.cjs +2 -2
- package/dist/components/Form/Message.cjs +4 -4
- package/dist/components/Form/Root.cjs +2 -2
- package/dist/components/Form/Submit.cjs +1 -1
- package/dist/components/Form/form.module.cjs +4 -9
- package/dist/components/Form/form.module.js +4 -9
- package/dist/components/Glass/Glass.cjs +2 -2
- package/dist/components/Icon/BigIcon/BigIcon.cjs +2 -2
- package/dist/components/Icon/IndicatorIcon/IndicatorIcon.cjs +2 -2
- package/dist/components/InlineSpinner/InlineSpinner.cjs +3 -3
- package/dist/components/InlineSpinner/InlineSpinner.module.cjs +2 -2
- package/dist/components/InlineSpinner/InlineSpinner.module.cjs.map +1 -1
- package/dist/components/InlineSpinner/InlineSpinner.module.js +2 -2
- package/dist/components/InlineSpinner/InlineSpinner.module.js.map +1 -1
- package/dist/components/Link/Link.cjs +2 -2
- package/dist/components/Menu/CheckboxMenuItem.cjs +3 -5
- package/dist/components/Menu/CheckboxMenuItem.cjs.map +1 -1
- package/dist/components/Menu/CheckboxMenuItem.js +2 -4
- package/dist/components/Menu/CheckboxMenuItem.js.map +1 -1
- package/dist/components/Menu/ContextMenu.cjs +19 -2
- package/dist/components/Menu/ContextMenu.cjs.map +1 -1
- package/dist/components/Menu/ContextMenu.d.ts.map +1 -1
- package/dist/components/Menu/ContextMenu.js +18 -1
- package/dist/components/Menu/ContextMenu.js.map +1 -1
- package/dist/components/Menu/DrawerMenu.cjs +2 -2
- package/dist/components/Menu/FloatingMenu.cjs +2 -2
- package/dist/components/Menu/FloatingMenu.module.cjs +5 -8
- package/dist/components/Menu/FloatingMenu.module.cjs.map +1 -1
- package/dist/components/Menu/FloatingMenu.module.js +5 -8
- package/dist/components/Menu/FloatingMenu.module.js.map +1 -1
- package/dist/components/Menu/Menu.cjs +30 -2
- package/dist/components/Menu/Menu.cjs.map +1 -1
- package/dist/components/Menu/Menu.d.ts.map +1 -1
- package/dist/components/Menu/Menu.js +30 -2
- package/dist/components/Menu/Menu.js.map +1 -1
- package/dist/components/Menu/MenuContext.cjs.map +1 -1
- package/dist/components/Menu/MenuContext.d.ts +22 -0
- package/dist/components/Menu/MenuContext.d.ts.map +1 -1
- package/dist/components/Menu/MenuContext.js.map +1 -1
- package/dist/components/Menu/MenuItem.cjs +3 -3
- package/dist/components/Menu/MenuItem.module.cjs +8 -14
- package/dist/components/Menu/MenuItem.module.cjs.map +1 -1
- package/dist/components/Menu/MenuItem.module.js +8 -14
- package/dist/components/Menu/MenuItem.module.js.map +1 -1
- package/dist/components/Menu/MenuTitle.cjs +2 -2
- package/dist/components/Menu/RadioMenuItem.cjs +3 -5
- package/dist/components/Menu/RadioMenuItem.cjs.map +1 -1
- package/dist/components/Menu/RadioMenuItem.js +2 -4
- package/dist/components/Menu/RadioMenuItem.js.map +1 -1
- package/dist/components/Menu/SubMenu.cjs +24 -0
- package/dist/components/Menu/SubMenu.cjs.map +1 -0
- package/dist/components/Menu/SubMenu.d.ts +26 -0
- package/dist/components/Menu/SubMenu.d.ts.map +1 -0
- package/dist/components/Menu/SubMenu.js +22 -0
- package/dist/components/Menu/SubMenu.js.map +1 -0
- package/dist/components/Menu/ToggleMenuItem.cjs +3 -5
- package/dist/components/Menu/ToggleMenuItem.cjs.map +1 -1
- package/dist/components/Menu/ToggleMenuItem.js +2 -4
- package/dist/components/Menu/ToggleMenuItem.js.map +1 -1
- package/dist/components/Nav/Nav.module.cjs +4 -4
- package/dist/components/Nav/Nav.module.cjs.map +1 -1
- package/dist/components/Nav/Nav.module.js +4 -4
- package/dist/components/Nav/Nav.module.js.map +1 -1
- package/dist/components/Nav/NavBar.cjs +2 -2
- package/dist/components/Nav/NavItem.cjs +1 -1
- package/dist/components/PageHeader/PageHeader.cjs +36 -0
- package/dist/components/PageHeader/PageHeader.cjs.map +1 -0
- package/dist/components/PageHeader/PageHeader.js +33 -0
- package/dist/components/PageHeader/PageHeader.js.map +1 -0
- package/dist/components/PageHeader/PageHeader.module.cjs +8 -0
- package/dist/components/PageHeader/PageHeader.module.cjs.map +1 -0
- package/dist/components/PageHeader/PageHeader.module.js +8 -0
- package/dist/components/PageHeader/PageHeader.module.js.map +1 -0
- package/dist/components/Progress/Progress.cjs +2 -2
- package/dist/components/Progress/Progress.module.cjs +4 -4
- package/dist/components/Progress/Progress.module.cjs.map +1 -1
- package/dist/components/Progress/Progress.module.js +4 -4
- package/dist/components/Progress/Progress.module.js.map +1 -1
- package/dist/components/ReleaseAnnouncement/ReleaseAnnouncement.cjs +1 -1
- package/dist/components/Search/Search.cjs +3 -3
- package/dist/components/Search/Search.module.cjs +3 -3
- package/dist/components/Search/Search.module.cjs.map +1 -1
- package/dist/components/Search/Search.module.js +3 -3
- package/dist/components/Search/Search.module.js.map +1 -1
- package/dist/components/Separator/Separator.cjs +3 -3
- package/dist/components/Toast/Toast.cjs +31 -6
- package/dist/components/Toast/Toast.cjs.map +1 -1
- package/dist/components/Toast/Toast.d.ts +13 -1
- package/dist/components/Toast/Toast.d.ts.map +1 -1
- package/dist/components/Toast/Toast.js +29 -5
- package/dist/components/Toast/Toast.js.map +1 -1
- package/dist/components/Toast/Toast.module.cjs +7 -2
- package/dist/components/Toast/Toast.module.cjs.map +1 -1
- package/dist/components/Toast/Toast.module.js +7 -2
- package/dist/components/Toast/Toast.module.js.map +1 -1
- package/dist/components/Tooltip/Tooltip.cjs +2 -2
- package/dist/components/Tooltip/TooltipProvider.cjs +1 -1
- package/dist/components/Typography/Body.cjs +1 -1
- package/dist/components/Typography/Heading.cjs +1 -1
- package/dist/components/Typography/Text.cjs +1 -1
- package/dist/components/Typography/Typography.cjs +2 -2
- package/dist/components/Typography/Typography.module.cjs +1 -3
- package/dist/components/Typography/Typography.module.js +1 -3
- package/dist/components/VisualList/VisualList.cjs +2 -2
- package/dist/components/VisualList/VisualListItem.cjs +2 -2
- package/dist/index.cjs +4 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/style.css +288 -145
- package/package.json +3 -3
- package/src/components/Avatar/Avatar.module.css +1 -1
- package/src/components/Badge/Badge.module.css +44 -11
- package/src/components/Badge/Badge.tsx +10 -2
- package/src/components/Button/IconButton/IconButton.tsx +12 -1
- package/src/components/Dropdown/Dropdown.module.css +3 -1
- package/src/components/Form/Controls/MFA/MFA.module.css +1 -0
- package/src/components/Form/Controls/Text/Text.module.css +1 -0
- package/src/components/InlineSpinner/InlineSpinner.module.css +4 -1
- package/src/components/Menu/ContextMenu.tsx +24 -0
- package/src/components/Menu/FloatingMenu.module.css +2 -0
- package/src/components/Menu/Menu.tsx +56 -1
- package/src/components/Menu/MenuContext.tsx +23 -0
- package/src/components/Menu/MenuItem.module.css +27 -5
- package/src/components/Menu/SubMenu.tsx +62 -0
- package/src/components/Nav/Nav.module.css +4 -1
- package/src/components/Progress/Progress.module.css +5 -1
- package/src/components/Search/Search.module.css +1 -0
- package/src/components/Toast/Toast.module.css +32 -2
- package/src/components/Toast/Toast.tsx +68 -6
- package/src/index.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vector-im/compound-web",
|
|
3
|
-
"version": "9.0
|
|
3
|
+
"version": "9.2.0",
|
|
4
4
|
"description": "Compound components for the Web",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"homepage": "https://github.com/vector-im/compound-web#readme",
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@element-hq/element-web-playwright-common": "^
|
|
54
|
+
"@element-hq/element-web-playwright-common": "^3.0.0",
|
|
55
55
|
"@fontsource/inconsolata": "^5.0.8",
|
|
56
56
|
"@fontsource/inter": "^5.0.8",
|
|
57
57
|
"@playwright/test": "^1.41.1",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"eslint-plugin-react": "^7.33.2",
|
|
84
84
|
"eslint-plugin-storybook": "^10.0.0",
|
|
85
85
|
"jsdom": "^29.0.0",
|
|
86
|
-
"prettier": "3.8.
|
|
86
|
+
"prettier": "3.8.2",
|
|
87
87
|
"react": "^19.1.0",
|
|
88
88
|
"react-dom": "^19.1.0",
|
|
89
89
|
"resize-observer-polyfill": "^1.5.1",
|
|
@@ -18,7 +18,7 @@ Please see LICENSE files in the repository root for full details.
|
|
|
18
18
|
font-family: var(--cpd-font-family-sans);
|
|
19
19
|
font-weight: bold;
|
|
20
20
|
overflow: hidden;
|
|
21
|
-
user-select: none;
|
|
21
|
+
user-select: none; /* stylelint-disable-line defensive-css/no-user-select-none */
|
|
22
22
|
|
|
23
23
|
/* Set a background color to help with visual consistency when displaying
|
|
24
24
|
* avatars with a translucent background */
|
|
@@ -11,37 +11,70 @@ Please see LICENSE files in the repository root for full details.
|
|
|
11
11
|
align-items: center;
|
|
12
12
|
border-radius: 9999px; /* pill effect */
|
|
13
13
|
padding: var(--cpd-space-1x) var(--cpd-space-3x);
|
|
14
|
+
box-sizing: border-box;
|
|
15
|
+
min-block-size: 28px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.has-icon {
|
|
19
|
+
padding-inline-start: var(--cpd-space-2x);
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
.badge[data-kind="default"] {
|
|
17
|
-
border: 1px solid var(--cpd-color-
|
|
23
|
+
border: 1px solid var(--cpd-color-border-interactive-secondary);
|
|
24
|
+
|
|
25
|
+
/* To keep the same height than the other badges despite the border */
|
|
26
|
+
padding-block: calc(var(--cpd-space-1x) - 1px);
|
|
18
27
|
outline: none;
|
|
19
|
-
color: var(--cpd-color-
|
|
28
|
+
color: var(--cpd-color-text-primary);
|
|
29
|
+
|
|
30
|
+
svg {
|
|
31
|
+
color: var(--cpd-color-icon-primary);
|
|
32
|
+
}
|
|
20
33
|
}
|
|
21
34
|
|
|
22
35
|
.badge[data-kind="grey"] {
|
|
23
|
-
background: var(--cpd-color-
|
|
24
|
-
color: var(--cpd-color-
|
|
36
|
+
background: var(--cpd-color-bg-badge-secondary);
|
|
37
|
+
color: var(--cpd-color-text-primary);
|
|
38
|
+
|
|
39
|
+
svg {
|
|
40
|
+
color: var(--cpd-color-icon-primary);
|
|
41
|
+
}
|
|
25
42
|
}
|
|
26
43
|
|
|
27
44
|
.badge[data-kind="on-solid"] {
|
|
28
|
-
background: var(--cpd-color-
|
|
45
|
+
background: var(--cpd-color-bg-badge-primary);
|
|
29
46
|
color: var(--cpd-color-text-on-solid-primary);
|
|
47
|
+
|
|
48
|
+
svg {
|
|
49
|
+
color: var(--cpd-color-icon-on-solid-primary);
|
|
50
|
+
}
|
|
30
51
|
}
|
|
31
52
|
|
|
32
53
|
.badge[data-kind="blue"] {
|
|
33
|
-
background: var(--cpd-color-
|
|
34
|
-
color: var(--cpd-color-
|
|
54
|
+
background: var(--cpd-color-bg-badge-info);
|
|
55
|
+
color: var(--cpd-color-text-badge-info);
|
|
56
|
+
|
|
57
|
+
svg {
|
|
58
|
+
color: var(--cpd-color-icon-info-primary);
|
|
59
|
+
}
|
|
35
60
|
}
|
|
36
61
|
|
|
37
62
|
.badge[data-kind="green"] {
|
|
38
|
-
background: var(--cpd-color-
|
|
39
|
-
color: var(--cpd-color-
|
|
63
|
+
background: var(--cpd-color-bg-badge-accent);
|
|
64
|
+
color: var(--cpd-color-text-badge-accent);
|
|
65
|
+
|
|
66
|
+
svg {
|
|
67
|
+
color: var(--cpd-color-icon-accent-primary);
|
|
68
|
+
}
|
|
40
69
|
}
|
|
41
70
|
|
|
42
71
|
.badge[data-kind="red"] {
|
|
43
|
-
background: var(--cpd-color-
|
|
44
|
-
color: var(--cpd-color-
|
|
72
|
+
background: var(--cpd-color-bg-badge-critical);
|
|
73
|
+
color: var(--cpd-color-text-critical-primary);
|
|
74
|
+
|
|
75
|
+
svg {
|
|
76
|
+
color: var(--cpd-color-icon-critical-primary);
|
|
77
|
+
}
|
|
45
78
|
}
|
|
46
79
|
|
|
47
80
|
@media (forced-colors: active) {
|
|
@@ -6,7 +6,7 @@ Please see LICENSE files in the repository root for full details.
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import classnames from "classnames";
|
|
9
|
-
import React, { type PropsWithChildren } from "react";
|
|
9
|
+
import React, { type ComponentType, type PropsWithChildren } from "react";
|
|
10
10
|
import styles from "./Badge.module.css";
|
|
11
11
|
import { Typography } from "../Typography/Typography";
|
|
12
12
|
|
|
@@ -19,6 +19,10 @@ type BadgeProps = {
|
|
|
19
19
|
* The type of badge.
|
|
20
20
|
*/
|
|
21
21
|
kind?: "default" | "grey" | "on-solid" | "blue" | "green" | "red";
|
|
22
|
+
/**
|
|
23
|
+
* An icon to display within the badge.
|
|
24
|
+
*/
|
|
25
|
+
Icon?: ComponentType<React.SVGAttributes<SVGElement>>;
|
|
22
26
|
};
|
|
23
27
|
|
|
24
28
|
/**
|
|
@@ -26,10 +30,13 @@ type BadgeProps = {
|
|
|
26
30
|
*/
|
|
27
31
|
export const Badge: React.FC<PropsWithChildren<BadgeProps>> = ({
|
|
28
32
|
children,
|
|
33
|
+
Icon,
|
|
29
34
|
kind = "default",
|
|
30
35
|
className,
|
|
31
36
|
}) => {
|
|
32
|
-
const classes = classnames(styles.badge, className
|
|
37
|
+
const classes = classnames(styles.badge, className, {
|
|
38
|
+
[styles["has-icon"]]: !!Icon,
|
|
39
|
+
});
|
|
33
40
|
return (
|
|
34
41
|
<Typography
|
|
35
42
|
as="span"
|
|
@@ -38,6 +45,7 @@ export const Badge: React.FC<PropsWithChildren<BadgeProps>> = ({
|
|
|
38
45
|
className={classes}
|
|
39
46
|
data-kind={kind}
|
|
40
47
|
>
|
|
48
|
+
{Icon && <Icon width="16" height="16" aria-hidden={true} />}
|
|
41
49
|
{children}
|
|
42
50
|
</Typography>
|
|
43
51
|
);
|
|
@@ -47,6 +47,10 @@ type IconButtonProps = UnstyledButtonPropsFor<"button"> & {
|
|
|
47
47
|
* Optional tooltip for the button
|
|
48
48
|
*/
|
|
49
49
|
tooltip?: string;
|
|
50
|
+
/**
|
|
51
|
+
* The placement of the tooltip, if `tooltip` is provided.
|
|
52
|
+
*/
|
|
53
|
+
tooltipPlacement?: React.ComponentProps<typeof Tooltip>["placement"];
|
|
50
54
|
/**
|
|
51
55
|
* Hide the background when the button is not active or hovered.
|
|
52
56
|
* @default false
|
|
@@ -71,6 +75,7 @@ export const IconButton = forwardRef<
|
|
|
71
75
|
disabled,
|
|
72
76
|
destructive,
|
|
73
77
|
tooltip,
|
|
78
|
+
tooltipPlacement,
|
|
74
79
|
noBackground = false,
|
|
75
80
|
...props
|
|
76
81
|
},
|
|
@@ -106,5 +111,11 @@ export const IconButton = forwardRef<
|
|
|
106
111
|
</UnstyledButton>
|
|
107
112
|
);
|
|
108
113
|
|
|
109
|
-
return tooltip ?
|
|
114
|
+
return tooltip ? (
|
|
115
|
+
<Tooltip label={tooltip} placement={tooltipPlacement}>
|
|
116
|
+
{button}
|
|
117
|
+
</Tooltip>
|
|
118
|
+
) : (
|
|
119
|
+
button
|
|
120
|
+
);
|
|
110
121
|
});
|
|
@@ -90,6 +90,7 @@ Please see LICENSE files in the repository root for full details.
|
|
|
90
90
|
border-color: var(--cpd-color-text-critical-primary);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
/* stylelint-disable-next-line defensive-css/require-focus-visible */
|
|
93
94
|
.control:focus ~ .digit:not([data-filled]) {
|
|
94
95
|
outline: 2px solid var(--cpd-color-border-focused);
|
|
95
96
|
border-color: transparent;
|
|
@@ -35,6 +35,7 @@ Please see LICENSE files in the repository root for full details.
|
|
|
35
35
|
border-color: var(--cpd-color-border-interactive-hovered);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/* stylelint-disable-next-line defensive-css/require-focus-visible */
|
|
38
39
|
.control:focus {
|
|
39
40
|
outline: 2px solid var(--cpd-color-border-focused);
|
|
40
41
|
border-color: transparent;
|
|
@@ -22,5 +22,8 @@ Please see LICENSE files in the repository root for full details.
|
|
|
22
22
|
align-items: center;
|
|
23
23
|
inline-size: 100%;
|
|
24
24
|
block-size: 100%;
|
|
25
|
-
|
|
25
|
+
|
|
26
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
27
|
+
animation: 1s linear spin infinite;
|
|
28
|
+
}
|
|
26
29
|
}
|
|
@@ -18,6 +18,10 @@ import {
|
|
|
18
18
|
Portal,
|
|
19
19
|
Content,
|
|
20
20
|
ContextMenuItem,
|
|
21
|
+
ContextMenuSub,
|
|
22
|
+
ContextMenuSubTrigger,
|
|
23
|
+
ContextMenuSubContent,
|
|
24
|
+
ContextMenuPortal,
|
|
21
25
|
} from "@radix-ui/react-context-menu";
|
|
22
26
|
import { FloatingMenu } from "./FloatingMenu";
|
|
23
27
|
import { Drawer } from "vaul";
|
|
@@ -27,6 +31,7 @@ import {
|
|
|
27
31
|
MenuContext,
|
|
28
32
|
type MenuData,
|
|
29
33
|
type MenuItemWrapperProps,
|
|
34
|
+
type SubMenuWrapperProps,
|
|
30
35
|
} from "./MenuContext";
|
|
31
36
|
import { DrawerMenu } from "./DrawerMenu";
|
|
32
37
|
import { getPlatform } from "../../utils/platform";
|
|
@@ -73,6 +78,24 @@ const ContextMenuItemWrapper: FC<MenuItemWrapperProps> = ({
|
|
|
73
78
|
</ContextMenuItem>
|
|
74
79
|
);
|
|
75
80
|
|
|
81
|
+
const ContextSubMenuWrapper: FC<SubMenuWrapperProps> = ({
|
|
82
|
+
trigger,
|
|
83
|
+
children,
|
|
84
|
+
open,
|
|
85
|
+
onOpenChange,
|
|
86
|
+
}) => (
|
|
87
|
+
<ContextMenuSub open={open} onOpenChange={onOpenChange}>
|
|
88
|
+
<ContextMenuSubTrigger asChild>{trigger}</ContextMenuSubTrigger>
|
|
89
|
+
<ContextMenuPortal>
|
|
90
|
+
<ContextMenuSubContent asChild alignOffset={-20}>
|
|
91
|
+
<FloatingMenu title="" showTitle={false}>
|
|
92
|
+
{children}
|
|
93
|
+
</FloatingMenu>
|
|
94
|
+
</ContextMenuSubContent>
|
|
95
|
+
</ContextMenuPortal>
|
|
96
|
+
</ContextMenuSub>
|
|
97
|
+
);
|
|
98
|
+
|
|
76
99
|
/**
|
|
77
100
|
* A menu opened by right-clicking or long-pressing another UI element.
|
|
78
101
|
*/
|
|
@@ -100,6 +123,7 @@ export const ContextMenu: FC<Props> = ({
|
|
|
100
123
|
const context: MenuData = useMemo(
|
|
101
124
|
() => ({
|
|
102
125
|
MenuItemWrapper: drawer ? null : ContextMenuItemWrapper,
|
|
126
|
+
SubMenuWrapper: drawer ? null : ContextSubMenuWrapper,
|
|
103
127
|
onOpenChange,
|
|
104
128
|
}),
|
|
105
129
|
[onOpenChange],
|
|
@@ -35,6 +35,8 @@ Please see LICENSE files in the repository root for full details.
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
.menu[data-state="open"] {
|
|
38
|
+
/* Disable linter, we have a reduced motion style below */
|
|
39
|
+
/* stylelint-disable-next-line defensive-css/require-prefers-reduced-motion */
|
|
38
40
|
animation: slide-in 180ms;
|
|
39
41
|
}
|
|
40
42
|
|
|
@@ -5,13 +5,23 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
|
5
5
|
Please see LICENSE files in the repository root for full details.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import React, {
|
|
8
|
+
import React, {
|
|
9
|
+
type FC,
|
|
10
|
+
type ReactNode,
|
|
11
|
+
useMemo,
|
|
12
|
+
useEffect,
|
|
13
|
+
useState,
|
|
14
|
+
} from "react";
|
|
9
15
|
import {
|
|
10
16
|
Root,
|
|
11
17
|
Trigger,
|
|
12
18
|
Portal,
|
|
13
19
|
Content,
|
|
14
20
|
DropdownMenuItem,
|
|
21
|
+
DropdownMenuSub,
|
|
22
|
+
DropdownMenuSubTrigger,
|
|
23
|
+
DropdownMenuSubContent,
|
|
24
|
+
DropdownMenuPortal,
|
|
15
25
|
} from "@radix-ui/react-dropdown-menu";
|
|
16
26
|
import { FloatingMenu } from "./FloatingMenu";
|
|
17
27
|
import { Drawer } from "vaul";
|
|
@@ -21,6 +31,7 @@ import {
|
|
|
21
31
|
MenuContext,
|
|
22
32
|
type MenuData,
|
|
23
33
|
type MenuItemWrapperProps,
|
|
34
|
+
type SubMenuWrapperProps,
|
|
24
35
|
} from "./MenuContext";
|
|
25
36
|
import { DrawerMenu } from "./DrawerMenu";
|
|
26
37
|
import { getPlatform } from "../../utils/platform";
|
|
@@ -84,6 +95,49 @@ const DropdownMenuItemWrapper: FC<MenuItemWrapperProps> = ({
|
|
|
84
95
|
</DropdownMenuItem>
|
|
85
96
|
);
|
|
86
97
|
|
|
98
|
+
/** Duration of the parent menu's slide-in animation (ms). */
|
|
99
|
+
const MENU_ANIMATION_DURATION = 180;
|
|
100
|
+
|
|
101
|
+
const DropdownSubMenuWrapper: FC<SubMenuWrapperProps> = ({
|
|
102
|
+
trigger,
|
|
103
|
+
children,
|
|
104
|
+
open: openProp,
|
|
105
|
+
onOpenChange,
|
|
106
|
+
}) => {
|
|
107
|
+
// When the submenu is programmatically opened at the same time as the parent
|
|
108
|
+
// menu (e.g. open={true} on mount), the parent is still mid-animation and
|
|
109
|
+
// the trigger position hasn't settled. Defer the open so the submenu
|
|
110
|
+
// positions correctly after the parent animation completes.
|
|
111
|
+
const [deferredOpen, setDeferredOpen] = useState(false);
|
|
112
|
+
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
if (openProp) {
|
|
115
|
+
const timer = setTimeout(
|
|
116
|
+
() => setDeferredOpen(true),
|
|
117
|
+
MENU_ANIMATION_DURATION,
|
|
118
|
+
);
|
|
119
|
+
return () => clearTimeout(timer);
|
|
120
|
+
} else {
|
|
121
|
+
setDeferredOpen(false);
|
|
122
|
+
}
|
|
123
|
+
}, [openProp]);
|
|
124
|
+
|
|
125
|
+
const open = openProp ? deferredOpen : openProp;
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<DropdownMenuSub open={open} onOpenChange={onOpenChange}>
|
|
129
|
+
<DropdownMenuSubTrigger asChild>{trigger}</DropdownMenuSubTrigger>
|
|
130
|
+
<DropdownMenuPortal>
|
|
131
|
+
<DropdownMenuSubContent asChild alignOffset={-20}>
|
|
132
|
+
<FloatingMenu title="" showTitle={false}>
|
|
133
|
+
{children}
|
|
134
|
+
</FloatingMenu>
|
|
135
|
+
</DropdownMenuSubContent>
|
|
136
|
+
</DropdownMenuPortal>
|
|
137
|
+
</DropdownMenuSub>
|
|
138
|
+
);
|
|
139
|
+
};
|
|
140
|
+
|
|
87
141
|
/**
|
|
88
142
|
* A menu opened by pressing a button.
|
|
89
143
|
*/
|
|
@@ -105,6 +159,7 @@ export const Menu: FC<Props> = ({
|
|
|
105
159
|
const context: MenuData = useMemo(
|
|
106
160
|
() => ({
|
|
107
161
|
MenuItemWrapper: drawer ? null : DropdownMenuItemWrapper,
|
|
162
|
+
SubMenuWrapper: drawer ? null : DropdownSubMenuWrapper,
|
|
108
163
|
onOpenChange,
|
|
109
164
|
}),
|
|
110
165
|
[onOpenChange],
|
|
@@ -17,11 +17,34 @@ export interface MenuItemWrapperProps {
|
|
|
17
17
|
children: ReactNode;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
export interface SubMenuWrapperProps {
|
|
21
|
+
/**
|
|
22
|
+
* The trigger element that opens the submenu (typically a MenuItem).
|
|
23
|
+
*/
|
|
24
|
+
trigger: ReactNode;
|
|
25
|
+
/**
|
|
26
|
+
* The submenu contents.
|
|
27
|
+
*/
|
|
28
|
+
children: ReactNode;
|
|
29
|
+
/**
|
|
30
|
+
* Whether the submenu is open (controlled).
|
|
31
|
+
*/
|
|
32
|
+
open?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Event handler called when the open state of the submenu changes.
|
|
35
|
+
*/
|
|
36
|
+
onOpenChange?: (open: boolean) => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
20
39
|
export interface MenuData {
|
|
21
40
|
/**
|
|
22
41
|
* A component that wraps interactive menu items.
|
|
23
42
|
*/
|
|
24
43
|
MenuItemWrapper: ComponentType<MenuItemWrapperProps> | null;
|
|
44
|
+
/**
|
|
45
|
+
* A component that wraps submenus.
|
|
46
|
+
*/
|
|
47
|
+
SubMenuWrapper: ComponentType<SubMenuWrapperProps> | null;
|
|
25
48
|
/**
|
|
26
49
|
* Event handler called when the open state of the menu changes.
|
|
27
50
|
*/
|
|
@@ -23,7 +23,8 @@ Please see LICENSE files in the repository root for full details.
|
|
|
23
23
|
background: var(--cpd-color-bg-action-secondary-rest);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
.item.interactive
|
|
26
|
+
.item.interactive,
|
|
27
|
+
.item[data-state] {
|
|
27
28
|
cursor: pointer;
|
|
28
29
|
}
|
|
29
30
|
|
|
@@ -88,12 +89,31 @@ button.item {
|
|
|
88
89
|
color: var(--cpd-color-icon-critical-primary);
|
|
89
90
|
}
|
|
90
91
|
|
|
92
|
+
/* Submenu triggers: always show the chevron and apply hover style when open */
|
|
93
|
+
.item[data-state] > .nav-hint {
|
|
94
|
+
display: initial;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.item[data-state] > .nav-hint ~ * {
|
|
98
|
+
display: none;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.item[data-state="open"][data-kind="primary"] {
|
|
102
|
+
background: var(--cpd-color-bg-action-secondary-hovered);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.item[data-state="open"][data-kind="critical"] {
|
|
106
|
+
background: var(--cpd-color-bg-critical-subtle);
|
|
107
|
+
}
|
|
108
|
+
|
|
91
109
|
@media (hover) {
|
|
92
|
-
.item.interactive[data-kind="primary"]:hover
|
|
110
|
+
.item.interactive[data-kind="primary"]:hover,
|
|
111
|
+
.item[data-state][data-kind="primary"]:hover {
|
|
93
112
|
background: var(--cpd-color-bg-action-secondary-hovered);
|
|
94
113
|
}
|
|
95
114
|
|
|
96
|
-
.item.interactive[data-kind="critical"]:hover
|
|
115
|
+
.item.interactive[data-kind="critical"]:hover,
|
|
116
|
+
.item[data-state][data-kind="critical"]:hover {
|
|
97
117
|
background: var(--cpd-color-bg-critical-subtle);
|
|
98
118
|
}
|
|
99
119
|
|
|
@@ -107,11 +127,13 @@ button.item {
|
|
|
107
127
|
}
|
|
108
128
|
}
|
|
109
129
|
|
|
110
|
-
.item.interactive[data-kind="primary"]:active
|
|
130
|
+
.item.interactive[data-kind="primary"]:active,
|
|
131
|
+
.item[data-state][data-kind="primary"]:active {
|
|
111
132
|
background: var(--cpd-color-bg-action-secondary-pressed);
|
|
112
133
|
}
|
|
113
134
|
|
|
114
|
-
.item.interactive[data-kind="critical"]:active
|
|
135
|
+
.item.interactive[data-kind="critical"]:active,
|
|
136
|
+
.item[data-state][data-kind="critical"]:active {
|
|
115
137
|
background: var(--cpd-color-bg-critical-subtle-hovered);
|
|
116
138
|
}
|
|
117
139
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 Element Creations Ltd.
|
|
3
|
+
|
|
4
|
+
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
5
|
+
Please see LICENSE files in the repository root for full details.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React, { type FC, type ReactNode, useContext } from "react";
|
|
9
|
+
import { MenuContext } from "./MenuContext";
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
/**
|
|
13
|
+
* The trigger element that opens the submenu. This should be a MenuItem.
|
|
14
|
+
*/
|
|
15
|
+
trigger: ReactNode;
|
|
16
|
+
/**
|
|
17
|
+
* Whether the submenu is open (controlled).
|
|
18
|
+
*/
|
|
19
|
+
open?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Event handler called when the open state of the submenu changes.
|
|
22
|
+
*/
|
|
23
|
+
onOpenChange?: (open: boolean) => void;
|
|
24
|
+
/**
|
|
25
|
+
* The submenu contents (typically MenuItem elements).
|
|
26
|
+
*/
|
|
27
|
+
children: ReactNode;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A submenu within a Menu or ContextMenu. The trigger should be a MenuItem
|
|
32
|
+
* component and the children are the submenu items.
|
|
33
|
+
*/
|
|
34
|
+
export const SubMenu: FC<Props> = ({
|
|
35
|
+
trigger,
|
|
36
|
+
open,
|
|
37
|
+
onOpenChange,
|
|
38
|
+
children,
|
|
39
|
+
}) => {
|
|
40
|
+
const context = useContext(MenuContext);
|
|
41
|
+
|
|
42
|
+
// When there's no SubMenuWrapper (e.g. drawer on mobile), flatten the
|
|
43
|
+
// submenu items inline — nested flyouts don't work well in drawers.
|
|
44
|
+
if (context?.SubMenuWrapper == null) {
|
|
45
|
+
return (
|
|
46
|
+
<>
|
|
47
|
+
{trigger}
|
|
48
|
+
{children}
|
|
49
|
+
</>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<context.SubMenuWrapper
|
|
55
|
+
trigger={trigger}
|
|
56
|
+
open={open}
|
|
57
|
+
onOpenChange={onOpenChange}
|
|
58
|
+
>
|
|
59
|
+
{children}
|
|
60
|
+
</context.SubMenuWrapper>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
@@ -36,7 +36,10 @@
|
|
|
36
36
|
block-size: 0;
|
|
37
37
|
border-radius: var(--cpd-radius-pill-effect) var(--cpd-radius-pill-effect) 0 0;
|
|
38
38
|
background-color: var(--cpd-color-bg-action-primary-rest);
|
|
39
|
-
|
|
39
|
+
|
|
40
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
41
|
+
transition: height 0.1s ease-in-out;
|
|
42
|
+
}
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
.nav-tab[data-current]::before {
|
|
@@ -59,7 +59,11 @@ Please see LICENSE files in the repository root for full details.
|
|
|
59
59
|
.progress-bar-indicator {
|
|
60
60
|
position: absolute;
|
|
61
61
|
inset: 0;
|
|
62
|
-
|
|
62
|
+
|
|
63
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
64
|
+
transition: transform 0.2s ease-in-out;
|
|
65
|
+
}
|
|
66
|
+
|
|
63
67
|
background-image: linear-gradient(
|
|
64
68
|
135deg,
|
|
65
69
|
var(--cpd-progress-bar-muted) 0%,
|
|
@@ -61,6 +61,7 @@ Please see LICENSE files in the repository root for full details.
|
|
|
61
61
|
color: var(--cpd-color-text-secondary);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/* stylelint-disable-next-line defensive-css/require-focus-visible */
|
|
64
65
|
.input:focus::placeholder {
|
|
65
66
|
color: var(--cpd-color-text-secondary);
|
|
66
67
|
}
|
|
@@ -7,11 +7,14 @@ Please see LICENSE files in the repository root for full details.
|
|
|
7
7
|
|
|
8
8
|
.toast-container {
|
|
9
9
|
inline-size: fit-content;
|
|
10
|
-
background-color: var(--cpd-color-
|
|
10
|
+
background-color: var(--cpd-color-bg-action-primary-rest);
|
|
11
11
|
color: var(--cpd-color-text-on-solid-primary);
|
|
12
12
|
border-radius: 99px;
|
|
13
|
-
font-size: var(--cpd-font-body-sm-medium);
|
|
14
13
|
padding: var(--cpd-space-2x) var(--cpd-space-4x);
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-wrap: nowrap;
|
|
16
|
+
align-items: center;
|
|
17
|
+
gap: var(--cpd-space-2x);
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
@media (forced-colors: active) {
|
|
@@ -19,3 +22,30 @@ Please see LICENSE files in the repository root for full details.
|
|
|
19
22
|
outline: 1px solid transparent;
|
|
20
23
|
}
|
|
21
24
|
}
|
|
25
|
+
|
|
26
|
+
.icon {
|
|
27
|
+
flex-shrink: 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.has-close {
|
|
31
|
+
gap: var(--cpd-space-3x);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.content {
|
|
35
|
+
display: flex;
|
|
36
|
+
flex-wrap: nowrap;
|
|
37
|
+
align-items: flex-start;
|
|
38
|
+
gap: var(--cpd-space-2x);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.close {
|
|
42
|
+
align-self: flex-start;
|
|
43
|
+
padding: var(--cpd-space-0-5x) !important;
|
|
44
|
+
|
|
45
|
+
&:not(:hover, :focus-visible) {
|
|
46
|
+
* {
|
|
47
|
+
/* Override default color of icon button. The container background is different than the default canvas color */
|
|
48
|
+
color: var(--cpd-color-icon-on-solid-primary) !important;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|