Skip to content

Commit

Permalink
Merge pull request #37 from buildo/actions_with_loader
Browse files Browse the repository at this point in the history
Actions with loader
  • Loading branch information
gabro committed Mar 7, 2022
2 parents 2b55930 + af2f589 commit 09e5e4e
Show file tree
Hide file tree
Showing 10 changed files with 151 additions and 34 deletions.
108 changes: 80 additions & 28 deletions src/Actions/createActions.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FunctionComponent } from "react";
import { BoxProps } from "../";
import { FunctionComponent, useState } from "react";
import { BannerProps, BoxProps, InlineLoader, LocalizedString } from "../";
import { bentoSprinkles, Column, Columns, Inline } from "../internal";
import { ButtonProps } from "../Button/createButton";

Expand All @@ -11,27 +11,23 @@ export type ActionsProps = {
isDestructive?: boolean;
};
secondaryAction?: ActionProps;
loadingMessage?: LocalizedString;
error?: LocalizedString;
};

type ActionsConfig = {
primaryPosition: "left" | "right";
defaultSize: ButtonProps["size"];
} & (
| {
buttonsAlignment: "left" | "right";
spaceBetweenButtons: BoxProps<typeof bentoSprinkles>["gap"];
}
| {
buttonsAlignment: "spaceBetween";
spaceBetweenButtons?: never;
}
);
buttonsAlignment: "left" | "right" | "spaceBetween";
spaceBetweenButtons: BoxProps<typeof bentoSprinkles>["gap"];
};

export function createActions(
Button: FunctionComponent<ButtonProps>,
Banner: FunctionComponent<BannerProps>,
config: ActionsConfig = {
primaryPosition: "right",
buttonsAlignment: "right",
primaryPosition: "right",
spaceBetweenButtons: 16,
defaultSize: "medium",
}
Expand All @@ -40,14 +36,22 @@ export function createActions(
primaryAction,
secondaryAction,
size = config.defaultSize,
loadingMessage,
error,
}: ActionsProps) {
const [isLoading, setIsLoading] = useState(false);

const primaryActionButton = primaryAction && (
<Button
key="primary"
{...primaryAction}
kind="solid"
hierarchy={primaryAction.isDestructive ? "danger" : "primary"}
size={size}
onPress={() => {
setIsLoading(true);
Promise.resolve(primaryAction.onPress()).then(() => setIsLoading(false));
}}
/>
);
const secondaryActionButton = secondaryAction && (
Expand All @@ -64,20 +68,68 @@ export function createActions(
? [primaryActionButton, secondaryActionButton]
: [secondaryActionButton, primaryActionButton];

return config.buttonsAlignment === "spaceBetween" ? (
<Columns space={0}>
{buttons[0] || <Column>{null}</Column>}
<Column width="content">{buttons[1]}</Column>
</Columns>
) : (
<Inline
space={config.spaceBetweenButtons}
align={config.buttonsAlignment}
alignY="center"
collapseBelow="tablet"
>
{buttons}
</Inline>
);
switch (config.buttonsAlignment) {
case "right":
return (
<Columns space={config.spaceBetweenButtons} alignY="center" collapseBelow="tablet">
<Column width="1/2">
{isLoading ? (
<InlineLoader message={loadingMessage} />
) : (
error && <Banner kind="negative" description={error} />
)}
</Column>
<Inline
space={config.spaceBetweenButtons}
align="right"
alignY="center"
collapseBelow="tablet"
reverse={{ mobile: true }}
>
{buttons}
</Inline>
</Columns>
);
case "left":
return (
<Columns
space={config.spaceBetweenButtons}
alignY="center"
collapseBelow="tablet"
reverse={{ mobile: true }}
>
<Inline space={config.spaceBetweenButtons} alignY="center" collapseBelow="tablet">
{buttons}
</Inline>
<Column width="1/2">
{isLoading ? (
<Inline space={0} align="right" alignY="center">
<InlineLoader message={loadingMessage} />
</Inline>
) : (
<Column>{error && <Banner kind="negative" description={error} />}</Column>
)}
</Column>
</Columns>
);
case "spaceBetween":
return (
<Columns space={config.spaceBetweenButtons} alignY="center" collapseBelow="tablet">
{buttons[0]}
<Column width="content">
{isLoading ? (
<Inline space={0} align="center" alignY="center">
<InlineLoader message={loadingMessage} />
</Inline>
) : (
error && <Banner kind="negative" description={error} />
)}
</Column>
<Inline space={0} align={{ desktop: "right", mobile: "left" }} alignY="center">
{buttons[1]}
</Inline>
</Columns>
);
}
};
}
2 changes: 2 additions & 0 deletions src/Banner/createBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,5 @@ export function createBanner(
);
};
}

