@noctuatech/uswds 0.0.7 → 0.0.9

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.
Files changed (97) hide show
  1. package/README.md +6 -6
  2. package/assets/img/4c-lg-on-black.svg +10 -0
  3. package/package.json +3 -2
  4. package/src/lib/button/button.element.ts +16 -1
  5. package/src/lib/define.ts +7 -1
  6. package/src/lib/file-input/{file-input-preview.element.ts → file-input-preview/file-input-preview.element.ts} +20 -17
  7. package/src/lib/file-input/file-input.element.ts +33 -21
  8. package/src/lib/file-input/file-input.stories.ts +0 -2
  9. package/src/lib/input/input.element.ts +11 -8
  10. package/src/lib/modal/modal-close/modal-close.element.ts +55 -0
  11. package/src/lib/modal/modal-close/modal-close.test.ts +15 -0
  12. package/src/lib/modal/modal-heading/modal-heading.element.ts +38 -0
  13. package/src/lib/modal/modal-heading/modal-heading.test.ts +15 -0
  14. package/src/lib/modal/modal.element.ts +85 -0
  15. package/src/lib/modal/modal.stories.ts +41 -0
  16. package/src/lib/modal/modal.test.ts +25 -0
  17. package/src/lib/radio/radio.element.ts +1 -4
  18. package/src/lib/select/select.element.ts +1 -0
  19. package/src/lib/step-indicator/step/step-indicator-step.element.ts +110 -0
  20. package/src/lib/step-indicator/step/step-indicator-step.test.ts +15 -0
  21. package/src/lib/step-indicator/step-indicator.element.ts +24 -0
  22. package/src/lib/step-indicator/step-indicator.stories.ts +49 -0
  23. package/src/lib/step-indicator/step-indicator.test.ts +21 -0
  24. package/src/lib/summary-box/summary-box.element.ts +35 -0
  25. package/src/lib/summary-box/summary-box.stories.ts +24 -0
  26. package/src/lib/summary-box/summary-box.test.ts +15 -0
  27. package/src/lib.ts +7 -0
  28. package/target/lib/button/button.element.d.ts +3 -0
  29. package/target/lib/button/button.element.js +27 -3
  30. package/target/lib/button/button.element.js.map +1 -1
  31. package/target/lib/define.d.ts +6 -0
  32. package/target/lib/define.js +6 -0
  33. package/target/lib/define.js.map +1 -1
  34. package/target/lib/file-input/file-input-preview.element.js +1 -1
  35. package/target/lib/file-input/file-input-preview.element.js.map +1 -1
  36. package/target/lib/file-input/file-input.element.js +31 -16
  37. package/target/lib/file-input/file-input.element.js.map +1 -1
  38. package/target/lib/file-input/file-input.stories.js +0 -2
  39. package/target/lib/file-input/file-input.stories.js.map +1 -1
  40. package/target/lib/input/input.element.d.ts +1 -0
  41. package/target/lib/input/input.element.js +12 -8
  42. package/target/lib/input/input.element.js.map +1 -1
  43. package/target/lib/modal/modal-close/modal-close.element.d.ts +8 -0
  44. package/target/lib/modal/modal-close/modal-close.element.js +78 -0
  45. package/target/lib/modal/modal-close/modal-close.element.js.map +1 -0
  46. package/target/lib/modal/modal-close/modal-close.test.d.ts +1 -0
  47. package/target/lib/modal/modal-close/modal-close.test.js +11 -0
  48. package/target/lib/modal/modal-close/modal-close.test.js.map +1 -0
  49. package/target/lib/modal/modal-heading/modal-heading.element.d.ts +7 -0
  50. package/target/lib/modal/modal-heading/modal-heading.element.js +50 -0
  51. package/target/lib/modal/modal-heading/modal-heading.element.js.map +1 -0
  52. package/target/lib/modal/modal-heading/modal-heading.test.d.ts +1 -0
  53. package/target/lib/modal/modal-heading/modal-heading.test.js +11 -0
  54. package/target/lib/modal/modal-heading/modal-heading.test.js.map +1 -0
  55. package/target/lib/modal/modal.element.d.ts +13 -0
  56. package/target/lib/modal/modal.element.js +96 -0
  57. package/target/lib/modal/modal.element.js.map +1 -0
  58. package/target/lib/modal/modal.stories.d.ts +12 -0
  59. package/target/lib/modal/modal.stories.js +34 -0
  60. package/target/lib/modal/modal.stories.js.map +1 -0
  61. package/target/lib/modal/modal.test.d.ts +3 -0
  62. package/target/lib/modal/modal.test.js +21 -0
  63. package/target/lib/modal/modal.test.js.map +1 -0
  64. package/target/lib/radio/radio.element.js +1 -4
  65. package/target/lib/radio/radio.element.js.map +1 -1
  66. package/target/lib/select/select.element.js +1 -0
  67. package/target/lib/select/select.element.js.map +1 -1
  68. package/target/lib/step-indicator/step/step-indicator-step.element.d.ts +7 -0
  69. package/target/lib/step-indicator/step/step-indicator-step.element.js +122 -0
  70. package/target/lib/step-indicator/step/step-indicator-step.element.js.map +1 -0
  71. package/target/lib/step-indicator/step/step-indicator-step.test.d.ts +1 -0
  72. package/target/lib/step-indicator/step/step-indicator-step.test.js +11 -0
  73. package/target/lib/step-indicator/step/step-indicator-step.test.js.map +1 -0
  74. package/target/lib/step-indicator/step-indicator.element.d.ts +7 -0
  75. package/target/lib/step-indicator/step-indicator.element.js +36 -0
  76. package/target/lib/step-indicator/step-indicator.element.js.map +1 -0
  77. package/target/lib/step-indicator/step-indicator.stories.d.ts +21 -0
  78. package/target/lib/step-indicator/step-indicator.stories.js +42 -0
  79. package/target/lib/step-indicator/step-indicator.stories.js.map +1 -0
  80. package/target/lib/step-indicator/step-indicator.test.d.ts +1 -0
  81. package/target/lib/step-indicator/step-indicator.test.js +17 -0
  82. package/target/lib/step-indicator/step-indicator.test.js.map +1 -0
  83. package/target/lib/summary-box/summary-box.element.d.ts +7 -0
  84. package/target/lib/summary-box/summary-box.element.js +47 -0
  85. package/target/lib/summary-box/summary-box.element.js.map +1 -0
  86. package/target/lib/summary-box/summary-box.stories.d.ts +12 -0
  87. package/target/lib/summary-box/summary-box.stories.js +17 -0
  88. package/target/lib/summary-box/summary-box.stories.js.map +1 -0
  89. package/target/lib/summary-box/summary-box.test.d.ts +1 -0
  90. package/target/lib/summary-box/summary-box.test.js +11 -0
  91. package/target/lib/summary-box/summary-box.test.js.map +1 -0
  92. package/target/lib.d.ts +6 -0
  93. package/target/lib.js +6 -0
  94. package/target/lib.js.map +1 -1
  95. package/assets/css/global.css +0 -21
  96. package/assets/uswds.min.js +0 -1
  97. /package/src/lib/file-input/{file-input-preview.test.ts → file-input-preview/file-input-preview.test.ts} +0 -0
