@grantcodes/ui 2.15.7 → 2.16.0

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 (300) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/custom-elements.json +476 -346
  3. package/package.json +23 -23
  4. package/src/components/accordion/accordion-item.component.js +17 -19
  5. package/src/components/accordion/accordion-item.css +40 -44
  6. package/src/components/accordion/accordion.component.js +7 -7
  7. package/src/components/accordion/accordion.css +5 -5
  8. package/src/components/accordion/accordion.js +6 -6
  9. package/src/components/accordion/accordion.react.js +6 -6
  10. package/src/components/accordion/accordion.stories.js +10 -10
  11. package/src/components/accordion/accordion.test.js +100 -100
  12. package/src/components/accordion/index.js +6 -6
  13. package/src/components/app-bar/app-bar.component.js +86 -92
  14. package/src/components/app-bar/app-bar.css +187 -203
  15. package/src/components/app-bar/app-bar.js +3 -3
  16. package/src/components/app-bar/app-bar.react.js +9 -9
  17. package/src/components/app-bar/app-bar.stories.js +20 -20
  18. package/src/components/app-bar/app-bar.test.js +190 -237
  19. package/src/components/app-bar/index.js +1 -1
  20. package/src/components/app-bar/nav-link.component.js +7 -9
  21. package/src/components/app-bar/nav-link.css +23 -28
  22. package/src/components/app-bar/nav-link.js +3 -3
  23. package/src/components/app-bar/nav-link.react.js +6 -6
  24. package/src/components/avatar/avatar.component.js +79 -68
  25. package/src/components/avatar/avatar.css +18 -18
  26. package/src/components/avatar/avatar.js +3 -3
  27. package/src/components/avatar/avatar.react.js +6 -6
  28. package/src/components/avatar/avatar.stories.js +24 -23
  29. package/src/components/avatar/avatar.test.js +128 -84
  30. package/src/components/avatar/index.js +1 -1
  31. package/src/components/badge/badge.component.js +23 -23
  32. package/src/components/badge/badge.css +39 -52
  33. package/src/components/badge/badge.js +3 -3
  34. package/src/components/badge/badge.react.js +6 -6
  35. package/src/components/badge/badge.stories.js +23 -23
  36. package/src/components/badge/badge.test.js +53 -68
  37. package/src/components/badge/index.js +1 -1
  38. package/src/components/breadcrumb/breadcrumb.component.js +95 -96
  39. package/src/components/breadcrumb/breadcrumb.css +42 -44
  40. package/src/components/breadcrumb/breadcrumb.js +6 -9
  41. package/src/components/breadcrumb/breadcrumb.react.js +9 -12
  42. package/src/components/breadcrumb/breadcrumb.stories.js +5 -5
  43. package/src/components/breadcrumb/breadcrumb.test.js +139 -151
  44. package/src/components/breadcrumb/index.js +1 -1
  45. package/src/components/button/button.component.js +101 -103
  46. package/src/components/button/button.css +90 -94
  47. package/src/components/button/button.js +3 -3
  48. package/src/components/button/button.react.js +6 -6
  49. package/src/components/button/button.stories.js +25 -25
  50. package/src/components/button/button.test.js +324 -344
  51. package/src/components/button/index.js +1 -1
  52. package/src/components/button-group/button-group.component.js +18 -18
  53. package/src/components/button-group/button-group.css +16 -16
  54. package/src/components/button-group/button-group.js +3 -3
  55. package/src/components/button-group/button-group.react.js +6 -6
  56. package/src/components/button-group/button-group.stories.js +23 -23
  57. package/src/components/button-group/button-group.test.js +54 -54
  58. package/src/components/button-group/index.js +1 -1
  59. package/src/components/card/card.component.js +31 -33
  60. package/src/components/card/card.css +85 -85
  61. package/src/components/card/card.js +4 -4
  62. package/src/components/card/card.react.js +6 -6
  63. package/src/components/card/card.stories.js +27 -26
  64. package/src/components/card/card.test.js +56 -56
  65. package/src/components/card/index.js +1 -1
  66. package/src/components/code-preview/code-preview.component.js +47 -48
  67. package/src/components/code-preview/code-preview.css +11 -11
  68. package/src/components/code-preview/code-preview.js +3 -3
  69. package/src/components/code-preview/code-preview.react.js +6 -6
  70. package/src/components/code-preview/code-preview.stories.js +29 -29
  71. package/src/components/code-preview/code-preview.test.js +112 -135
  72. package/src/components/code-preview/index.js +1 -1
  73. package/src/components/container/container.component.js +27 -27
  74. package/src/components/container/container.css +22 -22
  75. package/src/components/container/container.js +3 -3
  76. package/src/components/container/container.react.js +6 -6
  77. package/src/components/container/container.stories.js +44 -44
  78. package/src/components/container/container.test.js +54 -66
  79. package/src/components/container/index.js +1 -1
  80. package/src/components/countdown/countdown.component.js +160 -162
  81. package/src/components/countdown/countdown.css +50 -34
  82. package/src/components/countdown/countdown.js +3 -3
  83. package/src/components/countdown/countdown.react.js +6 -6
  84. package/src/components/countdown/countdown.stories.js +34 -34
  85. package/src/components/countdown/index.js +1 -1
  86. package/src/components/cta/cta.component.js +77 -81
  87. package/src/components/cta/cta.css +24 -24
  88. package/src/components/cta/cta.js +3 -3
  89. package/src/components/cta/cta.react.js +6 -6
  90. package/src/components/cta/cta.stories.js +32 -32
  91. package/src/components/cta/index.js +1 -1
  92. package/src/components/dialog/dialog.component.js +95 -58
  93. package/src/components/dialog/dialog.css +45 -50
  94. package/src/components/dialog/dialog.js +3 -3
  95. package/src/components/dialog/dialog.react.js +6 -6
  96. package/src/components/dialog/dialog.stories.js +38 -29
  97. package/src/components/dialog/dialog.test.js +188 -102
  98. package/src/components/dialog/index.js +1 -1
  99. package/src/components/dropdown/dropdown.component.js +220 -201
  100. package/src/components/dropdown/dropdown.css +60 -84
  101. package/src/components/dropdown/dropdown.js +6 -9
  102. package/src/components/dropdown/dropdown.react.js +15 -15
  103. package/src/components/dropdown/dropdown.stories.js +10 -10
  104. package/src/components/dropdown/dropdown.test.js +211 -157
  105. package/src/components/dropdown/index.js +1 -1
  106. package/src/components/dropzone/dropzone.component.js +126 -129
  107. package/src/components/dropzone/dropzone.css +35 -37
  108. package/src/components/dropzone/dropzone.js +3 -3
  109. package/src/components/dropzone/dropzone.react.js +6 -6
  110. package/src/components/dropzone/dropzone.stories.js +28 -28
  111. package/src/components/dropzone/dropzone.test.js +91 -99
  112. package/src/components/dropzone/index.js +1 -1
  113. package/src/components/feature-list/feature-list.component.js +78 -86
  114. package/src/components/feature-list/feature-list.css +37 -37
  115. package/src/components/feature-list/feature-list.js +3 -3
  116. package/src/components/feature-list/feature-list.react.js +6 -6
  117. package/src/components/feature-list/feature-list.stories.js +86 -90
  118. package/src/components/feature-list/index.js +1 -1
  119. package/src/components/footer/footer-column.component.js +7 -7
  120. package/src/components/footer/footer-column.css +22 -22
  121. package/src/components/footer/footer.component.js +19 -19
  122. package/src/components/footer/footer.css +38 -38
  123. package/src/components/footer/footer.js +8 -8
  124. package/src/components/footer/footer.react.js +9 -9
  125. package/src/components/footer/footer.stories.js +9 -9
  126. package/src/components/footer/footer.test.js +101 -111
  127. package/src/components/footer/index.js +2 -2
  128. package/src/components/form-field/form-field.component.js +167 -125
  129. package/src/components/form-field/form-field.css +24 -24
  130. package/src/components/form-field/form-field.js +3 -3
  131. package/src/components/form-field/form-field.react.js +6 -6
  132. package/src/components/form-field/form-field.stories.js +53 -44
  133. package/src/components/form-field/form-field.test.js +280 -134
  134. package/src/components/form-field/index.js +1 -1
  135. package/src/components/gallery/gallery-image.component.js +30 -30
  136. package/src/components/gallery/gallery-image.js +3 -3
  137. package/src/components/gallery/gallery-image.react.js +6 -6
  138. package/src/components/gallery/gallery.component.js +24 -17
  139. package/src/components/gallery/gallery.css +79 -72
  140. package/src/components/gallery/gallery.js +3 -3
  141. package/src/components/gallery/gallery.react.js +6 -6
  142. package/src/components/gallery/gallery.stories.js +69 -68
  143. package/src/components/gallery/gallery.test.js +95 -88
  144. package/src/components/gallery/index.js +2 -2
  145. package/src/components/hero/hero.component.js +53 -57
  146. package/src/components/hero/hero.css +27 -27
  147. package/src/components/hero/hero.js +3 -3
  148. package/src/components/hero/hero.react.js +6 -6
  149. package/src/components/hero/hero.stories.js +39 -39
  150. package/src/components/hero/index.js +1 -1
  151. package/src/components/icon/icon.component.js +10 -10
  152. package/src/components/icon/icon.css +13 -13
  153. package/src/components/icon/icon.js +4 -4
  154. package/src/components/icon/icon.react.js +6 -6
  155. package/src/components/icon/icon.stories.js +8 -9
  156. package/src/components/icon/icon.test.js +54 -55
  157. package/src/components/icon/index.js +1 -1
  158. package/src/components/loading/index.js +1 -1
  159. package/src/components/loading/loading.component.js +41 -14
  160. package/src/components/loading/loading.css +47 -30
  161. package/src/components/loading/loading.js +4 -4
  162. package/src/components/loading/loading.react.js +6 -6
  163. package/src/components/loading/loading.stories.js +19 -18
  164. package/src/components/loading/loading.test.js +77 -54
  165. package/src/components/logo-cloud/index.js +1 -1
  166. package/src/components/logo-cloud/logo-cloud.component.js +39 -43
  167. package/src/components/logo-cloud/logo-cloud.css +31 -31
  168. package/src/components/logo-cloud/logo-cloud.js +3 -3
  169. package/src/components/logo-cloud/logo-cloud.react.js +6 -6
  170. package/src/components/logo-cloud/logo-cloud.stories.js +80 -80
  171. package/src/components/map/index.js +1 -1
  172. package/src/components/map/map.component.js +103 -107
  173. package/src/components/map/map.css +22 -22
  174. package/src/components/map/map.js +3 -3
  175. package/src/components/map/map.react.js +6 -6
  176. package/src/components/map/map.stories.js +38 -38
  177. package/src/components/media-text/index.js +1 -1
  178. package/src/components/media-text/media-text.component.js +76 -70
  179. package/src/components/media-text/media-text.css +32 -32
  180. package/src/components/media-text/media-text.js +3 -3
  181. package/src/components/media-text/media-text.react.js +6 -6
  182. package/src/components/media-text/media-text.stories.js +45 -45
  183. package/src/components/media-text/media-text.test.js +72 -0
  184. package/src/components/newsletter/index.js +1 -1
  185. package/src/components/newsletter/newsletter.component.js +56 -64
  186. package/src/components/newsletter/newsletter.css +48 -48
  187. package/src/components/newsletter/newsletter.js +3 -3
  188. package/src/components/newsletter/newsletter.react.js +6 -6
  189. package/src/components/newsletter/newsletter.stories.js +35 -35
  190. package/src/components/notice/index.js +1 -1
  191. package/src/components/notice/notice.component.js +66 -55
  192. package/src/components/notice/notice.css +37 -38
  193. package/src/components/notice/notice.js +3 -3
  194. package/src/components/notice/notice.react.js +6 -6
  195. package/src/components/notice/notice.stories.js +54 -53
  196. package/src/components/notice/notice.test.js +210 -156
  197. package/src/components/pagination/index.js +1 -1
  198. package/src/components/pagination/pagination.component.js +44 -44
  199. package/src/components/pagination/pagination.css +6 -6
  200. package/src/components/pagination/pagination.js +4 -4
  201. package/src/components/pagination/pagination.react.js +6 -6
  202. package/src/components/pagination/pagination.stories.js +20 -18
  203. package/src/components/pagination/pagination.test.js +95 -102
  204. package/src/components/person/index.js +1 -1
  205. package/src/components/person/person.component.js +29 -29
  206. package/src/components/person/person.css +17 -17
  207. package/src/components/person/person.js +4 -4
  208. package/src/components/person/person.react.js +6 -6
  209. package/src/components/person/person.stories.js +34 -33
  210. package/src/components/person/person.test.js +55 -68
  211. package/src/components/pricing/index.js +1 -1
  212. package/src/components/pricing/pricing.component.js +58 -70
  213. package/src/components/pricing/pricing.css +65 -65
  214. package/src/components/pricing/pricing.js +3 -3
  215. package/src/components/pricing/pricing.react.js +6 -6
  216. package/src/components/pricing/pricing.stories.js +96 -96
  217. package/src/components/sidebar/index.js +1 -1
  218. package/src/components/sidebar/sidebar.component.js +157 -130
  219. package/src/components/sidebar/sidebar.css +118 -115
  220. package/src/components/sidebar/sidebar.js +4 -4
  221. package/src/components/sidebar/sidebar.react.js +10 -10
  222. package/src/components/sidebar/sidebar.stories.js +10 -10
  223. package/src/components/sidebar/sidebar.test.js +250 -230
  224. package/src/components/stats/index.js +1 -1
  225. package/src/components/stats/stats.component.js +42 -50
  226. package/src/components/stats/stats.css +29 -29
  227. package/src/components/stats/stats.js +3 -3
  228. package/src/components/stats/stats.react.js +6 -6
  229. package/src/components/stats/stats.stories.js +40 -40
  230. package/src/components/tabs/index.js +2 -2
  231. package/src/components/tabs/internal/tabs-button.component.js +16 -27
  232. package/src/components/tabs/internal/tabs-button.js +3 -3
  233. package/src/components/tabs/internal/tabs-button.react.js +6 -6
  234. package/src/components/tabs/internal/tabs-item.component.js +30 -30
  235. package/src/components/tabs/tab.component.js +9 -11
  236. package/src/components/tabs/tab.js +3 -3
  237. package/src/components/tabs/tab.react.js +6 -6
  238. package/src/components/tabs/tabs.component.js +107 -109
  239. package/src/components/tabs/tabs.css +50 -59
  240. package/src/components/tabs/tabs.js +4 -4
  241. package/src/components/tabs/tabs.react.js +6 -6
  242. package/src/components/tabs/tabs.stories.js +23 -22
  243. package/src/components/tabs/tabs.test.js +201 -147
  244. package/src/components/testimonials/index.js +1 -1
  245. package/src/components/testimonials/testimonials.component.js +47 -51
  246. package/src/components/testimonials/testimonials.css +31 -31
  247. package/src/components/testimonials/testimonials.js +3 -3
  248. package/src/components/testimonials/testimonials.react.js +6 -6
  249. package/src/components/testimonials/testimonials.stories.js +49 -51
  250. package/src/components/toast/index.js +1 -1
  251. package/src/components/toast/toast.component.js +217 -148
  252. package/src/components/toast/toast.css +101 -99
  253. package/src/components/toast/toast.js +6 -9
  254. package/src/components/toast/toast.react.js +12 -12
  255. package/src/components/toast/toast.stories.js +75 -49
  256. package/src/components/toast/toast.test.js +260 -221
  257. package/src/components/tooltip/index.js +1 -1
  258. package/src/components/tooltip/tooltip.component.js +56 -49
  259. package/src/components/tooltip/tooltip.css +61 -50
  260. package/src/components/tooltip/tooltip.js +4 -4
  261. package/src/components/tooltip/tooltip.react.js +6 -6
  262. package/src/components/tooltip/tooltip.stories.js +24 -23
  263. package/src/components/tooltip/tooltip.test.js +143 -155
  264. package/src/constructor.test.js +31 -0
  265. package/src/css/base.css +1 -0
  266. package/src/css/colors.stories.js +120 -122
  267. package/src/css/elements/a.css +28 -28
  268. package/src/css/elements/forms/button.css +1 -1
  269. package/src/css/elements/forms/input.css +80 -80
  270. package/src/css/elements/forms/label.css +3 -3
  271. package/src/css/elements/media/image.css +2 -2
  272. package/src/css/elements.stories.js +48 -49
  273. package/src/css/helpers.css +8 -8
  274. package/src/css/layers.stories.js +5 -5
  275. package/src/css/reset.css +102 -102
  276. package/src/css/themes/todomap.css +1 -1
  277. package/src/css/tokens.stories.js +50 -53
  278. package/src/css/typography.css +30 -32
  279. package/src/css/typography.stories.js +92 -92
  280. package/src/css/util/focus-ring.css +12 -20
  281. package/src/css/view-transitions.css +16 -0
  282. package/src/exports.test.js +89 -105
  283. package/src/icons.js +1 -1
  284. package/src/lib/generate-id.js +1 -1
  285. package/src/lib/slot-presence-controller.js +48 -0
  286. package/src/lib/styles/all.css +38 -37
  287. package/src/lib/styles/focus-ring.css +12 -20
  288. package/src/lib/view-transition.js +42 -0
  289. package/src/lib/view-transition.test.js +105 -0
  290. package/src/main.js +29 -29
  291. package/src/pages/agency.stories.js +59 -59
  292. package/src/pages/blog-post.stories.js +48 -48
  293. package/src/pages/saas-landing.stories.js +180 -180
  294. package/src/react.js +44 -44
  295. package/src/react.test.js +42 -51
  296. package/src/test-utils/assert-helpers.js +39 -58
  297. package/src/test-utils/events.js +32 -34
  298. package/src/test-utils/fixture.js +31 -31
  299. package/src/test-utils/index.js +3 -3
  300. package/src/types.d.ts +3 -3