export type { Props as BannerProps };
2 changes: 1 addition & 1 deletion src/Button/createButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const defaultButtonConfig: ButtonConfig = {
large: 16,
},
labelSize: "large",
radius: 8,
radius: 4,
internalSpacing: 8,
iconSize: {
small: 12,
Expand Down
3 changes: 3 additions & 0 deletions src/DefaultMessagesContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ export type DefaultMessages = {
noResultsDescription: LocalizedString;
missingValue: LocalizedString;
};
Loader: {
loadingMessage: LocalizedString;
};
};
};

Expand Down
11 changes: 10 additions & 1 deletion src/Form/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type Props = {
description?: TextChildren;
submitButton?: Omit<ButtonProps, "kind" | "hierarchy">;
secondaryButton?: Omit<ButtonProps, "kind" | "hierarchy">;
error?: LocalizedString;
};

export type FormConfig = {
Expand All @@ -29,7 +30,14 @@ export type FormConfig = {
};

export function createForm(Actions: FunctionComponent<ActionsProps>, config: FormConfig) {
return function Form({ title, description, children, submitButton, secondaryButton }: Props) {
return function Form({
title,
description,
children,
submitButton,
secondaryButton,
error,
}: Props) {
return (
<Box as="form" onSubmit={(e) => e.preventDefault()}>
<ContentBlock maxWidth={700}>
Expand All @@ -46,6 +54,7 @@ export function createForm(Actions: FunctionComponent<ActionsProps>, config: For
size={config.actionsSize}
primaryAction={submitButton}
secondaryAction={secondaryButton}
error={error}
/>
)}
</Stack>
Expand Down
13 changes: 11 additions & 2 deletions src/InlineLoader/InlineLoader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { Box, Inline } from "../internal";
import { Body, LocalizedString } from "..";
import { icon } from "./InlineLoader.css";
import { vars } from "../vars.css";
import { useDefaultMessages } from "../util/useDefaultMessages";

type Props = {
message: LocalizedString;
message?: LocalizedString;
};

const Loader = (
Expand All @@ -14,11 +15,19 @@ const Loader = (
);

export function InlineLoader({ message }: Props) {
const {
defaultMessages: {
Loader: { loadingMessage: defaultLoadingMessage },
},
} = useDefaultMessages();

const loadingMessage = message || defaultLoadingMessage;

return (
<Inline space={8} alignY="center">
<Box className={icon}>{Loader}</Box>
<Body size="medium" color="secondary">
{message}
{loadingMessage}
</Body>
</Inline>
);
Expand Down
21 changes: 21 additions & 0 deletions stories/Components/Actions.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const { defaultExport, createStory } = createComponentStories({
component: Actions,
args: {
size: "medium",
error: formatMessage("Something went wrong"),
},
});

Expand Down Expand Up @@ -41,6 +42,12 @@ export const TwoActions = createStory({
secondaryAction,
});

export const TwoActionsWithError = createStory({
primaryAction,
secondaryAction,
error: formatMessage("Something went wrong"),
});

export const TwoActionsDestructive = createStory({
primaryAction: {
...primaryAction,
Expand All @@ -60,3 +67,17 @@ export const TwoActionsLarge = createStory({
secondaryAction,
size: "large",
});

export const AsyncPrimaryAction = createStory({
primaryAction: {
...primaryAction,
onPress: () =>
new Promise((resolve) =>
setTimeout(() => {
primaryAction.onPress();
resolve(null);
}, 3000)
),
},
secondaryAction,
});
18 changes: 18 additions & 0 deletions stories/Components/Modal.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,21 @@ export const Destructive = createStory({
onPress: action("Cancel"),
},
});

export const WithAsyncPrimaryAction = createStory({
children: [<Placeholder />],
primaryAction: {
label: formatMessage("Create"),
onPress: () =>
new Promise((resolve) => {
setTimeout(() => {
action("Create")();
resolve(null);
}, 3000);
}),
},
secondaryAction: {
label: formatMessage("Cancel"),
onPress: action("Cancel"),
},
});
3 changes: 3 additions & 0 deletions stories/defaultMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,7 @@ export const defaultMessages: DefaultMessages["defaultMessages"] = {
),
missingValue: formatMessage("-"),
},
Loader: {
loadingMessage: formatMessage("Loading..."),
},
};
4 changes: 2 additions & 2 deletions stories/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ export const {
TextField,
} = createFormFields();
export const { Button, ButtonLink } = createButtons();
export const Actions = createActions(Button);
export const { Form, FormSection, FormRow } = createFormLayoutComponents(Actions);
export const Banner = createBanner(Button, {});
export const Actions = createActions(Button, Banner);
export const { Form, FormSection, FormRow } = createFormLayoutComponents(Actions);
export const { Toast, ToastProvider } = createToast(Button, {});
export const BentoProvider = createBentoProvider(Toast);
export const Card = createCard<24 | 32 | 40>({});
Expand Down

0 comments on commit 09e5e4e

Please sign in to comment.