@noctuatech/uswds 0.0.1

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 (187) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +25 -0
  3. package/package.json +89 -0
  4. package/src/lib/alert/alert-types.ts +33 -0
  5. package/src/lib/alert/alert.element.ts +105 -0
  6. package/src/lib/alert/alert.stories.ts +63 -0
  7. package/src/lib/alert/alert.test.ts +23 -0
  8. package/src/lib/button/button.element.ts +224 -0
  9. package/src/lib/button/button.stories.ts +34 -0
  10. package/src/lib/button/button.test.ts +17 -0
  11. package/src/lib/checkbox/checkbox.element.ts +166 -0
  12. package/src/lib/checkbox/checkbox.stories.ts +57 -0
  13. package/src/lib/checkbox/checkbox.test.ts +47 -0
  14. package/src/lib/config/config.element.ts +31 -0
  15. package/src/lib/config/config.test.ts +15 -0
  16. package/src/lib/define.ts +14 -0
  17. package/src/lib/description/description.element.ts +22 -0
  18. package/src/lib/description/description.test.ts +15 -0
  19. package/src/lib/file-input/file-input-preview.element.ts +121 -0
  20. package/src/lib/file-input/file-input-preview.test.ts +95 -0
  21. package/src/lib/file-input/file-input.element.ts +140 -0
  22. package/src/lib/file-input/file-input.stories.ts +46 -0
  23. package/src/lib/file-input/file-input.test.ts +47 -0
  24. package/src/lib/icon/icon-types.ts +263 -0
  25. package/src/lib/icon/icon.element.ts +65 -0
  26. package/src/lib/icon/icon.stories.ts +50 -0
  27. package/src/lib/input/input.element.ts +138 -0
  28. package/src/lib/input/input.stories.ts +30 -0
  29. package/src/lib/input/input.test.ts +48 -0
  30. package/src/lib/input-mask/format.ts +56 -0
  31. package/src/lib/input-mask/input-mask.element.ts +93 -0
  32. package/src/lib/input-mask/input-mask.stories.ts +38 -0
  33. package/src/lib/input-mask/input-mask.test.ts +106 -0
  34. package/src/lib/input-mask/maskable.element.ts +5 -0
  35. package/src/lib/link/link.element.ts +62 -0
  36. package/src/lib/link/link.stories.ts +30 -0
  37. package/src/lib/radio/radio-option.element.ts +46 -0
  38. package/src/lib/radio/radio-option.test.ts +20 -0
  39. package/src/lib/radio/radio.element.ts +152 -0
  40. package/src/lib/radio/radio.stories.ts +47 -0
  41. package/src/lib/radio/radio.test.ts +174 -0
  42. package/src/lib/select/select-option.element.ts +40 -0
  43. package/src/lib/select/select.element.ts +121 -0
  44. package/src/lib/select/select.stories.ts +33 -0
  45. package/src/lib/select/select.test.ts +113 -0
  46. package/src/lib/tag/tag.element.ts +46 -0
  47. package/src/lib/tag/tag.stories.ts +31 -0
  48. package/src/lib/tag/tag.test.ts +15 -0
  49. package/src/lib.ts +13 -0
  50. package/target/lib/alert/alert-types.d.ts +7 -0
  51. package/target/lib/alert/alert-types.js +25 -0
  52. package/target/lib/alert/alert-types.js.map +1 -0
  53. package/target/lib/alert/alert.element.d.ts +11 -0
  54. package/target/lib/alert/alert.element.js +124 -0
  55. package/target/lib/alert/alert.element.js.map +1 -0
  56. package/target/lib/alert/alert.stories.d.ts +11 -0
  57. package/target/lib/alert/alert.stories.js +56 -0
  58. package/target/lib/alert/alert.stories.js.map +1 -0
  59. package/target/lib/alert/alert.test.d.ts +1 -0
  60. package/target/lib/alert/alert.test.js +20 -0
  61. package/target/lib/alert/alert.test.js.map +1 -0
  62. package/target/lib/button/button.element.d.ts +17 -0
  63. package/target/lib/button/button.element.js +259 -0
  64. package/target/lib/button/button.element.js.map +1 -0
  65. package/target/lib/button/button.stories.d.ts +12 -0
  66. package/target/lib/button/button.stories.js +25 -0
  67. package/target/lib/button/button.stories.js.map +1 -0
  68. package/target/lib/button/button.test.d.ts +1 -0
  69. package/target/lib/button/button.test.js +14 -0
  70. package/target/lib/button/button.test.js.map +1 -0
  71. package/target/lib/checkbox/checkbox.element.d.ts +16 -0
  72. package/target/lib/checkbox/checkbox.element.js +205 -0
  73. package/target/lib/checkbox/checkbox.element.js.map +1 -0
  74. package/target/lib/checkbox/checkbox.stories.d.ts +31 -0
  75. package/target/lib/checkbox/checkbox.stories.js +46 -0
  76. package/target/lib/checkbox/checkbox.stories.js.map +1 -0
  77. package/target/lib/checkbox/checkbox.test.d.ts +1 -0
  78. package/target/lib/checkbox/checkbox.test.js +38 -0
  79. package/target/lib/checkbox/checkbox.test.js.map +1 -0
  80. package/target/lib/config/config.element.d.ts +8 -0
  81. package/target/lib/config/config.element.js +57 -0
  82. package/target/lib/config/config.element.js.map +1 -0
  83. package/target/lib/config/config.test.d.ts +1 -0
  84. package/target/lib/config/config.test.js +11 -0
  85. package/target/lib/config/config.test.js.map +1 -0
  86. package/target/lib/define.d.ts +14 -0
  87. package/target/lib/define.js +15 -0
  88. package/target/lib/define.js.map +1 -0
  89. package/target/lib/description/description.element.d.ts +7 -0
  90. package/target/lib/description/description.element.js +34 -0
  91. package/target/lib/description/description.element.js.map +1 -0
  92. package/target/lib/description/description.test.d.ts +1 -0
  93. package/target/lib/description/description.test.js +11 -0
  94. package/target/lib/description/description.test.js.map +1 -0
  95. package/target/lib/file-input/file-input-preview.element.d.ts +11 -0
  96. package/target/lib/file-input/file-input-preview.element.js +136 -0
  97. package/target/lib/file-input/file-input-preview.element.js.map +1 -0
  98. package/target/lib/file-input/file-input-preview.test.d.ts +2 -0
  99. package/target/lib/file-input/file-input-preview.test.js +67 -0
  100. package/target/lib/file-input/file-input-preview.test.js.map +1 -0
  101. package/target/lib/file-input/file-input.element.d.ts +18 -0
  102. package/target/lib/file-input/file-input.element.js +180 -0
  103. package/target/lib/file-input/file-input.element.js.map +1 -0
  104. package/target/lib/file-input/file-input.stories.d.ts +12 -0
  105. package/target/lib/file-input/file-input.stories.js +36 -0
  106. package/target/lib/file-input/file-input.stories.js.map +1 -0
  107. package/target/lib/file-input/file-input.test.d.ts +1 -0
  108. package/target/lib/file-input/file-input.test.js +37 -0
  109. package/target/lib/file-input/file-input.test.js.map +1 -0
  110. package/target/lib/icon/icon-types.d.ts +2 -0
  111. package/target/lib/icon/icon-types.js +262 -0
  112. package/target/lib/icon/icon-types.js.map +1 -0
  113. package/target/lib/icon/icon.element.d.ts +12 -0
  114. package/target/lib/icon/icon.element.js +84 -0
  115. package/target/lib/icon/icon.element.js.map +1 -0
  116. package/target/lib/icon/icon.stories.d.ts +12 -0
  117. package/target/lib/icon/icon.stories.js +39 -0
  118. package/target/lib/icon/icon.stories.js.map +1 -0
  119. package/target/lib/input/input.element.d.ts +19 -0
  120. package/target/lib/input/input.element.js +166 -0
  121. package/target/lib/input/input.element.js.map +1 -0
  122. package/target/lib/input/input.stories.d.ts +12 -0
  123. package/target/lib/input/input.stories.js +23 -0
  124. package/target/lib/input/input.stories.js.map +1 -0
  125. package/target/lib/input/input.test.d.ts +1 -0
  126. package/target/lib/input/input.test.js +38 -0
  127. package/target/lib/input/input.test.js.map +1 -0
  128. package/target/lib/input-mask/format.d.ts +15 -0
  129. package/target/lib/input-mask/format.js +47 -0
  130. package/target/lib/input-mask/format.js.map +1 -0
  131. package/target/lib/input-mask/input-mask.element.d.ts +12 -0
  132. package/target/lib/input-mask/input-mask.element.js +111 -0
  133. package/target/lib/input-mask/input-mask.element.js.map +1 -0
  134. package/target/lib/input-mask/input-mask.stories.d.ts +14 -0
  135. package/target/lib/input-mask/input-mask.stories.js +31 -0
  136. package/target/lib/input-mask/input-mask.stories.js.map +1 -0
  137. package/target/lib/input-mask/input-mask.test.d.ts +2 -0
  138. package/target/lib/input-mask/input-mask.test.js +85 -0
  139. package/target/lib/input-mask/input-mask.test.js.map +1 -0
  140. package/target/lib/input-mask/maskable.element.d.ts +5 -0
  141. package/target/lib/input-mask/maskable.element.js +2 -0
  142. package/target/lib/input-mask/maskable.element.js.map +1 -0
  143. package/target/lib/link/link.element.d.ts +13 -0
  144. package/target/lib/link/link.element.js +98 -0
  145. package/target/lib/link/link.element.js.map +1 -0
  146. package/target/lib/link/link.stories.d.ts +16 -0
  147. package/target/lib/link/link.stories.js +23 -0
  148. package/target/lib/link/link.stories.js.map +1 -0
  149. package/target/lib/radio/radio-option.element.d.ts +13 -0
  150. package/target/lib/radio/radio-option.element.js +63 -0
  151. package/target/lib/radio/radio-option.element.js.map +1 -0
  152. package/target/lib/radio/radio-option.test.d.ts +2 -0
  153. package/target/lib/radio/radio-option.test.js +15 -0
  154. package/target/lib/radio/radio-option.test.js.map +1 -0
  155. package/target/lib/radio/radio.element.d.ts +18 -0
  156. package/target/lib/radio/radio.element.js +177 -0
  157. package/target/lib/radio/radio.element.js.map +1 -0
  158. package/target/lib/radio/radio.stories.d.ts +12 -0
  159. package/target/lib/radio/radio.stories.js +40 -0
  160. package/target/lib/radio/radio.stories.js.map +1 -0
  161. package/target/lib/radio/radio.test.d.ts +2 -0
  162. package/target/lib/radio/radio.test.js +147 -0
  163. package/target/lib/radio/radio.test.js.map +1 -0
  164. package/target/lib/select/select-option.element.d.ts +11 -0
  165. package/target/lib/select/select-option.element.js +58 -0
  166. package/target/lib/select/select-option.element.js.map +1 -0
  167. package/target/lib/select/select.element.d.ts +16 -0
  168. package/target/lib/select/select.element.js +144 -0
  169. package/target/lib/select/select.element.js.map +1 -0
  170. package/target/lib/select/select.stories.d.ts +12 -0
  171. package/target/lib/select/select.stories.js +26 -0
  172. package/target/lib/select/select.stories.js.map +1 -0
  173. package/target/lib/select/select.test.d.ts +2 -0
  174. package/target/lib/select/select.test.js +89 -0
  175. package/target/lib/select/select.test.js.map +1 -0
  176. package/target/lib/tag/tag.element.d.ts +10 -0
  177. package/target/lib/tag/tag.element.js +66 -0
  178. package/target/lib/tag/tag.element.js.map +1 -0
  179. package/target/lib/tag/tag.stories.d.ts +19 -0
  180. package/target/lib/tag/tag.stories.js +25 -0
  181. package/target/lib/tag/tag.stories.js.map +1 -0
  182. package/target/lib/tag/tag.test.d.ts +1 -0
  183. package/target/lib/tag/tag.test.js +11 -0
  184. package/target/lib/tag/tag.test.js.map +1 -0
  185. package/target/lib.d.ts +13 -0
  186. package/target/lib.js +14 -0
  187. package/target/lib.js.map +1 -0
