Skip to content

Commit 75f3558

Browse files
authored
feat: evo-dialog component (#596)
1 parent e72d6d9 commit 75f3558

File tree

13 files changed

+633
-28
lines changed

13 files changed

+633
-28
lines changed

.changeset/icy-comics-find.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@evo-web/marko": patch
3+
"@ebay/skin": patch
4+
---
5+
6+
Add evo-dialog component
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<h1 style='display: flex; justify-content: space-between; align-items: center;'>
2+
<span>
3+
evo-dialog
4+
</span>
5+
<span style='font-weight: normal; font-size: medium; margin-bottom: -15px;'>
6+
DS v1.2.0
7+
</span>
8+
</h1>
9+
10+
A native HTML `<dialog>` component that opens as a modal with backdrop scrim, close animation, and cancel support.
11+
12+
## Examples and Documentation
13+
14+
- [Storybook](https://ebay.github.io/evo-web/ebayui-core/?path=/story/navigation-disclosure-evo-dialog)
15+
- [Storybook Docs](https://ebay.github.io/evo-web/ebayui-core/?path=/docs/navigation-disclosure-evo-dialog)
16+
- [Code Examples](https://github.com/eBay/evo-web/tree/main/packages/ebayui-core/src/components/evo-dialog/examples)
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { buildExtensionTemplate } from "../../common/storybook/utils";
2+
import { type Meta } from "@storybook/marko";
3+
import Readme from "./README.md";
4+
import Dialog, { type Input } from "./index.marko";
5+
import DefaultTemplate from "./examples/default.marko";
6+
import DefaultTemplateCode from "./examples/default.marko?raw";
7+
import BannerTemplate from "./examples/banner.marko";
8+
import BannerTemplateCode from "./examples/banner.marko?raw";
9+
import CustomBannerTemplate from "./examples/custom-banner.marko";
10+
import CustomBannerTemplateCode from "./examples/custom-banner.marko?raw";
11+
12+
export default {
13+
title: "navigation & disclosure/evo-dialog",
14+
component: Dialog,
15+
parameters: {
16+
docs: {
17+
description: {
18+
component: Readme,
19+
},
20+
},
21+
},
22+
23+
argTypes: {
24+
open: {
25+
type: "boolean",
26+
controllable: true,
27+
description: "Whether the dialog is open",
28+
table: { defaultValue: { summary: "false" } },
29+
},
30+
size: {
31+
type: "string",
32+
options: ["regular (default)", "wide", "narrow", "large"],
33+
control: "inline-radio",
34+
description: "Size variant of the dialog",
35+
},
36+
closedby: {
37+
type: "string",
38+
options: ["any", "closerequest", "none"],
39+
control: "inline-radio",
40+
description:
41+
'The [`closedby=` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog#closedby) from the native `<dialog>` component. Defaults to `"any"` if not specified',
42+
table: { defaultValue: { summary: "any" } },
43+
},
44+
header: {
45+
description:
46+
"The header content rendered inside the dialog title (required)",
47+
"@": {
48+
as: {
49+
type: "string",
50+
description:
51+
"The heading element to use for the title. Defaults to `h2`",
52+
},
53+
["<h2> attributes" as any]: {
54+
description:
55+
"All attributes and event handlers from the heading element will be passed through",
56+
},
57+
},
58+
},
59+
footer: {
60+
description:
61+
"The footer content rendered below the dialog main content area",
62+
"@": {
63+
["<div> attributes" as any]: {
64+
description:
65+
"All attributes and event handlers from [the native HTML `<div>` tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/div) will be passed through",
66+
},
67+
},
68+
},
69+
close: {
70+
description:
71+
"Close button rendered in the dialog header (required). Pass `a11yText` for the accessible label",
72+
"@": {
73+
["<button> attributes" as any]: {
74+
description:
75+
"All attributes and event handlers from [the native HTML `<button>` tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button) will be passed through",
76+
},
77+
},
78+
},
79+
previous: {
80+
description: "Optional previous/back button rendered in the header",
81+
"@": {
82+
a11yText: {
83+
type: "string",
84+
description: "Accessible label for the previous button",
85+
},
86+
["<button> attributes" as any]: {
87+
description:
88+
"All attributes and event handlers from [the native HTML `<button>` tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button) will be passed through",
89+
},
90+
},
91+
},
92+
banner: {
93+
description: "Optional banner image displayed at the top of the dialog",
94+
"@": {
95+
src: {
96+
type: "string",
97+
description: "URL of the banner image",
98+
},
99+
position: {
100+
type: "string",
101+
description:
102+
"Position of the image within the banner area using the CSS `background-position` property. Options include [keywords, lengths, and edge distances](https://developer.mozilla.org/en-US/docs/Web/CSS/background-position)",
103+
},
104+
["<div> attributes" as any]: {
105+
description:
106+
"All attributes and event handlers from [the native HTML `<div>` tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/div) will be passed through",
107+
},
108+
},
109+
},
110+
["<dialog> attributes" as any]: {
111+
description:
112+
"All attributes and event handlers from [the native HTML `<dialog>` tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dialog) will be passed through",
113+
},
114+
},
115+
} satisfies Meta<Input>;
116+
117+
export const Default = buildExtensionTemplate(
118+
DefaultTemplate,
119+
DefaultTemplateCode,
120+
);
121+
122+
export const Banner = buildExtensionTemplate(
123+
BannerTemplate,
124+
BannerTemplateCode,
125+
);
126+
127+
export const CustomBanner = buildExtensionTemplate(
128+
CustomBannerTemplate,
129+
CustomBannerTemplateCode,
130+
);
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { type Input as DialogInput } from "<evo-dialog>";
2+
export interface Input extends DialogInput {}
3+
4+
<let/open:=input.open>
5+
6+
<evo-button onClick() { open = true; }>
7+
Open Dialog With Banner
8+
</evo-button>
9+
10+
<evo-dialog ...input open:=open>
11+
<@banner
12+
src="https://ir.ebaystatic.com/cr/v/c01/skin/docs/tb-landscape-pic.jpg"
13+
position="top"
14+
/>
15+
<@header>Dialog Title</@header>
16+
<@close a11yText="Close Dialog"/>
17+
<p>This dialog uses a banner image via the src attribute.</p>
18+
</evo-dialog>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { type Input as DialogInput } from "<evo-dialog>";
2+
export interface Input extends DialogInput {}
3+
4+
<style>
5+
.banner-demo {
6+
background: linear-gradient(90deg, #2f7ede, #17a7ce);
7+
display: flex;
8+
align-items: center;
9+
justify-content: center;
10+
color: white;
11+
font-size: 24px;
12+
font-weight: bold;
13+
}
14+
</style>
15+
16+
<let/open:=input.open>
17+
18+
<evo-button onClick() { open = true; }>
19+
Open Dialog With Custom Banner
20+
</evo-button>
21+
22+
<evo-dialog ...input open:=open>
23+
<@banner class="banner-demo">
24+
Custom Banner
25+
</@banner>
26+
<@header>Dialog Title</@header>
27+
<@close a11yText="Close Dialog"/>
28+
<@previous a11yText="Back"/>
29+
<p>This dialog uses a custom body inside the banner slot instead of a background image.</p>
30+
</evo-dialog>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { type Input as DialogInput } from "<evo-dialog>";
2+
export interface Input extends DialogInput {}
3+
4+
<let/open:=input.open>
5+
6+
<evo-button onClick() { open = true; }>
7+
Open Dialog
8+
</evo-button>
9+
10+
<evo-dialog ...input open:=open>
11+
<@header>Dialog Title</@header>
12+
<@close a11yText="Close Dialog"/>
13+
<p>This is the default dialog content.</p>
14+
</evo-dialog>
Lines changed: 115 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,124 @@
1-
<!-- NOTE, this is not a full implementation but only here so the code is not lost -->
2-
<let/open = false>
3-
<let/visible = false>
1+
import { type Input as IconButtonInput } from "<evo-icon-button>";
2+
3+
static const validSizes = ["wide", "narrow", "large"] as const;
4+
5+
export interface Input extends Marko.HTML.Dialog {
6+
open?: boolean;
7+
openChange?: (o: boolean) => void;
8+
size?: (typeof validSizes)[number];
9+
banner?: Marko.AttrTag<
10+
Marko.Input<"div"> & {
11+
src?: string;
12+
position?: Marko.CSS.Properties["background-position"];
13+
}
14+
>;
15+
header: Marko.AttrTag<Marko.Input<"h2"> & { as?: string }>;
16+
footer?: Marko.AttrTag<Marko.Input<"div">>;
17+
close: Marko.AttrTag<IconButtonInput>;
18+
previous?: Marko.AttrTag<IconButtonInput>;
19+
}
20+
21+
<const/{
22+
open: inputOpen,
23+
openChange,
24+
class: inputClass,
25+
size,
26+
banner,
27+
header,
28+
footer,
29+
close,
30+
previous,
31+
content,
32+
"aria-labelledby": inputLabelledBy,
33+
closedby = "any",
34+
onCancel,
35+
onAnimationEnd,
36+
...htmlInput
37+
}=input>
38+
39+
<let/open:=input.open>
40+
<id/headerId=header.id>
441
<script>
5-
if (open) {
42+
if (open && !$dialog().open) {
643
$dialog().showModal();
7-
visible = true;
8-
} else {
9-
if (visible) {
10-
$dialog().classList.add("dialog-close");
11-
$dialog().addEventListener(
12-
"animationend",
13-
() => {
14-
$dialog().close();
15-
visible = false;
16-
$dialog().classList.remove("dialog-close");
17-
},
18-
{
19-
signal: $signal,
20-
once: true,
21-
},
22-
);
23-
}
2444
}
2545
</script>
2646

2747
<dialog/$dialog
28-
class="dialog"
29-
// closedby="any"
30-
onCancel(e) {
48+
...htmlInput
49+
open=null // tell Marko it's not a part of the spread because the browser does DOM manipulation
50+
closedby=closedby
51+
aria-labelledby=(inputLabelledBy ? `${inputLabelledBy} ${headerId}` : headerId)
52+
class=[
53+
"dialog",
54+
!open && "dialog--close",
55+
size && validSizes.includes(size) && `dialog--${size}`,
56+
banner && "dialog--expressive",
57+
inputClass,
58+
]
59+
onCancel(e, el) {
3160
e.preventDefault();
3261
open = false;
62+
onCancel && onCancel(e, el);
63+
}
64+
onAnimationEnd(e, el) {
65+
if (!open) {
66+
el.close();
67+
}
68+
onAnimationEnd && onAnimationEnd(e, el);
3369
}
34-
...input>
35-
</dialog>
70+
>
71+
<if=banner>
72+
<const/{
73+
src: bannerSrc,
74+
position: bannerPosition,
75+
class: bannerClass,
76+
style: bannerStyle,
77+
...bannerInput
78+
}=banner>
79+
<div
80+
...bannerInput
81+
class=["dialog__image", bannerClass]
82+
style={
83+
...bannerStyle as Record<string, unknown>,
84+
"background-image": bannerSrc && `url(${bannerSrc})`,
85+
"background-position": bannerPosition,
86+
}
87+
/>
88+
</if>
89+
<div class="dialog__header">
90+
<if=previous>
91+
<evo-icon-button
92+
...previous
93+
class=["dialog__prev", previous.class]
94+
>
95+
<evo-icon-chevron-left-16/>
96+
</evo-icon-button>
97+
</if>
98+
<const/{
99+
as: headerAs,
100+
...headerInput
101+
}=header>
102+
<${headerAs || "h2"}
103+
...headerInput
104+
id=headerId
105+
class=["dialog__title", headerInput.class]
106+
/>
107+
<evo-icon-button
108+
...close
109+
class=["dialog__close", close.class]
110+
onClick(e, el) {
111+
$dialog().requestClose();
112+
close.onClick && close.onClick(e, el);
113+
}
114+
>
115+
<evo-icon-close-16/>
116+
</evo-icon-button>
117+
</div>
118+
<div class="dialog__main">
119+
<${content}/>
120+
</div>
121+
<if=footer>
122+
<div ...footer class=["dialog__footer", footer.class]/>
123+
</if>
124+
</dialog>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import "@ebay/skin/dialog";

0 commit comments

Comments
 (0)