Skip to content

Commit 4baa099

Browse files
committed
feat(actionmenu): migration to S2
1 parent e3eb056 commit 4baa099

File tree

14 files changed

+356
-278
lines changed

14 files changed

+356
-278
lines changed

.storybook/decorators/arg-events.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ export const withArgEvents = makeDecorator({
1313
parameterName: "argEvents",
1414
wrapper: (StoryFn, context) => {
1515
/** @type {[Args, (newArgs: Partial<Args>) => void, (argNames?: (keyof Args)[]) => void]} */
16-
const [, updateArgs] = useArgs(context.args);
16+
const [, updateArgs, resetArgs] = useArgs(Object.keys(context.args));
1717

1818
// Bind the updateArgs function for use in nested templates
1919
context.updateArgs = updateArgs;
20+
context.resetArgs = resetArgs;
2021

2122
return StoryFn(context);
2223
},

components/actionbutton/stories/template.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export const Template = ({
5555
isActive = false,
5656
isDisabled = false,
5757
hasPopup = "false",
58+
showPopup = false,
5859
popupId,
5960
hideLabel = false,
6061
staticColor,
@@ -105,7 +106,7 @@ export const Template = ({
105106
updateArgs({ isFocused: false });
106107
}}
107108
>
108-
${when(hasPopup && hasPopup !== "false", () =>
109+
${when(showPopup && hasPopup && hasPopup !== "false", () =>
109110
Icon({
110111
size,
111112
iconName: "CornerTriangle" + ({
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"sourceFile": "index.css",
3+
"selectors": [".spectrum-ActionMenu", ".spectrum-ActionMenu-popover"],
4+
"modifiers": ["--mod-actionmenu-button-to-menu-gap"],
5+
"component": ["--spectrum-actionmenu-button-to-menu-gap"],
6+
"global": ["--spectrum-spacing-100"],
7+
"passthroughs": ["--mod-popover-animation-distance"],
8+
"high-contrast": []
9+
}

components/actionmenu/index.css

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*!
2+
* Copyright 2024 Adobe. All rights reserved.
3+
*
4+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License. You may obtain a copy
6+
* of the License at <http://www.apache.org/licenses/LICENSE-2.0>
7+
*
8+
* Unless required by applicable law or agreed to in writing, software distributed under
9+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
10+
* OF ANY KIND, either express or implied. See the License for the specific language
11+
* governing permissions and limitations under the License.
12+
*/
13+
14+
/*
15+
* @spectrum-css/actionmenu
16+
* This component is a combination of a menu, popover, and action button.
17+
* It is used to display a list of actions in a popover menu when the user clicks on an action button.
18+
* The markup has the following structure:
19+
* <div class="spectrum-ActionMenu"> <!-- This is the container -->
20+
* <button class="spectrum-ActionMenu-trigger spectrum-ActionButton"> ... </button> <!-- This is the action button -->
21+
* <div class="spectrum-ActionMenu-popover spectrum-Popover"> <!-- This is the popover that contains the menu -->
22+
* <ul class="spectrum-ActionMenu-menu spectrum-Menu"> <!-- This is the menu -->
23+
*/
24+
25+
.spectrum-ActionMenu {
26+
--spectrum-actionmenu-button-to-menu-gap: var(--mod-actionmenu-button-to-menu-gap, var(--spectrum-spacing-100));
27+
}
28+
29+
.spectrum-ActionMenu-popover {
30+
/* @passthrough start -- popover */
31+
/* note: right now this is already the same value as the popover animation distance */
32+
--mod-popover-animation-distance: var(--spectrum-actionmenu-button-to-menu-gap);
33+
/* @passthrough end */
34+
}

components/actionmenu/project.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
3+
"name": "actionmenu",
4+
"tags": ["component"],
5+
"targets": {
6+
"build": {},
7+
"clean": {},
8+
"compare": {},
9+
"format": {},
10+
"lint": {},
11+
"report": {},
12+
"test": {
13+
"defaultConfiguration": "scope"
14+
},
15+
"validate": {}
16+
}
17+
}

components/actionmenu/stories/actionmenu.stories.js

Lines changed: 130 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,38 @@
1+
import { ArgGrid, Container } from "@spectrum-css/preview/decorators/utilities.js";
2+
import { disableDefaultModes } from "@spectrum-css/preview/modes";
3+
import { isOpen } from "@spectrum-css/preview/types";
4+
15
import { default as ActionButton } from "@spectrum-css/actionbutton/stories/actionbutton.stories.js";
26
import { default as IconStories } from "@spectrum-css/icon/stories/icon.stories.js";
37
import { default as Menu } from "@spectrum-css/menu/stories/menu.stories.js";
48
import { default as Popover } from "@spectrum-css/popover/stories/popover.stories.js";
5-
import { disableDefaultModes } from "@spectrum-css/preview/modes";
6-
import { isOpen } from "@spectrum-css/preview/types";
7-
import packageJson from "../package.json";
9+
10+
import { Template as IconTemplate } from "@spectrum-css/icon/stories/template.js";
811
import { ActionMenuGroup } from "./actionmenu.test.js";
12+
import { Template } from "./template.js";
13+
14+
import metadata from "../dist/metadata.json";
15+
import packageJson from "../package.json";
916

1017
/**
11-
* The action menu component is an action button with a popover. The `is-selected` class should be applied to the button when the menu is open. Note that the accessibility roles are different for an action menu compared to a normal menu.
18+
* Action menu allows users to access and execute various commands or tasks related to their current workflow. It's typically triggered from an action button by user interaction.
19+
*
20+
* Note that the accessibility roles are different for an action menu compared to a normal menu. The action menu is a combination of a menu, popover, and action button.
1221
*/
1322
export default {
1423
title: "Action menu",
1524
component: "ActionMenu",
1625
argTypes: {
1726
withTip: Popover.argTypes.withTip,
18-
position: Popover.argTypes.position,
27+
position: {
28+
...Popover.argTypes.position,
29+
options: [
30+
"bottom-start",
31+
"bottom-end",
32+
"start-top",
33+
"end-top",
34+
]
35+
},
1936
isOpen,
2037
iconName: {
2138
...(IconStories?.argTypes?.iconName ?? {}),
@@ -35,7 +52,9 @@ export default {
3552
args: {
3653
isOpen: false,
3754
withTip: Popover.args.withTip,
38-
position: Popover.args.position,
55+
position: "bottom-start",
56+
iconName: "AddCircle",
57+
label: "Add",
3958
},
4059
parameters: {
4160
actions: {
@@ -45,36 +64,123 @@ export default {
4564
...(Menu.parameters?.actions?.handles ?? []),
4665
],
4766
},
48-
packageJson,
4967
docs: {
5068
story: {
5169
height: "200px",
5270
}
53-
}
71+
},
72+
design: {
73+
type: "figma",
74+
url: "https://www.figma.com/design/eoZHKJH9a3LJkHYCGt60Vb/S2-token-specs?node-id=19758-3424",
75+
},
76+
packageJson,
77+
metadata,
78+
status: {
79+
type: "migrated",
80+
},
5481
},
82+
tags: ["migrated"],
5583
};
5684

5785
export const Default = ActionMenuGroup.bind({});
5886
Default.args = {
5987
isOpen: true,
60-
position: "bottom",
61-
label: "More actions",
62-
iconName: "More",
63-
items: [
64-
{
65-
label: "Action 1",
66-
},
67-
{
68-
label: "Action 2",
69-
},
70-
{
71-
label: "Action 3",
72-
},
73-
{
74-
label: "Action 4",
75-
},
88+
iconName: "AddCircle",
89+
label: "Add",
90+
items: [{
91+
heading: "Add new page",
92+
items: [
93+
{
94+
label: "Same size",
95+
iconName: "Copy"
96+
},
97+
{
98+
label: "Custom size",
99+
iconName: "Properties"
100+
},
101+
{
102+
label: "Duplicate",
103+
iconName: "Duplicate"
104+
}
105+
]
106+
}, {
107+
type: "divider"
108+
}, {
109+
heading: "Edit page",
110+
items: [{
111+
label: "Edit timeline",
112+
iconName: "Clock",
113+
description: "Add time to this page"
114+
}],
115+
}],
116+
};
117+
118+
// ********* DOCS ONLY ********* //
119+
120+
/**
121+
* Action menus can be positioned in four locals relative to the trigger but <u>only one menu can be triggered at a single time</u>.
122+
*/
123+
export const PlacementOptions = (args, context) => ArgGrid({
124+
Template,
125+
argKey: "position",
126+
widthBorder: false,
127+
...args,
128+
}, context);
129+
PlacementOptions.args = Default.args;
130+
PlacementOptions.tags = ["!dev"];
131+
PlacementOptions.parameters = {
132+
chromatic: {
133+
disableSnapshot: true,
134+
},
135+
};
136+
137+
/**
138+
* Icon used is a placeholder and can be swapped with any other from icon set along with corresponding label.
139+
*/
140+
export const PlaceholderIcon = (args, context) => Container({
141+
withBorder: false,
142+
content: [
143+
Template(args, context),
144+
IconTemplate({
145+
iconName: "ArrowRight400",
146+
setName: "ui",
147+
fill: "var(--spectrum-gray-400)",
148+
customStyles: {
149+
"margin-block-start": "var(--spectrum-spacing-200)",
150+
},
151+
}, context),
152+
Template(Default.args, context),
76153
],
154+
});
155+
PlaceholderIcon.args = {
156+
iconName: "More",
157+
label: "",
158+
isOpen: true,
159+
items: [{
160+
heading: "Menu section header",
161+
items: [
162+
{
163+
label: "Menu item",
164+
iconName: "Circle"
165+
},
166+
{
167+
label: "Menu item",
168+
iconName: "Circle"
169+
},
170+
{
171+
label: "Menu item",
172+
iconName: "Circle"
173+
}
174+
]
175+
}],
77176
};
177+
PlaceholderIcon.tags = ["!dev"];
178+
PlaceholderIcon.parameters = {
179+
chromatic: {
180+
disableSnapshot: true,
181+
},
182+
};
183+
78184

79185
// ********* VRT ONLY ********* //
80186
export const WithForcedColors = ActionMenuGroup.bind({});
Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,17 @@
1-
import { Variants } from "@spectrum-css/preview/decorators";
1+
import { ArgGrid, Variants } from "@spectrum-css/preview/decorators";
22
import { Template } from "./template.js";
33

44
export const ActionMenuGroup = Variants({
55
Template,
6-
testData: [{
7-
wrapperStyles: {
8-
"min-block-size": "200px",
9-
"align-items": "flex-start",
10-
},
11-
}, {
12-
testHeading: "Closed menu",
13-
isOpen: false,
14-
wrapperStyles: {
15-
"min-block-size": "50px",
16-
},
17-
}, {
18-
testHeading: "Custom icon",
19-
isOpen: false,
20-
iconName: "Add",
21-
iconSet: "workflow",
22-
wrapperStyles: {
23-
"min-block-size": "50px",
24-
},
6+
withSizes: false,
7+
testData: [{}, {
8+
testHeading: "Positioning",
9+
withStates: false,
10+
Template: (args, context) => ArgGrid({
11+
Template,
12+
argKey: "position",
13+
widthBorder: false,
14+
...args,
15+
}, context),
2516
}],
2617
});

components/actionmenu/stories/template.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Template as Popover } from "@spectrum-css/popover/stories/template.js";
44
import { getRandomId } from "@spectrum-css/preview/decorators";
55

66
export const Template = ({
7+
rootClass = "spectrum-ActionMenu",
78
id = getRandomId("actionmenu"),
89
testId,
910
triggerId = getRandomId("actionmenu-trigger"),
@@ -33,13 +34,16 @@ export const Template = ({
3334
iconName,
3435
iconSet,
3536
id: triggerId,
36-
customClasses,
37+
customClasses: [`${rootClass}-trigger`],
3738
}, context),
3839
position: "bottom-start",
3940
customStyles,
41+
customClasses: [`${rootClass}-popover`],
42+
customWrapperClasses: [rootClass, ...customClasses],
4043
content: [
4144
(passthroughs) => Menu({
4245
...passthroughs,
46+
customClasses: [`${rootClass}-menu`],
4347
items,
4448
isOpen,
4549
size

components/icon/stories/template.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export const Template = ({
3939
fill,
4040
id = getRandomId("icon"),
4141
customClasses = [],
42+
customStyles = {},
4243
useRef = true,
4344
} = {}, context = {}) => {
4445
// All icons SVG markup from the global IconLoader are in loaded.icons
@@ -113,6 +114,8 @@ export const Template = ({
113114
...customClasses.reduce((a, c) => ({ ...a, [c]: true }), {}),
114115
};
115116

117+
if (fill) customStyles.color = fill;
118+
116119
/**
117120
* Display full SVG file markup from global IconLoader data, when not using a reference to the sprite sheet.
118121
*/
@@ -129,8 +132,13 @@ export const Template = ({
129132
return acc;
130133
}, "");
131134

135+
const stylesAsString = Object.entries(customStyles).reduce((acc, [key, value]) => {
136+
acc += `${key}: ${value};`;
137+
return acc;
138+
}, "");
139+
132140
return html`${unsafeSVG(
133-
svgString.replace(/<svg/, `<svg class="${classesAsString}" ${fill ? `style="color: ${fill};"` : ""} focusable="false" aria-hidden="true" role="img"`)
141+
svgString.replace(/<svg/, `<svg class="${classesAsString}" style="${stylesAsString}" focusable="false" aria-hidden="true" role="img"`)
134142
)}`;
135143
}
136144
else {
@@ -145,7 +153,7 @@ export const Template = ({
145153
return html`<svg
146154
class=${classMap(classList)}
147155
id=${ifDefined(id)}
148-
style=${ifDefined(fill ? `color: ${fill};` : undefined)}
156+
style=${styleMap(customStyles)}
149157
focusable="false"
150158
aria-hidden="true"
151159
aria-label=${iconName}

0 commit comments

Comments
 (0)