Skip to content

Commit dbf5337

Browse files
authored
feat(MenuItem): support action states
1 parent 948009e commit dbf5337

File tree

19 files changed

+233
-9
lines changed

19 files changed

+233
-9
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import {
2+
Action,
3+
Button,
4+
ContextMenuTrigger,
5+
ContextMenu,
6+
MenuItem,
7+
} from "@mittwald/flow-react-components";
8+
import { sleepLong } from "@/content/04-components/actions/action/examples/lib";
9+
10+
<ContextMenuTrigger>
11+
<Button>Trigger</Button>
12+
<ContextMenu>
13+
<Action onAction={sleepLong}>
14+
<MenuItem>Herunterladen</MenuItem>
15+
</Action>
16+
</ContextMenu>
17+
</ContextMenuTrigger>;

apps/docs/src/content/04-components/actions/action/overview.mdx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,11 @@ Wenn mehrere Aktionen auf eine Userinteraktion folgen, können Actions auch
4242
ineinander verschachtelt werden.
4343

4444
<LiveCodeEditor example="nested" editorCollapsed />
45+
46+
---
47+
48+
# In MenuItems
49+
50+
Actions können auch um `MenuItems` gelegt werden, um deren State zu steuern.
51+
52+
<LiveCodeEditor example="menuItem" editorCollapsed />

apps/docs/src/content/04-components/actions/button/overview.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ deinen Button neben Input Feldern zu platzieren.
114114

115115
# States
116116

117-
Ein Button hat sieben verschiedene States: **Default, Hover, Pressed, Disabled,
118-
Pending, Succeeded** und Failed.
117+
Ein Button kann unterschiedliche States annehmen, die anzeigen, dass aktuell
118+
keine Interaktion möglich ist.
119119

120120
<LiveCodeEditor example="states" editorCollapsed />
121121

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import {
2+
Button,
3+
ContextMenu,
4+
ContextMenuTrigger,
5+
MenuItem,
6+
} from "@mittwald/flow-react-components";
7+
8+
<ContextMenuTrigger>
9+
<Button>Trigger</Button>
10+
<ContextMenu>
11+
<MenuItem isDisabled>Disabled</MenuItem>
12+
<MenuItem isPending>Pending</MenuItem>
13+
<MenuItem isSucceeded>Succeeded</MenuItem>
14+
<MenuItem isFailed>Failed</MenuItem>
15+
</ContextMenu>
16+
</ContextMenuTrigger>;

apps/docs/src/content/04-components/actions/context-menu/overview.mdx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,23 @@ Verwende das ContextMenu als Profilmenü, indem innerhalb ein
9494

9595
---
9696

97+
# States
98+
99+
Ein MenuItem kann unterschiedliche States annehmen, die anzeigen, dass aktuell
100+
keine Interaktion möglich ist.
101+
102+
<LiveCodeEditor example="states" editorCollapsed />
103+
104+
**Disabled:** Verwende den Disabled-State, wenn das MenuItem keine Aktion oder
105+
kein Ereignis auslösen soll.
106+
107+
**Pending, Succeeded und Failed:** Pending, Succeeded und Failed sollen dem
108+
Nutzer signalisieren, dass im Hintergrund etwas passiert, wenn er auf ein
109+
MenuItem geklickt hat. Verwende das MenuItem zusammen mit der Komponente
110+
[Action](/04-components/actions/action/overview), um die States zu steuern.
111+
112+
---
113+
97114
# Kombiniere mit
98115

99116
## Modal