package/README.md CHANGED
@@ -6,12 +6,12 @@ https://designsystem.digital.gov/
6
6
 
7
7
  ## Getting Started
8
8
 
9
+ ```sh
10
+ npm i @noctuatech/uswds
11
+ ```
12
+
9
13
  ```html
10
14
  <style>
11
- body:has(:not(:defined)) {
12
- display: none;
13
- }
14
-
15
15
  * {
16
16
  font-family:
17
17
  Source Sans Pro Web,
@@ -23,10 +23,10 @@ https://designsystem.digital.gov/
23
23
  }
24
24
  </style>
25
25
 
26
- <script src="https://cdn.jsdelivr.net/npm/@noctuatech/uswds@latest/assets/uswds.min.js"></script>
26
+ <script src="/node_modules/@noctuatech/uswds/assets/uswds.min.js"></script>
27
27
 
28
28
  <usa-config
29
- spritesheet="https://cdn.jsdelivr.net/npm/@noctuatech/uswds@latest/assets/img/sprite.svg"
29
+ spritesheet="/node_modules/@noctuatech/uswds/assets/img/sprite.svg"
30
30
  >
31
31
  <usa-alert type="info">
32
32
  <h3 slot="heading">Informative status</h3>
@@ -0,0 +1,10 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" width="299" height="283" viewBox="0 0 299 283">
3
+ <g fill="none" fill-rule="evenodd">
4
+ <polygon fill="#81AEFC" points="298.45 169.342 193.773 169.335 246.104 8.944"/>
5
+ <polygon fill="#4D52AF" points="233.844 0 201.49 99.116 64.451 0"/>
6
+ <polygon fill="#EE601D" points="4.458 183.269 89.148 122.016 141.5 282.401"/>
7
+ <polygon fill="#F6BD9C" points="52.346 8.624 137.027 69.889 0 169.023"/>
8
+ <polygon fill="#E6E6E6" points="156.817 282.475 124.476 183.354 293.859 183.343"/>
9
+ </g>
10
+ </svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noctuatech/uswds",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "type": "module",
5
5
  "workspaces": [
6
6
  "packages/**"
@@ -95,13 +95,14 @@
95
95
  },
96
96
  "devDependencies": {
97
97
  "@open-wc/testing": "^4.0.0",
98
- "@rollup/plugin-node-resolve": "^15.3.0",
98
+ "@rollup/plugin-node-resolve": "^16.0.0",
99
99
  "@rollup/plugin-terser": "^0.4.4",
100
100
  "@storybook/addon-essentials": "^8.4.4",
101
101
  "@storybook/web-components": "^8.4.4",
102
102
  "@storybook/web-components-vite": "^8.4.4",
103
103
  "@types/mocha": "^10.0.7",
104
104
  "@types/node": "^22.0.0",
105
+ "@uswds/uswds": "^3.10.0",
105
106
  "@web/test-runner": "^0.19.0",
106
107
  "browser-sync": "^3.0.3",
107
108
  "husky": "^9.0.11",
@@ -1,4 +1,4 @@
1
- import { attr, css, element, html, listen, query } from "@joist/element";
1
+ import { attr, css, element, html, listen, query, ready } from "@joist/element";
2
2
 
3
3
  declare global {
4
4
  interface HTMLElementTagNameMap {
@@ -18,6 +18,10 @@ export type ButtonVariant = (typeof BUTTON_VARIANTS)[number];
18
18
 
19
19
  @element({
20
20
  tagName: "usa-button",
21
+ shadowDomOpts: {
22
+ mode: "open",
23
+ delegatesFocus: true,
24
+ },
21
25
  shadowDom: [
22
26
  css`