@@ -1,9 +1,9 @@
1
- import React from "react";
2
- import { createComponent } from "@lit/react";
3
- import { GrantCodesIcon } from "./icon.js";
1
+ import { createComponent } from '@lit/react';
2
+ import React from 'react';
3
+ import { GrantCodesIcon } from './icon.js';
4
4
 
5
5
  export const Icon = createComponent({
6
- tagName: "grantcodes-icon",
7
- elementClass: GrantCodesIcon,
8
- react: React,
6
+ tagName: 'grantcodes-icon',
7
+ elementClass: GrantCodesIcon,
8
+ react: React,
9
9
  });
@@ -1,22 +1,21 @@
1
- import { html } from "lit/static-html.js";
2
- import { unsafeHTML } from "lit/directives/unsafe-html.js";
3
- import "./icon.js";
4
- import { ArrowRight, Heart, Star, CheckCircle2 } from "../../icons.js";
1
+ import { unsafeHTML } from 'lit/directives/unsafe-html.js';
2
+ import { html } from 'lit/static-html.js';
3
+ import './icon.js';
4
+ import { ArrowRight, CheckCircle2, Heart, Star } from '../../icons.js';
5
5
 
6
6
  const meta = {
7
- title: "Components/Icon",
8
- component: "grantcodes-icon",
7
+ title: 'Components/Icon',
8
+ component: 'grantcodes-icon',
9
9
  };