apps/remote-dom-demo/src/app/remote/modal/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ export default function Page() {
134134
</LightBox>
135135
</LightBoxTrigger>
136136

137-
<Action action={() => console.log("Projekt löschen clicked")}>
137+
<Action onAction={() => console.log("Projekt löschen clicked")}>
138138
<Modal slot="actionConfirm">
139139
<Heading>Projekt löschen</Heading>
140140
<Content>

packages/components/src/components/Action/Action.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { useActionButtonState } from "@/components/Action/hooks/useActionButtonS
88
import type { ComponentPropsContext } from "@/lib/propsContext/types";
99
import { flowComponent } from "@/lib/componentFactory/flowComponent";
1010
import type { ActionFn } from "@/components/Action/types";
11+
import { useActionState } from "@/components/Action/hooks/useActionState";
1112

1213
const actionButtonContext: ComponentPropsContext<"Button"> = {
1314
onPress: dynamic((props) => {
@@ -77,6 +78,26 @@ export const Action = flowComponent(
7778

7879
MenuItem: {
7980
onAction: dynamic(() => ActionModel.use().execute),
81+
isPending: dynamic((props) => {
82+
const actionState = useActionState();
83+
return props.isPending ?? actionState === "isPending";
84+
}),
85+
isSucceeded: dynamic((props) => {
86+
const actionState = useActionState();
87+
return props.isSucceeded ?? actionState === "isSucceeded";
88+
}),
89+
isFailed: dynamic((props) => {
90+
const actionState = useActionState();
91+
return props.isFailed ?? actionState === "isFailed";
92+
}),
93+
"aria-disabled": dynamic((props) => {
94+
const state = useActionState();
95+
const someActionInContextIsBusy = useActionStateContext().useIsBusy();
96+
return (
97+
props["aria-disabled"] ??
98+
(state === "isExecuting" || someActionInContextIsBusy)
99+
);
100+
}),
80101
},
81102

82103
Modal: {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import type { ActionStateValue } from "@/components/Action/models/ActionState";
2+
import { ActionModel } from "@/components/Action/models/ActionModel";
3+
4+
export const useActionState = (): ActionStateValue => {
5+
const action = ActionModel.use();
6+
return action.state.useValue();
7+
};

packages/components/src/components/Action/stories/Default.stories.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import { Button } from "@/components/Button";
1313
import { Heading } from "@/components/Heading";
1414
import { Content } from "@/components/Content";
1515
import { Action } from "@/components/Action";
16+
import ContextMenu, { ContextMenuTrigger } from "@/components/ContextMenu";
17+
import { MenuItem } from "@/components/MenuItem";
1618

1719
const meta: Meta<typeof Action> = {
1820
title: "Actions/Action",
@@ -114,3 +116,24 @@ export const WithConfirmationModal: Story = {
114116
</Action>
115117
),
116118
};
119+
120+
export const InContextMenu: Story = {
121+
render: (props) => (
122+
<ContextMenuTrigger>
123+
<Button>Trigger</Button>
124+
<ContextMenu {...props}>
125+
<Action onAction={asyncFunction}>
126+
<MenuItem>Async</MenuItem>
127+
</Action>
128+
<Action onAction={asyncLongFunction}>
129+
<MenuItem>Async Long</MenuItem>
130+
</Action>
131+
<Action onAction={asyncFunction}>
132+
<Action onAction={asyncFunction}>
133+
<MenuItem>Nested Async</MenuItem>
134+
</Action>
135+
</Action>
136+
</ContextMenu>
137+
</ContextMenuTrigger>
138+
),
139+
};

packages/components/src/components/ContextMenu/stories/Default.stories.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,3 +209,17 @@ export const WithSectionSelectionMode: Story = {
209209
defaultOpen: true,
210210
},
211211
};
212+
213+
export const MenuItemStates: Story = {
214+
render: (props) => (
215+
<ContextMenuTrigger>
216+
<Button>Trigger</Button>
217+
<ContextMenu {...props}>
218+
<MenuItem isDisabled>Disabled</MenuItem>
219+
<MenuItem isPending>Pending</MenuItem>
220+
<MenuItem isSucceeded>Succeeded</MenuItem>
221+
<MenuItem isFailed>Failed</MenuItem>
222+
</ContextMenu>
223+
</ContextMenuTrigger>
224+
),
225+
};

0 commit comments

Comments
 (0)