23
27
  :host {
@@ -182,9 +186,20 @@ export class USAButtonElement extends HTMLElement {
182
186
  @attr()
183
187
  accessor variant: ButtonVariant = "primary";
184
188
 
189
+ @attr()
190
+ accessor value = "";
191
+
192
+ accessor tabIndex = 0;
193
+
185
194
  #internals = this.attachInternals();
186
195
  #button = query("button");
187
196
 
197
+ @ready()
198
+ onReady() {
199
+ const input = this.#button();
200
+ input.autofocus = this.autofocus;
201
+ }
202
+
188
203
  @listen("keydown", () => document.body)
189
204
  onKeyDown(e: KeyboardEvent) {
190
205
  if (this.type === "submit") {
package/src/lib/define.ts CHANGED
@@ -4,7 +4,7 @@ import "./button/button.element.js";
4
4
  import "./checkbox/checkbox.element.js";
5
5
  import "./description/description.element.js";
6
6
  import "./file-input/file-input.element.js";
7
- import "./file-input/file-input-preview.element.js";
7
+ import "./file-input/file-input-preview/file-input-preview.element.js";
8
8
  import "./icon/icon.element.js";
9
9
  import "./input/input.element.js";
10
10
  import "./input-mask/input-mask.element.js";
@@ -16,3 +16,9 @@ import "./select/select-option.element.js";
16
16
  import "./tag/tag.element.js";
17
17
  import "./accordion/accordion.element.js";
18
18
  import "./side-nav/side-nav.element.js";
19
+ import "./summary-box/summary-box.element.js";
20
+ import "./step-indicator/step-indicator.element.js";
21
+ import "./step-indicator/step/step-indicator-step.element.js";
22
+ import "./modal/modal.element.js";
23
+ import "./modal/modal-close/modal-close.element.js";
24
+ import "./modal/modal-heading/modal-heading.element.js";
@@ -61,14 +61,23 @@ declare global {
61
61
  margin-top: 1px;
62
62
  }
63
63
  `,
64
- html`<slot class="preview-heading"></slot>`,
64
+ html`
65
+ <template>
66
+ <div class="preview-item">
67
+ <img height="40" width="40" aria-hidden="true" />
68
+ </div>
69
+ </template>
70
+
71
+ <slot class="preview-heading"></slot>
72
+ `,
65
73
  ],
66
74
  })
67
75
  export class USAFileInputPreviewElement extends HTMLElement {
68
76
  @observe()
69
77
  accessor files: FileList | null = null;
70
78
 
71
- #items = new Map<string, HTMLElement>();
79
+ #items = new Map<string, Element>();
80
+ #template = query("template");
72
81
 
73
82
  connectedCallback() {
74
83
  this.onChange();
@@ -76,7 +85,9 @@ export class USAFileInputPreviewElement extends HTMLElement {
76
85
 
77
86
  @effect()
78
87
  onChange() {
79
- if (this.files) {
88
+ const template = this.#template();
89
+
90
+ if (this.files && this.files.length) {
80
91
  this.hidden = false;
81
92
 
82
93
  let names = new Set<string>();
@@ -85,13 +96,15 @@ export class USAFileInputPreviewElement extends HTMLElement {
85
96
  names.add(file.name);
86
97
 
87
98
  if (!this.#items.has(file.name)) {
88
- const item = document.createElement("div");
99
+ const clone = template.content.cloneNode(true);
100
+ const item = clone.childNodes[1] as Element;
101
+
89
102
  item.id = file.name;
90
- item.className = "preview-item";
91
103
 
92
- const img = createImagePreview(file);
104
+ const img = item.querySelector("img")!;
105
+ img.src = URL.createObjectURL(file);
93
106
 
94
- item.append(img, document.createTextNode(file.name));
107
+ item.append(document.createTextNode(file.name));
95
108
 
96
109
  this.shadowRoot!.append(item);
97
110
  this.#items.set(file.name, item);
@@ -109,13 +122,3 @@ export class USAFileInputPreviewElement extends HTMLElement {
109
122
  }
110
123
  }
111
124
  }
112
-
113
- function createImagePreview(file: File) {
114
- const img = new Image();
115
- img.height = 40;
116
- img.width = 40;
117
- img.src = URL.createObjectURL(file);
118
- img.ariaHidden = "true";
119
-
120
- return img;
121
- }
@@ -1,6 +1,3 @@
1
- import "./file-input-preview.element.js";
2
- import "../link/link.element.js";
3
-
4
1
  import { attr, css, element, html, listen, query } from "@joist/element";
5
2
  import { effect, observe } from "@joist/observable";
6
3
 
@@ -22,6 +19,7 @@ declare global {
22
19
  display: block;
23
20
  max-width: 30rem;
24
21
  position: relative;
22
+ margin-bottom: 1.5rem;
25
23
  }
26
24
 
27
25
  label {
@@ -30,18 +28,23 @@ declare global {
30
28
 
31
29
  input {
32
30
  cursor: pointer;
33
- height: 100%;
34
31
  left: 0;
35
32
  margin: 0;
36
33
  max-width: none;
37
34
  position: absolute;
38
35
  text-indent: -999em;
39
- top: 0;
40
36
  width: 100%;
41
37
  z-index: 1;
38
+ bottom: 0;
39
+ top: 0;
42
40
  }
43
41
 
44
- label slot {
42
+ input:focus {
43
+ outline: 0.25rem solid #2491ff;
44
+ outline-offset: 0;
45
+ }
46
+
47
+ label slot.label {
45
48
  font-size: 1.06rem;
46
49
  line-height: 1.3;
47
50
  display: block;
@@ -51,30 +54,39 @@ declare global {
51
54
 
52
55
  .box {
53
56
  border: 1px dashed #adadad;
54
- display: block;
57
+ display: flex;
55
58
  font-size: 0.93rem;
56
59
  position: relative;
57
60
  text-align: center;
58
61
  width: 100%;
59
62
  max-width: 30rem;
60
- padding: 2rem 1rem;
63
+ height: 5.2rem;
64
+ align-items: center;
65
+ justify-content: center;
66
+ }
67
+
68
+ .container {
69
+ position: relative;
61
70
  }
62
71
  `,
63
72
  html`
64
73
  <label>
65
- <slot></slot>
66
- <input type="file" />
67
- </label>
74
+ <slot class="label"></slot>
75
+
76
+ <div class="container">
77
+ <input type="file" />
68
78
 
69
- <div class="box">
70
- <slot name="description">
71
- Drag file here or <usa-link>choose from folder</usa-link>
72
- </slot>
73
- </div>
79
+ <div class="box">
80
+ <slot name="description">
81
+ Drag file here or <usa-link>choose from folder</usa-link>
82
+ </slot>
83
+ </div>
74
84
 
75
- <usa-file-input-preview>
76
- Selected file <usa-link>Change file</usa-link>
77
- </usa-file-input-preview>
85
+ <usa-file-input-preview>
86
+ Selected file <usa-link>Change file</usa-link>
87
+ </usa-file-input-preview>
88
+ </div>
89
+ </label>
78
90
  `,
79
91
  ],
80
92
  })
@@ -125,14 +137,14 @@ export class USAFileInputElement extends HTMLElement {
125
137
 
126
138
  const formData = new FormData();
127
139
 
128
- if (input.files) {
140
+ if (input.files && input.files.length) {
129
141
  box.style.display = "none";
130
142
 
131
143
  for (let file of input.files) {
132
144
  formData.append(this.name, file);
133
145
  }
134
146
  } else {
135
- box.style.display = "block";
147
+ box.style.display = "flex";
136
148
  }
137
149
 
138
150
  this.#internals.setFormValue(formData);
@@ -26,8 +26,6 @@ const meta = {
26
26
  </div>
27
27
  </usa-file-input>
28
28
 
29
- <br />
30
-
31
29
  <usa-button type="submit">SUBMIT</usa-button>
32
30
  </form>
33
31
  `;
@@ -1,4 +1,4 @@
1
- import { attr, css, element, html, listen, query } from "@joist/element";
1
+ import { attr, css, element, html, listen, query, ready } from "@joist/element";
2
2
  import { effect, observe } from "@joist/observable";
3
3
 
4
4
  import { MaskableElement } from "../input-mask/maskable.element.js";
@@ -11,6 +11,10 @@ declare global {
11
11
 
12
12
  @element({
13
13
  tagName: "usa-input",
14
+ shadowDomOpts: {
15
+ mode: "open",
16
+ delegatesFocus: true,
17
+ },
14
18
  shadowDom: [
15
19
  css`
16
20
  * {
@@ -18,13 +22,6 @@ declare global {
18
22
  }
19
23
 
20
24
  :host {
21
- font-family:
22
- Source Sans Pro Web,
23
- Helvetica Neue,
24
- Helvetica,
25
- Roboto,
26
- Arial,
27
- sans-serif;
28
25
  font-size: 1.06rem;
29
26
  line-height: 1.3;
30
27
  display: block;
@@ -98,6 +95,12 @@ export class USATextInputElement
98
95
  input.setSelectionRange(start, end);
99
96
  }
100
97
 
98
+ @ready()
99
+ onReady() {
100
+ const input = this.#input();
101
+ input.autofocus = this.autofocus;
102
+ }
103
+
101
104
  @effect()
102
105
  onChange() {
103
106
  const input = this.#input();
@@ -0,0 +1,55 @@
1
+ import { attr, css, element, html } from "@joist/element";
2
+
3
+ declare global {
4
+ interface HTMLElementTagNameMap {
5
+ "usa-modal-close": USAModalCloseElement;
6
+ }
7
+ }
8
+
9
+ @element({
10
+ tagName: "usa-modal-close",
11
+ shadowDom: [
12
+ css`
13
+ * {
14
+ box-sizing: border-box;
15
+ }
16
+
17
+ :host {
18
+ display: block;
19
+ position: absolute;
20
+ top: 1rem;
21
+ right: 1rem;
22
+ }
23
+
24
+ button {
25
+ display: flex;
26
+ align-items: center;
27
+ justify-content: center;
28
+ background: none;
29
+ border: none;
30
+ cursor: pointer;
31
+ height: 100%;
32
+ width: 100%;
33
+ border-radius: 100%;
34
+ padding: 0;
35
+ margin: 0;
36
+ }
37
+
38
+ button:focus {
39
+ outline: 0.25rem solid #2491ff;
40
+ outline-offset: 0;
41
+ }
42
+ `,
43
+ html`
44
+ <button aria-label="close modal">
45
+ <usa-icon icon="close"></usa-icon>
46
+ </button>
47
+ `,
48
+ ],
49
+ })
50
+ export class USAModalCloseElement extends HTMLElement {
51
+ @attr({
52
+ name: "modal-action",
53
+ })
54
+ accessor modalAction = "close";
55
+ }
@@ -0,0 +1,15 @@
1
+ import "./modal-close.element.js";
2
+
3
+ import { fixture, html, assert } from "@open-wc/testing";
4
+
5
+ import { USAModalCloseElement } from "./modal-close.element.js";
6
+
7
+ describe("usa-modal-close", () => {
8
+ it("should be accessible", async () => {
9
+ const modalClose = await fixture<USAModalCloseElement>(html`
10
+ <usa-modal-close>Hello World</usa-modal-close>
11
+ `);
12
+
13
+ return assert.isAccessible(modalClose);
14
+ });
15
+ });
@@ -0,0 +1,38 @@
1
+ import { css, element, html } from "@joist/element";
2
+
3
+ declare global {
4
+ interface HTMLElementTagNameMap {
5
+ "usa-modal-heading": USAModalHeadingElement;
6
+ }
7
+ }
8
+
9
+ @element({
10
+ tagName: "usa-modal-heading",
11
+ shadowDom: [
12
+ css`
13
+ :host {
14
+ font-family:
15
+ Merriweather Web,
16
+ Georgia,
17
+ Cambria,
18
+ Times New Roman,
19
+ Times,
20
+ serif !important;
21
+
22
+ font-size: 1.22rem;
23
+ }
24
+
25
+ h2 {
26
+ margin-top: 0;
27
+ line-height: 1.4;
28
+ font-size: 1.45rem;
29
+ }
30
+ `,
31
+ html`
32
+ <h2>
33
+ <slot></slot>
34
+ </h2>
35
+ `,
36
+ ],
37
+ })
38
+ export class USAModalHeadingElement extends HTMLElement {}
@@ -0,0 +1,15 @@
1
+ import "./modal-heading.element.js";
2
+
3
+ import { fixture, html, assert } from "@open-wc/testing";
4
+
5
+ import { USAModalHeadingElement } from "./modal-heading.element.js";
6
+
7
+ describe("usa-modal-heading", () => {
8
+ it("should be accessible", async () => {
9
+ const modalHeading = await fixture<USAModalHeadingElement>(html`
10
+ <usa-modal-heading>Hello World</usa-modal-heading>
11
+ `);
12
+
13
+ return assert.isAccessible(modalHeading);
14
+ });
15
+ });
@@ -0,0 +1,85 @@
1
+ import { css, element, html, listen, query } from "@joist/element";
2
+
3
+ declare global {
4
+ interface HTMLElementTagNameMap {
5
+ "usa-modal": USAModalElement;
6
+ }
7
+ }
8
+
9
+ @element({
10
+ tagName: "usa-modal",
11
+ shadowDom: [
12
+ css`
13
+ * {
14
+ box-sizing: border-box;
15
+ }
16
+
17
+ :host {
18
+ display: contents;
19
+ }
20
+
21
+ dialog {
22
+ border: none;
23
+ font-size: 1.06rem;
24
+ line-height: 1.5;
25
+ border-radius: 0.5rem;
26
+ background: #fff;
27
+ color: #1b1b1b;
28
+ max-width: 30rem;
29
+ padding-top: 4rem;
30
+ padding-left: 4rem;
31
+ padding-right: 4rem;
32
+ padding-bottom: 2rem;
33
+ width: 100%;
34
+ }
35
+
36
+ ::backdrop {
37
+ background: rgba(0, 0, 0, 0.7);
38
+ }
39
+ `,
40
+ html`
41
+ <dialog>
42
+ <slot></slot>
43
+ </dialog>
44
+ `,
45
+ ],
46
+ })
47
+ export class USAModalElement extends HTMLElement {
48
+ modalAction: null | string = null;
49
+
50
+ #dialog = query("dialog");
51
+
52
+ openModal() {
53
+ const dialog = this.#dialog();
54
+
55
+ dialog.showModal();
56
+ }
57
+
58
+ closeModal() {
59
+ const dialog = this.#dialog();
60
+
61
+ dialog.close();
62
+ }
63
+
64
+ @listen("click", () => document.body)
65
+ onGlobalModalAction(e: Event) {
66
+ if (e.target instanceof Element) {
67
+ const modalTarget = e.target.getAttribute("modal-target");
68
+
69
+ if (modalTarget === this.id) {
70
+ this.openModal();
71
+ }
72
+ }
73
+ }
74
+
75
+ @listen("click", (host) => host)
76
+ onModalAction(e: Event) {
77
+ if (e.target instanceof Element) {
78
+ this.modalAction = e.target.getAttribute("modal-action");
79
+
80
+ this.closeModal();
81
+
82
+ this.dispatchEvent(new Event("usa::modal::close"));
83
+ }
84
+ }
85
+ }
@@ -0,0 +1,41 @@
1
+ import type { Meta, StoryObj } from "@storybook/web-components";
2
+ import { html } from "lit";
3
+
4
+ import type { USAModalElement } from "./modal.element.js";
5
+
6
+ // More on how to set up stories at: https://storybook.js.org/docs/writing-stories
7
+ const meta = {
8
+ title: "usa-modal",
9
+ tags: ["autodocs"],
10
+ render() {
11
+ return html`
12
+ <usa-button modal-target="mymodal">Open Modal</usa-button>
13
+
14
+ <usa-modal id="mymodal">
15
+ <usa-modal-close></usa-modal-close>
16
+
17
+ <usa-modal-heading>
18
+ Are you sure you want to continue?
19
+ </usa-modal-heading>
20
+
21
+ <usa-input placeholder="foo@email.com" autofocus>
22
+ Enter your email to continue
23
+ </usa-input>
24
+
25
+ <usa-button modal-action="confirm">Yes I am sure</usa-button>
26
+ <usa-button modal-action="cancel" variant="outline">Cancel</usa-button>
27
+ </usa-modal>
28
+ `;
29
+ },
30
+ argTypes: {},
31
+ args: {},
32
+ } satisfies Meta<USAModalElement>;
33
+
34
+ export default meta;
35
+
36
+ type Story = StoryObj<USAModalElement>;
37
+
38
+ // More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
39
+ export const Primary: Story = {
40
+ args: {},
41
+ };
@@ -0,0 +1,25 @@
1
+ import "./modal.element.js";
2
+ import "./modal-heading/modal-heading.element.js";
3
+ import "./modal-close/modal-close.element.js";
4
+
5
+ import { fixture, html, assert } from "@open-wc/testing";
6
+
7
+ import { USAModalElement } from "./modal.element.js";
8
+
9
+ describe("usa-modal", () => {
10
+ it("should be accessible", async () => {
11
+ const modal = await fixture<USAModalElement>(html`
12
+ <usa-modal open>
13
+ <usa-modal-close></usa-modal-close>
14
+
15
+ <usa-modal-heading>
16
+ Are you sure you want to continue?
17
+ </usa-modal-heading>
18
+
19
+ <p>This is some other example of content</p>
20
+ </usa-modal>
21
+ `);
22
+
23
+ return assert.isAccessible(modal);
24
+ });
25
+ });
@@ -17,6 +17,7 @@ declare global {
17
17
  flex-direction: column;
18
18
  gap: 1rem;
19
19
  max-width: 30rem;
20
+ margin-bottom: 1.5rem;
20
21
  }
21
22
 
22
23
  label {
@@ -75,10 +76,6 @@ declare global {
75
76
  slot {
76
77
  display: flex;
77
78
  }
78
-
79
- slot#main {
80
- margin-bottom: 0.5rem;
81
- }
82
79
  `,
83
80
  html`<slot id="main"></slot>`,
84
81
  ],
@@ -25,6 +25,7 @@ declare global {
25
25
  position: relative;
26
26
  width: 100%;
27
27
  max-width: 30rem;
28
+ margin-bottom: 1.5rem;
28
29
  }
29
30
 
30
31
  select {