@@ -0,0 +1,140 @@
1
+ import "./file-input-preview.element.js";
2
+ import "../link/link.element.js";
3
+
4
+ import { attr, css, element, html, listen, query } from "@joist/element";
5
+ import { effect, observe } from "@joist/observable";
6
+
7
+ declare global {
8
+ interface HTMLElementTagNameMap {
9
+ "usa-file-input": USAFileInputElement;
10
+ }
11
+ }
12
+
13
+ @element({
14
+ tagName: "usa-file-input",
15
+ shadowDom: [
16
+ css`
17
+ * {
18
+ box-sizing: border-box;
19
+ }
20
+
21
+ :host {
22
+ display: block;
23
+ max-width: 30rem;
24
+ position: relative;
25
+ }
26
+
27
+ label {
28
+ display: block;
29
+ }
30
+
31
+ input {
32
+ cursor: pointer;
33
+ height: 100%;
34
+ left: 0;
35
+ margin: 0;
36
+ max-width: none;
37
+ position: absolute;
38
+ text-indent: -999em;
39
+ top: 0;
40
+ width: 100%;
41
+ z-index: 1;
42
+ }
43
+
44
+ label slot {
45
+ font-size: 1.06rem;
46
+ line-height: 1.3;
47
+ display: block;
48
+ font-weight: 400;
49
+ margin-bottom: 0.5rem;
50
+ }
51
+
52
+ .box {
53
+ border: 1px dashed #adadad;
54
+ display: block;
55
+ font-size: 0.93rem;
56
+ position: relative;
57
+ text-align: center;
58
+ width: 100%;
59
+ max-width: 30rem;
60
+ padding: 2rem 1rem;
61
+ }
62
+ `,
63
+ html`
64
+ <label>
65
+ <slot></slot>
66
+ <input type="file" />
67
+ </label>
68
+
69
+ <div class="box">
70
+ <slot name="description">
71
+ Drag file here or <usa-link>choose from folder</usa-link>
72
+ </slot>
73
+ </div>
74
+
75
+ <usa-file-input-preview>
76
+ Selected file <usa-link>Change file</usa-link>
77
+ </usa-file-input-preview>
78
+ `,
79
+ ],
80
+ })
81
+ export class USAFileInputElement extends HTMLElement {
82
+ static formAssociated = true;
83
+
84
+ @attr()
85
+ accessor name = "";
86
+
87
+ @attr()
88
+ accessor multiple = true;
89
+
90
+ @attr()
91
+ accessor accept = "";
92
+
93
+ @observe()
94
+ accessor files: FileList | null = null;
95
+
96
+ #internals = this.attachInternals();
97
+ #input = query("input");
98
+ #box = query(".box");
99
+ #preview = query("usa-file-input-preview");
100
+
101
+ @effect()
102
+ onChange() {
103
+ const input = this.#input();
104
+
105
+ if (this.files) {
106
+ input.files = this.files;
107
+ this.onInputChange();
108
+ }
109
+ }
110
+
111
+ attributeChangedCallback() {
112
+ const input = this.#input();
113
+ input.name = this.name;
114
+ input.multiple = this.multiple;
115
+ input.accept = this.accept;
116
+ }
117
+
118
+ @listen("change")
119
+ onInputChange() {
120
+ const input = this.#input();
121
+ const box = this.#box();
122
+ const preview = this.#preview();
123
+
124
+ preview.files = input.files;
125
+
126
+ const formData = new FormData();
127
+
128
+ if (input.files) {
129
+ box.style.display = "none";
130
+
131
+ for (let file of input.files) {
132
+ formData.append(this.name, file);
133
+ }
134
+ } else {
135
+ box.style.display = "block";
136
+ }
137
+
138
+ this.#internals.setFormValue(formData);
139
+ }
140
+ }
@@ -0,0 +1,46 @@
1
+ import type { Meta, StoryObj } from "@storybook/web-components";
2
+ import { html } from "lit";
3
+
4
+ import type { USAFileInputElement } from "./file-input.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-file-input",
9
+ tags: ["autodocs"],
10
+ render(args) {
11
+ function onSubmit(e: Event) {
12
+ e.preventDefault();
13
+
14
+ const data = new FormData(e.target as HTMLFormElement);
15
+
16
+ console.log(data.getAll("upload"));
17
+ }
18
+
19
+ return html`
20
+ <form @submit=${onSubmit}>
21
+ <usa-file-input name="upload">
22
+ Input accepts a single file
23
+
24
+ <div slot="description">
25
+ Drag file here or <usa-link>choose from folder</usa-link>
26
+ </div>
27
+ </usa-file-input>
28
+
29
+ <br />
30
+
31
+ <usa-button type="submit">SUBMIT</usa-button>
32
+ </form>
33
+ `;
34
+ },
35
+ argTypes: {},
36
+ args: {},
37
+ } satisfies Meta<USAFileInputElement>;
38
+
39
+ export default meta;
40
+
41
+ type Story = StoryObj<USAFileInputElement>;
42
+
43
+ // More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
44
+ export const Primary: Story = {
45
+ args: {},
46
+ };
@@ -0,0 +1,47 @@
1
+ import "./file-input.element.js";
2
+
3
+ import { fixture, html, assert } from "@open-wc/testing";
4
+
5
+ import { USAFileInputElement } from "./file-input.element.js";
6
+
7
+ describe("usa-file-input", () => {
8
+ it("should be accessible", async () => {
9
+ const fileInput = await fixture<USAFileInputElement>(html`
10
+ <usa-file-input>Hello World</usa-file-input>
11
+ `);
12
+
13
+ return assert.isAccessible(fileInput);
14
+ });
15
+
16
+ it("should submit files with the form", async () => {
17
+ const data = new DataTransfer();
18
+ data.items.add(new File([], "first.txt"));
19
+ data.items.add(new File([], "second.txt"));
20
+
21
+ const form = await fixture<HTMLFormElement>(html`
22
+ <form>
23
+ <usa-file-input name="upload" .files=${data.files}>
24
+ Input accepts a single file
25
+
26
+ <div slot="description">
27
+ Drag file here or <usa-link>choose from folder</usa-link>
28
+ </div>
29
+ </usa-file-input>
30
+
31
+ <button>Submit</button>
32
+ </form>
33
+ `);
34
+
35
+ const formData = new FormData(form);
36
+
37
+ const fileNames = formData.getAll("upload").map((file) => {
38
+ if (file instanceof File) {
39
+ return file.name;
40
+ }
41
+
42
+ return "";
43
+ });
44
+
45
+ assert.deepEqual(fileNames, ["first.txt", "second.txt"]);
46
+ });
47
+ });
@@ -0,0 +1,263 @@
1
+ export const ICON_TYPES = [
2
+ "accessibility_new",
3
+ "accessible_forward",
4
+ "account_balance",
5
+ "account_box",
6
+ "account_circle",
7
+ "add",
8
+ "add_circle",
9
+ "add_circle_outline",
10
+ "alarm",
11
+ "alternate_email",
12
+ "announcement",
13
+ "arrow_back",
14
+ "arrow_downward",
15
+ "arrow_drop_down",
16
+ "arrow_drop_up",
17
+ "arrow_forward",
18
+ "arrow_upward",
19
+ "api",
20
+ "assessment",
21
+ "attach_file",
22
+ "attach_money",
23
+ "autorenew",
24
+ "backpack",
25
+ "bathtub",
26
+ "bedding",
27
+ "bookmark",
28
+ "bug_report",
29
+ "build",
30
+ "calendar_today",
31
+ "campaign",
32
+ "camping",
33
+ "cancel",
34
+ "chat",
35
+ "check",
36
+ "check_box_outline_blank",
37
+ "check_circle",
38
+ "check_circle_outline",
39
+ "checkroom",
40
+ "clean_hands",
41
+ "clothes",
42
+ "close",
43
+ "closed_caption",
44
+ "cloud",
45
+ "code",
46
+ "comment",
47
+ "connect_without_contact",
48
+ "construction",
49
+ "construction_worker",
50
+ "contact_page",
51
+ "content_copy",
52
+ "coronavirus",
53
+ "credit_card",
54
+ "deck",
55
+ "delete",
56
+ "device_thermostat",
57
+ "directions",
58
+ "directions_bike",
59
+ "directions_bus",
60
+ "directions_car",
61
+ "directions_walk",
62
+ "do_not_disturb",
63
+ "do_not_touch",
64
+ "drag_handle",
65
+ "eco",
66
+ "edit",
67
+ "electrical_services",
68
+ "emoji_events",
69
+ "error",
70
+ "error_outline",
71
+ "event",
72
+ "expand_less",
73
+ "expand_more",
74
+ "facebook",
75
+ "fast_forward",
76
+ "fast_rewind",
77
+ "favorite",
78
+ "favorite_border",
79
+ "fax",
80
+ "file_download",
81
+ "file_present",
82
+ "file_upload",
83
+ "filter_alt",
84
+ "filter_list",
85
+ "fingerprint",
86
+ "first_page",
87
+ "flag",
88
+ "flickr",
89
+ "flight",
90
+ "flooding",
91
+ "folder",
92
+ "folder_open",
93
+ "format_quote",
94
+ "format_size",
95
+ "forum",
96
+ "github",
97
+ "grid_view",
98
+ "group_add",
99
+ "groups",
100
+ "hearing",
101
+ "help",
102
+ "help_outline",
103
+ "highlight_off",
104
+ "history",
105
+ "home",
106
+ "hospital",
107
+ "hotel",
108
+ "hourglass_empty",
109
+ "hurricane",
110
+ "identification",
111
+ "image",
112
+ "info",
113
+ "info_outline",
114
+ "insights",
115
+ "instagram",
116
+ "keyboard",
117
+ "label",
118
+ "language",
119
+ "last_page",
120
+ "launch",
121
+ "lightbulb",
122
+ "lightbulb_outline",
123
+ "link",
124
+ "link_off",
125
+ "linkedin",
126
+ "list",
127
+ "local_cafe",
128
+ "local_fire_department",
129
+ "local_gas_station",
130
+ "local_grocery_store",
131
+ "local_hospital",
132
+ "local_laundry_service",
133
+ "local_library",
134
+ "local_offer",
135
+ "local_parking",
136
+ "local_pharmacy",
137
+ "local_police",
138
+ "local_taxi",
139
+ "location_city",
140
+ "location_on",
141
+ "lock",
142
+ "lock_open",
143
+ "lock_outline",
144
+ "login",
145
+ "logout",
146
+ "loop",
147
+ "mail",
148
+ "mail_outline",
149
+ "map",
150
+ "masks",
151
+ "medical_services",
152
+ "menu",
153
+ "military_tech",
154
+ "more_horiz",
155
+ "more_vert",
156
+ "my_location",
157
+ "navigate_before",
158
+ "navigate_next",
159
+ "navigate_far_before",
160
+ "navigate_far_next",
161
+ "near_me",
162
+ "notifications",
163
+ "notifications_active",
164
+ "notifications_none",
165
+ "notifications_off",
166
+ "park",
167
+ "people",
168
+ "person",
169
+ "pets",
170
+ "phone",
171
+ "photo_camera",
172
+ "print",
173
+ "priority_high",
174
+ "public",
175
+ "push_pin",
176
+ "radio_button_unchecked",
177
+ "rain",
178
+ "reduce_capacity",
179
+ "remove",
180
+ "remove_circle",
181
+ "report",
182
+ "restaurant",
183
+ "rss_feed",
184
+ "safety_divider",
185
+ "sanitizer",
186
+ "severe_weather",
187
+ "save_alt",
188
+ "schedule",
189
+ "school",
190
+ "science",
191
+ "search",
192
+ "security",
193
+ "send",
194
+ "sentiment_dissatisfied",
195
+ "sentiment_neutral",
196
+ "sentiment_satisfied",
197
+ "sentiment_satisfied_alt",
198
+ "sentiment_very_dissatisfied",
199
+ "settings",
200
+ "share",
201
+ "shield",
202
+ "shopping_basket",
203
+ "snow",
204
+ "soap",
205
+ "social_distance",
206
+ "sort_arrow",
207
+ "spellcheck",
208
+ "star",
209
+ "star_half",
210
+ "star_outline",
211
+ "store",
212
+ "support",
213
+ "support_agent",
214
+ "text_fields",
215
+ "thumb_down_alt",
216
+ "thumb_up_alt",
217
+ "timer",
218
+ "toggle_off",
219
+ "toggle_on",
220
+ "topic",
221
+ "tornado",
222
+ "translate",
223
+ "trending_down",
224
+ "trending_up",
225
+ "twitter",
226
+ "undo",
227
+ "unfold_less",
228
+ "unfold_more",
229
+ "update",
230
+ "upload_file",
231
+ "verified",
232
+ "verified_user",
233
+ "visibility",
234
+ "visibility_off",
235
+ "volume_off",
236
+ "warning",
237
+ "wash",
238
+ "wifi",
239
+ "work",
240
+ "x",
241
+ "youtube",
242
+ "zoom_in",
243
+ "zoom_out_map",
244
+ "zoom_out",
245
+ "visibility",
246
+ "home",
247
+ "print",
248
+ "search",
249
+ "check_circle",
250
+ "warning",
251
+ "info",
252
+ "cancel",
253
+ "check_circle",
254
+ "warning",
255
+ "info",
256
+ "cancel",
257
+ "help",
258
+ "people",
259
+ "mail",
260
+ "support",
261
+ ] as const;
262
+
263
+ export type USAIcon = (typeof ICON_TYPES)[number];
@@ -0,0 +1,65 @@
1
+ import { attr, css, element, html, query } from "@joist/element";
2
+ import { inject, injectable } from "@joist/di";
3
+
4
+ import { USAIcon } from "./icon-types.js";
5
+ import { USAConfig } from "../config/config.element.js";
6
+
7
+ declare global {
8
+ interface HTMLElementTagNameMap {
9
+ "usa-icon": USAIconElement;
10
+ }
11
+ }
12
+
13
+ @element({
14
+ tagName: "usa-icon",
15
+ shadowDom: [
16
+ css`
17
+ :host {
18
+ display: inline-block;
19
+ height: 2rem;
20
+ position: relative;
21
+ width: 2rem;
22
+ }
23
+
24
+ svg {
25
+ fill: currentColor;
26
+ height: 100%;
27
+ width: 100%;
28
+ }
29
+ `,
30
+ html`
31
+ <svg class="usa-icon" aria-hidden="true" focusable="false" role="img">
32
+ <use></use>
33
+ </svg>
34
+ `,
35
+ ],
36
+ })
37
+ @injectable()
38
+ export class USAIconElement extends HTMLElement {
39
+ @attr()
40
+ accessor icon: USAIcon = "accessibility_new";
41
+
42
+ #use = query("use");
43
+ #config = inject(USAConfig);
44
+ #connected = false;
45
+
46
+ connectedCallback() {
47
+ this.#connected = true;
48
+ this.#updateIcon();
49
+ }
50
+
51
+ attributeChangedCallback() {
52
+ if (this.#connected) {
53
+ this.#updateIcon();
54
+ }
55
+ }
56
+
57
+ #updateIcon() {
58
+ const config = this.#config();
59
+ const use = this.#use();
60
+
61
+ if (this.icon !== use.getAttribute("href")) {
62
+ use.setAttribute("href", `${config.spriteSheet}#${this.icon}`);
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,50 @@
1
+ import type { Meta, StoryObj } from "@storybook/web-components";
2
+ import { html } from "lit";
3
+
4
+ import type { USAIconElement } from "./icon.element.js";
5
+ import { ICON_TYPES } from "./icon-types.js";
6
+
7
+ // More on how to set up stories at: https://storybook.js.org/docs/writing-stories
8
+ const meta = {
9
+ title: "usa-icon",
10
+ tags: [],
11
+ render(args) {
12
+ return html`<usa-icon icon=${args.icon}></usa-icon>`;
13
+ },
14
+ argTypes: {},
15
+ args: {},
16
+ } satisfies Meta<USAIconElement>;
17
+
18
+ export default meta;
19
+
20
+ type Story = StoryObj<USAIconElement>;
21
+
22
+ export const AllIcons: Story = {
23
+ render() {
24
+ async function copyIcon(icon: string) {
25
+ try {
26
+ await navigator.clipboard.writeText(
27
+ `<usa-icon icon=${icon}></usa-icon>`
28
+ );
29
+ alert(`copied markup for ${icon}`);
30
+ } catch (err) {
31
+ console.error("Failed to copy: ", err);
32
+ }
33
+ }
34
+
35
+ return html`<div style="display: flex; flex-wrap: wrap; gap: .75rem;">
36
+ ${html`${ICON_TYPES.map(
37
+ (icon) => html`
38
+ <button
39
+ @click=${() => copyIcon(icon)}
40
+ style="padding: .5rem 1rem .5rem .5rem; cursor: pointer; background: #fff; border-radius: 4px; gap: .5rem; display: flex; align-items: center"
41
+ >
42
+ <usa-icon icon=${icon}></usa-icon>
43
+
44
+ ${icon}
45
+ </button>
46
+ `
47
+ )}`}
48
+ </div>`;
49
+ },
50
+ };