10
10
 
11
11
  export default meta;
12
12
 
13
13
  export const Icon = {
14
- render: () =>
15
- html`<grantcodes-icon>${unsafeHTML(ArrowRight)}</grantcodes-icon>`,
14
+ render: () => html`<grantcodes-icon>${unsafeHTML(ArrowRight)}</grantcodes-icon>`,
16
15
  };
17
16
 
18
17
  export const MultipleIcons = {
19
- render: () => html`
18
+ render: () => html`
20
19
  <div style="display: flex; gap: 1rem;">
21
20
  <grantcodes-icon>${unsafeHTML(Heart)}</grantcodes-icon>
22
21
  <grantcodes-icon>${unsafeHTML(Star)}</grantcodes-icon>
@@ -1,56 +1,55 @@
1
- import { describe, it, afterEach } from "node:test";
2
- import { strict as assert } from "node:assert";
3
- import { fixture, cleanup } from "../../test-utils/index.js";
4
- import "./icon.js";
5
-
6
- describe("Icon Component", () => {
7
- let element;
8
-
9
- afterEach(() => {
10
- cleanup(element);
11
- });
12
-
13
- it("should render with default properties", async () => {
14
- element = await fixture("grantcodes-icon");
15
- assert.ok(element, "Element should be created");
16
- assert.ok(element.shadowRoot, "Element should have shadow root");
17
- });
18
-
19
- it("should have icon wrapper span", async () => {
20
- element = await fixture("grantcodes-icon");
21
- const iconSpan = element.shadowRoot.querySelector(".icon");
22
- assert.ok(iconSpan, "Icon span should exist");
23
- });
24
-
25
- it("should have slot for SVG content", async () => {
26
- element = await fixture("grantcodes-icon");
27
- const slot = element.shadowRoot.querySelector("slot");
28
- assert.ok(slot, "Slot should exist");
29
- });
30
-
31
- it("should render slotted SVG content", async () => {
32
- element = await fixture("grantcodes-icon");
33
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
34
- svg.setAttribute("width", "24");
35
- svg.setAttribute("height", "24");
36
- element.appendChild(svg);
37
-
38
- await element.updateComplete;
39
-
40
- const slottedSvg = element.querySelector("svg");
41
- assert.ok(slottedSvg, "SVG should be slotted");
42
- assert.strictEqual(slottedSvg.getAttribute("width"), "24");
43
- });
44
-
45
- it("should accept any SVG as slot content", async () => {
46
- element = await fixture("grantcodes-icon");
47
- element.innerHTML =
48
- '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/></svg>';
49
-
50
- await element.updateComplete;
51
-
52
- const svg = element.querySelector("svg");
53
- assert.ok(svg, "SVG should be present");
54
- assert.ok(svg.querySelector("circle"), "SVG content should be preserved");
55
- });
1
+ import { strict as assert } from 'node:assert';
2
+ import { afterEach, describe, it } from 'node:test';
3
+ import { cleanup, fixture } from '../../test-utils/index.js';
4
+ import './icon.js';
5
+
6
+ describe('Icon Component', () => {
7
+ let element;
8
+
9
+ afterEach(() => {
10
+ cleanup(element);
11
+ });
12
+
13
+ it('should render with default properties', async () => {
14
+ element = await fixture('grantcodes-icon');
15
+ assert.ok(element, 'Element should be created');
16
+ assert.ok(element.shadowRoot, 'Element should have shadow root');
17
+ });
18
+
19
+ it('should have icon wrapper span', async () => {
20
+ element = await fixture('grantcodes-icon');
21
+ const iconSpan = element.shadowRoot.querySelector('.icon');
22
+ assert.ok(iconSpan, 'Icon span should exist');
23
+ });
24
+
25
+ it('should have slot for SVG content', async () => {
26
+ element = await fixture('grantcodes-icon');
27
+ const slot = element.shadowRoot.querySelector('slot');
28
+ assert.ok(slot, 'Slot should exist');
29
+ });
30
+
31
+ it('should render slotted SVG content', async () => {
32
+ element = await fixture('grantcodes-icon');
33
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
34
+ svg.setAttribute('width', '24');
35
+ svg.setAttribute('height', '24');
36
+ element.appendChild(svg);
37
+
38
+ await element.updateComplete;
39
+
40
+ const slottedSvg = element.querySelector('svg');
41
+ assert.ok(slottedSvg, 'SVG should be slotted');
42
+ assert.strictEqual(slottedSvg.getAttribute('width'), '24');
43
+ });
44
+
45
+ it('should accept any SVG as slot content', async () => {
46
+ element = await fixture('grantcodes-icon');
47
+ element.innerHTML = '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/></svg>';
48
+
49
+ await element.updateComplete;
50
+
51
+ const svg = element.querySelector('svg');
52
+ assert.ok(svg, 'SVG should be present');
53
+ assert.ok(svg.querySelector('circle'), 'SVG content should be preserved');
54
+ });
56
55
  });
@@ -1 +1 @@
1
- export * from "./icon.js";
1
+ export * from './icon.js';
@@ -1 +1 @@
1
- export * from "./loading.js";
1
+ export * from './loading.js';
@@ -1,21 +1,48 @@
1
- import { LitElement } from "lit";
2
- import { html } from "lit/static-html.js";
3
- import loadingStyles from "./loading.css" with { type: "css" };
1
+ import { LitElement, nothing } from 'lit';
2
+ import { html } from 'lit/static-html.js';
3
+ import { SlotPresenceController } from '../../lib/slot-presence-controller.js';
4
+ import loadingStyles from './loading.css' with { type: 'css' };
4
5
 
5
6
  export class GrantCodesLoading extends LitElement {
6
- // Styles are scoped to this element: they won't conflict with styles
7
- // on the main page or in other components. Styling API can be exposed
8
- // via CSS custom properties.
9
- static styles = [loadingStyles];
7
+ // Styles are scoped to this element: they won't conflict with styles
8
+ // on the main page or in other components. Styling API can be exposed
9
+ // via CSS custom properties.
10
+ static styles = [loadingStyles];
10
11
 
11
- // Define reactive properties--updating a reactive property causes
12
- // the component to update.
12
+ // Define reactive properties--updating a reactive property causes
13
+ // the component to update.
14
+ static properties = {
15
+ _hasLabel: { state: true },
16
+ };
13
17
 
14
- render() {
15
- return html`
16
- <span class="loading">
17
- <slot></slot>
18
+ constructor() {
19
+ super();
20
+
21
+ // Slotted text is visually hidden and doubles as the status label.
22
+ this._hasLabel = false;
23
+ this._labelPresence = new SlotPresenceController(this, '', (present) => {
24
+ this._hasLabel = present;
25
+ });
26
+ }
27
+
28
+ connectedCallback() {
29
+ super.connectedCallback();
30
+ this.setAttribute('aria-busy', 'true');
31
+ }
32
+
33
+ _handleSlotChange() {
34
+ this._labelPresence.refresh();
35
+ }
36
+
37
+ render() {
38
+ return html`
39
+ <span
40
+ class="loading"
41
+ role="status"
42
+ aria-label=${this._hasLabel ? nothing : 'Loading'}
43
+ >
44
+ <slot @slotchange=${this._handleSlotChange}></slot>
18
45
  </span>
19
46
  `;
20
- }
47
+ }
21
48
  }
@@ -1,43 +1,60 @@
1
1
  *,
2
2
  *::before,
3
3
  *::after {
4
- box-sizing: border-box;
4
+ box-sizing: border-box;
5
5
  }
6
6
 
7
7
  :host {
8
- --height: 0.5rem;
9
- --inner-width: 40%;
10
- --starting-position: calc(0% - (var(--inner-width) * 2));
11
- --ending-position: calc(100% + (var(--inner-width) * 2));
8
+ --height: 0.5rem;
9
+ --inner-width: 40%;
10
+ --starting-position: calc(0% - (var(--inner-width) * 2));
11
+ --ending-position: calc(100% + (var(--inner-width) * 2));
12
12
  }
13
13
 
14
14
  .loading {
15
- display: block;
16
- height: var(--height);
17
- border-radius: calc(var(--height) / 2);
18
- overflow: hidden;
19
- padding-top: var(--height);
20
- background-repeat: no-repeat;
21
- background-color: var(--g-color-primary-500);
22
- background-image: linear-gradient(
23
- to right,
24
- transparent 0%,
25
- var(--g-color-background-shimmer) 10%,
26
- var(--g-color-background-shimmer) 90%,
27
- transparent 100%
28
- );
29
- background-size: var(--inner-width) 100%;
30
- background-position-x: var(--starting-position);
31
- animation: loading 2s linear infinite;
32
- /* This is probably a bad way of hiding the text */
33
- font-size: 0;
15
+ display: block;
16
+ height: var(--height);
17
+ border-radius: calc(var(--height) / 2);
18
+ overflow: hidden;
19
+ padding-top: var(--height);
20
+ background-repeat: no-repeat;
21
+ background-color: var(--g-color-primary-500);
22
+ background-image: linear-gradient(
23
+ to right,
24
+ transparent 0%,
25
+ var(--g-color-background-shimmer) 10%,
26
+ var(--g-color-background-shimmer) 90%,
27
+ transparent 100%
28
+ );
29
+ background-size: var(--inner-width) 100%;
30
+ background-position-x: var(--starting-position);
31
+ animation: loading 2s linear infinite;
32
+ /* This is probably a bad way of hiding the text */
33
+ font-size: 0;
34
34
  }
35
35
 
36
36
  @keyframes loading {
37
- 0% {
38
- background-position-x: var(--starting-position);
39
- }
40
- 100% {
41
- background-position-x: var(--ending-position);
42
- }
37
+ 0% {
38
+ background-position-x: var(--starting-position);
39
+ }
40
+ 100% {
41
+ background-position-x: var(--ending-position);
42
+ }
43
+ }
44
+
45
+ /* A static bar reads as stuck, so reduced motion gets a pulse instead of movement. */
46
+ @media (prefers-reduced-motion: reduce) {
47
+ .loading {
48
+ animation: loading-pulse 2s ease-in-out infinite;
49
+ }
50
+ }
51
+
52
+ @keyframes loading-pulse {
53
+ 0%,
54
+ 100% {
55
+ opacity: 1;
56
+ }
57
+ 50% {
58
+ opacity: 0.5;
59
+ }
43
60
  }
@@ -1,8 +1,8 @@
1
- import { GrantCodesLoading } from "./loading.component.js";
1
+ import { GrantCodesLoading } from './loading.component.js';
2
2
 
3
- export * from "./loading.component.js";
3
+ export * from './loading.component.js';
4
4
  export default GrantCodesLoading;
5
5
 
6
- if (!customElements.get("grantcodes-loading")) {
7
- customElements.define("grantcodes-loading", GrantCodesLoading);
6
+ if (!customElements.get('grantcodes-loading')) {
7
+ customElements.define('grantcodes-loading', GrantCodesLoading);
8
8
  }
@@ -1,9 +1,9 @@
1
- import React from "react";
2
- import { createComponent } from "@lit/react";
3
- import { GrantCodesLoading } from "./loading.js";
1
+ import { createComponent } from '@lit/react';
2
+ import React from 'react';
3
+ import { GrantCodesLoading } from './loading.js';
4
4
 
5
5
  export const Loading = createComponent({
6
- tagName: "grantcodes-loading",
7
- elementClass: GrantCodesLoading,
8
- react: React,
6
+ tagName: 'grantcodes-loading',
7
+ elementClass: GrantCodesLoading,
8
+ react: React,
9
9
  });
@@ -1,23 +1,24 @@
1
- import { html } from "lit/static-html.js";
2
- import { getStorybookHelpers } from "@wc-toolkit/storybook-helpers";
3
- const { events, args, argTypes, template } =
4
- getStorybookHelpers("grantcodes-loading");
5
- import "./loading.js";
1
+ import { getStorybookHelpers } from '@wc-toolkit/storybook-helpers';
2
+ import { html } from 'lit/static-html.js';
3
+
4
+ const { events, args, argTypes, template } = getStorybookHelpers('grantcodes-loading');
5
+
6
+ import './loading.js';
6
7
 
7
8
  const meta = {
8
- title: "Components/Loading",
9
- component: "grantcodes-loading",
10
- args: {
11
- ...args,
12
- text: "Loading...",
13
- },
14
- argTypes,
15
- render: (args) => template(args, html`${args.text}`),
16
- parameters: {
17
- actions: {
18
- handles: events,
19
- },
20
- },
9
+ title: 'Components/Loading',
10
+ component: 'grantcodes-loading',
11
+ args: {
12
+ ...args,
13
+ text: 'Loading...',
14
+ },
15
+ argTypes,
16
+ render: (args) => template(args, html`${args.text}`),
17
+ parameters: {
18
+ actions: {
19
+ handles: events,
20
+ },
21
+ },
21
22
  };
22
23
 
23
24
  export default meta;
@@ -1,55 +1,78 @@
1
- import { describe, it, afterEach } from "node:test";
2
- import { strict as assert } from "node:assert";
3
- import { fixture, cleanup } from "../../test-utils/index.js";
4
- import "./loading.js";
5
-
6
- describe("Loading Component", () => {
7
- let element;
8
-
9
- afterEach(() => {
10
- cleanup(element);
11
- });
12
-
13
- it("should render with default properties", async () => {
14
- element = await fixture("grantcodes-loading");
15
- assert.ok(element, "Element should be created");
16
- assert.ok(element.shadowRoot, "Element should have shadow root");
17
- });
18
-
19
- it("should have loading wrapper span", async () => {
20
- element = await fixture("grantcodes-loading");
21
- const loadingSpan = element.shadowRoot.querySelector(".loading");
22
- assert.ok(loadingSpan, "Loading span should exist");
23
- });
24
-
25
- it("should have slot for content", async () => {
26
- element = await fixture("grantcodes-loading");
27
- const slot = element.shadowRoot.querySelector("slot");
28
- assert.ok(slot, "Slot should exist");
29
- });
30
-
31
- it("should render slotted content", async () => {
32
- element = await fixture("grantcodes-loading");
33
- element.textContent = "Loading...";
34
-
35
- await element.updateComplete;
36
-
37
- assert.strictEqual(
38
- element.textContent,
39
- "Loading...",
40
- "Slotted text should be present",
41
- );
42
- });
43
-
44
- it("should support custom loading indicators", async () => {
45
- element = await fixture("grantcodes-loading");
46
- const spinner = document.createElement("div");
47
- spinner.className = "custom-spinner";
48
- element.appendChild(spinner);
49
-
50
- await element.updateComplete;
51
-
52
- const customSpinner = element.querySelector(".custom-spinner");
53
- assert.ok(customSpinner, "Custom spinner should be slotted");
54
- });
1
+ import { strict as assert } from 'node:assert';
2
+ import { afterEach, describe, it } from 'node:test';
3
+ import { cleanup, fixture } from '../../test-utils/index.js';
4
+ import './loading.js';
5
+
6
+ describe('Loading Component', () => {
7
+ let element;
8
+
9
+ afterEach(() => {
10
+ cleanup(element);
11
+ });
12
+
13
+ it('should render with default properties', async () => {
14
+ element = await fixture('grantcodes-loading');
15
+ assert.ok(element, 'Element should be created');
16
+ assert.ok(element.shadowRoot, 'Element should have shadow root');
17
+ });
18
+
19
+ it('should have loading wrapper span', async () => {
20
+ element = await fixture('grantcodes-loading');
21
+ const loadingSpan = element.shadowRoot.querySelector('.loading');
22
+ assert.ok(loadingSpan, 'Loading span should exist');
23
+ });
24
+
25
+ it('should have slot for content', async () => {
26
+ element = await fixture('grantcodes-loading');
27
+ const slot = element.shadowRoot.querySelector('slot');
28
+ assert.ok(slot, 'Slot should exist');
29
+ });
30
+
31
+ it('should render slotted content', async () => {
32
+ element = await fixture('grantcodes-loading');
33
+ element.textContent = 'Loading...';
34
+
35
+ await element.updateComplete;
36
+
37
+ assert.strictEqual(element.textContent, 'Loading...', 'Slotted text should be present');
38
+ });
39
+
40
+ it('should support custom loading indicators', async () => {
41
+ element = await fixture('grantcodes-loading');
42
+ const spinner = document.createElement('div');
43
+ spinner.className = 'custom-spinner';
44
+ element.appendChild(spinner);
45
+
46
+ await element.updateComplete;
47
+
48
+ const customSpinner = element.querySelector('.custom-spinner');
49
+ assert.ok(customSpinner, 'Custom spinner should be slotted');
50
+ });
51
+
52
+ it('should expose status semantics', async () => {
53
+ element = await fixture('grantcodes-loading');
54
+
55
+ const span = element.shadowRoot.querySelector('.loading');
56
+ assert.strictEqual(span.getAttribute('role'), 'status', 'Loading should be a status region');
57
+ assert.strictEqual(
58
+ span.getAttribute('aria-label'),
59
+ 'Loading',
60
+ 'Status region needs a fallback name',
61
+ );
62
+ assert.strictEqual(element.getAttribute('aria-busy'), 'true', 'Host should be marked busy');
63
+ });
64
+
65
+ it('should let slotted text name the status region', async () => {
66
+ element = document.createElement('grantcodes-loading');
67
+ element.textContent = 'Loading images';
68
+ document.body.appendChild(element);
69
+ await element.updateComplete;
70
+
71
+ const span = element.shadowRoot.querySelector('.loading');
72
+ assert.strictEqual(
73
+ span.hasAttribute('aria-label'),
74
+ false,
75
+ 'Slotted text already names the status region',
76
+ );
77
+ });
55
78
  });
@@ -1 +1 @@
1
- export * from "./logo-cloud.js";
1
+ export * from './logo-cloud.js';
@@ -1,54 +1,50 @@
1
- import { LitElement, html } from "lit";
2
- import logoCloudStyles from "./logo-cloud.css" with { type: "css" };
3
- import "../container/container.js";
1
+ import { html, LitElement } from 'lit';
2
+ import logoCloudStyles from './logo-cloud.css' with { type: 'css' };
3
+ import '../container/container.js';
4
4
 
5
5
  export class GrantCodesLogoCloud extends LitElement {
6
- static styles = [logoCloudStyles];
6
+ static styles = [logoCloudStyles];
7
7
 
8
- static properties = {
9
- /**
10
- * Optional label shown above the logos.
11
- * @type {string}
12
- */
13
- title: { type: String },
14
- /**
15
- * Logo items as a JSON string array: `[{"name":"...","src":"...","alt":"...","href":"..."}]`.
16
- * @type {string}
17
- */
18
- logos: { type: String },
19
- };
8
+ static properties = {
9
+ /**
10
+ * Optional label shown above the logos.
11
+ * @type {string}
12
+ */
13
+ title: { type: String },
14
+ /**
15
+ * Logo items as a JSON string array: `[{"name":"...","src":"...","alt":"...","href":"..."}]`.
16
+ * @type {string}
17
+ */
18
+ logos: { type: String },
19
+ };
20
20
 
21
- constructor() {
22
- super();
23
- this.title = "";
24
- this.logos = "[]";
25
- }
21
+ constructor() {
22
+ super();
23
+ this.title = '';
24
+ this.logos = '[]';
25
+ }
26
26
 
27
- get _logos() {
28
- try {
29
- return JSON.parse(this.logos);
30
- } catch {
31
- return [];
32
- }
33
- }
27
+ get _logos() {
28
+ try {
29
+ return JSON.parse(this.logos);
30
+ } catch {
31
+ return [];
32
+ }
33
+ }
34
34
 
35
- render() {
36
- const logos = this._logos;
37
- return html`
35
+ render() {
36
+ const logos = this._logos;
37
+ return html`
38
38
  <section class="logo-cloud">
39
39
  <grantcodes-container>
40
- ${
41
- this.title
42
- ? html`<p class="logo-cloud__title">${this.title}</p>`
43
- : null
44
- }
40
+ ${this.title ? html`<p class="logo-cloud__title">${this.title}</p>` : null}
45
41
  <ul class="logo-cloud__grid" role="list">
46
42
  ${logos.map(
47
- (logo) => html`
43
+ (logo) => html`
48
44
  <li class="logo-cloud__item">
49
45
  ${
50
- logo.href
51
- ? html`
46
+ logo.href
47
+ ? html`
52
48
  <a
53
49
  href=${logo.href}
54
50
  class="logo-cloud__link"
@@ -62,7 +58,7 @@ export class GrantCodesLogoCloud extends LitElement {
62
58
  />
63
59
  </a>
64
60
  `
65
- : html`
61
+ : html`
66
62
  <img
67
63
  src=${logo.src}
68
64
  alt=${logo.alt ?? logo.name}
@@ -70,13 +66,13 @@ export class GrantCodesLogoCloud extends LitElement {
70
66
  loading="lazy"
71
67
  />
72
68
  `
73
- }
69
+ }
74
70
  </li>
75
71
  `,
76
- )}
72
+ )}
77
73
  </ul>
78
74
  </grantcodes-container>
79
75
  </section>
80
76
  `;
81
- }
77
+ }
82
78
  }