@grantcodes/ui 2.12.0 → 2.15.2

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 (74) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +5 -1
  3. package/custom-elements.json +295 -176
  4. package/package.json +11 -3
  5. package/src/components/accordion/accordion-item.css +14 -14
  6. package/src/components/accordion/accordion.css +1 -1
  7. package/src/components/app-bar/app-bar.css +22 -22
  8. package/src/components/app-bar/nav-link.css +10 -11
  9. package/src/components/avatar/avatar.css +5 -6
  10. package/src/components/badge/badge.css +16 -15
  11. package/src/components/breadcrumb/breadcrumb.css +10 -11
  12. package/src/components/button/button.css +112 -24
  13. package/src/components/button/button.stories.js +37 -0
  14. package/src/components/button-group/button-group.css +4 -4
  15. package/src/components/card/card.css +25 -25
  16. package/src/components/code-preview/code-preview.css +6 -6
  17. package/src/components/container/container.css +2 -1
  18. package/src/components/container/container.stories.js +1 -1
  19. package/src/components/countdown/countdown.css +13 -13
  20. package/src/components/cta/cta.component.js +3 -3
  21. package/src/components/cta/cta.css +16 -18
  22. package/src/components/dialog/dialog.component.js +1 -0
  23. package/src/components/dialog/dialog.css +14 -14
  24. package/src/components/dropdown/dropdown.component.js +1 -0
  25. package/src/components/dropdown/dropdown.css +14 -14
  26. package/src/components/dropzone/dropzone.component.js +1 -0
  27. package/src/components/dropzone/dropzone.css +11 -12
  28. package/src/components/feature-list/feature-list.css +15 -15
  29. package/src/components/footer/footer-column.css +9 -10
  30. package/src/components/footer/footer.css +13 -13
  31. package/src/components/form-field/form-field.css +6 -6
  32. package/src/components/hero/hero.css +9 -9
  33. package/src/components/loading/loading.css +3 -3
  34. package/src/components/logo-cloud/logo-cloud.css +9 -11
  35. package/src/components/map/map.component.js +9 -4
  36. package/src/components/map/map.css +8 -8
  37. package/src/components/media-text/media-text.css +9 -9
  38. package/src/components/newsletter/newsletter.css +21 -21
  39. package/src/components/notice/notice.css +17 -17
  40. package/src/components/person/index.js +1 -0
  41. package/src/components/person/person.component.js +51 -0
  42. package/src/components/person/person.css +39 -0
  43. package/src/components/person/person.js +8 -0
  44. package/src/components/person/person.react.js +9 -0
  45. package/src/components/person/person.stories.js +59 -0
  46. package/src/components/person/person.test.js +69 -0
  47. package/src/components/pricing/pricing.css +34 -36
  48. package/src/components/sidebar/sidebar.component.js +1 -0
  49. package/src/components/sidebar/sidebar.css +21 -21
  50. package/src/components/stats/stats.css +14 -16
  51. package/src/components/tabs/internal/tabs-item.component.js +5 -0
  52. package/src/components/tabs/tabs.component.js +4 -2
  53. package/src/components/tabs/tabs.css +15 -15
  54. package/src/components/testimonials/testimonials.component.js +8 -22
  55. package/src/components/testimonials/testimonials.css +12 -31
  56. package/src/components/toast/toast.css +23 -24
  57. package/src/components/tooltip/tooltip.css +6 -6
  58. package/src/css/colors.stories.js +6 -6
  59. package/src/css/elements/a.css +4 -4
  60. package/src/css/elements/forms/input.css +27 -27
  61. package/src/css/elements/forms/label.css +1 -1
  62. package/src/css/elements/media/image.css +1 -0
  63. package/src/css/layers.stories.js +3 -3
  64. package/src/css/reset.css +3 -3
  65. package/src/css/themes/todomap.css +1 -1
  66. package/src/css/tokens.stories.js +16 -16
  67. package/src/css/typography.css +4 -4
  68. package/src/css/util/focus-ring.css +9 -9
  69. package/src/lib/styles/focus-ring.css +9 -9
  70. package/src/main.js +1 -0
  71. package/src/pages/agency.stories.js +5 -4
  72. package/src/pages/blog-post.stories.js +33 -50
  73. package/src/pages/saas-landing.stories.js +8 -7
  74. package/src/react.js +1 -0
@@ -7,7 +7,7 @@
7
7
  .form-field {
8
8
  display: flex;
9
9
  flex-direction: column;
10
- gap: var(--g-theme-spacing-xs);
10
+ gap: var(--g-spacing-xs);
11
11
  padding: 0;
12
12
  margin: 0;
13
13
  border: none;
@@ -24,7 +24,7 @@
24
24
  flex-direction: row-reverse;
25
25
  align-items: center;
26
26
  justify-content: flex-end;
27
- gap: var(--g-theme-spacing-sm);
27
+ gap: var(--g-spacing-sm);
28
28
  }
29
29
 
30
30
  .form-field__label {
@@ -32,17 +32,17 @@
32
32
  padding: 0;
33
33
 
34
34
  &:where(legend) {
35
- margin-bottom: var(--g-theme-spacing-xs);
35
+ margin-bottom: var(--g-spacing-xs);
36
36
  }
37
37
  }
38
38
 
39
39
  .form-field__help {
40
- font-size: var(--g-theme-typography-body-sm-font-size);
40
+ font-size: var(--g-typography-body-sm-font-size);
41
41
  opacity: 0.7;
42
42
  }
43
43
 
44
44
  .form-field__error {
45
45
  margin: 0;
46
- font-size: var(--g-theme-typography-body-sm-font-size);
47
- color: var(--g-color-utility-red-700);
46
+ font-size: var(--g-typography-body-sm-font-size);
47
+ color: var(--g-color-content-utility-error);
48
48
  }
@@ -14,10 +14,10 @@
14
14
  justify-content: center;
15
15
  align-items: center;
16
16
  min-height: 50vh;
17
- padding: var(--g-theme-spacing-2xl) var(--g-theme-spacing-md);
17
+ padding: var(--g-spacing-2xl) var(--g-spacing-md);
18
18
  background: linear-gradient(
19
19
  135deg,
20
- var(--g-theme-color-background-primary-knockout),
20
+ var(--g-color-background-primary-knockout),
21
21
  var(--g-color-primary-900)
22
22
  );
23
23
  }
@@ -25,7 +25,7 @@
25
25
  .hero__container {
26
26
  width: 100%;
27
27
  max-width: 65ch;
28
- gap: var(--g-theme-spacing-lg);
28
+ gap: var(--g-spacing-lg);
29
29
  }
30
30
 
31
31
  :host([center]) .hero {
@@ -38,24 +38,24 @@
38
38
 
39
39
  .hero__title {
40
40
  font: var(--g-typography-h1-font);
41
- color: var(--g-theme-color-content-primary-knockout);
41
+ color: var(--g-color-content-primary-knockout);
42
42
  }
43
43
 
44
44
  .hero__text {
45
- font: var(--g-typography-body-lg);
46
- color: var(--g-theme-color-content-primary-knockout);
45
+ font: var(--g-typography-body-lg-font);
46
+ color: var(--g-color-content-primary-knockout);
47
47
  }
48
48
 
49
49
  :host([size="sm"]) .hero {
50
50
  min-height: 30vh;
51
- padding: var(--g-theme-spacing-xl) var(--g-theme-spacing-md);
51
+ padding: var(--g-spacing-xl) var(--g-spacing-md);
52
52
  }
53
53
 
54
54
  :host([size="lg"]) .hero {
55
55
  min-height: 70vh;
56
- padding: var(--g-theme-spacing-3xl) var(--g-theme-spacing-md);
56
+ padding: var(--g-spacing-3xl) var(--g-spacing-md);
57
57
  }
58
58
 
59
59
  ::slotted(*) {
60
- color: var(--g-theme-color-content-primary-knockout, #ffffff);
60
+ color: var(--g-color-content-primary-knockout, #ffffff);
61
61
  }
@@ -18,12 +18,12 @@
18
18
  overflow: hidden;
19
19
  padding-top: var(--height);
20
20
  background-repeat: no-repeat;
21
- background-color: var(--g-theme-color-primary-500);
21
+ background-color: var(--g-color-primary-500);
22
22
  background-image: linear-gradient(
23
23
  to right,
24
24
  transparent 0%,
25
- var(--g-color-neutral-500) 10%,
26
- var(--g-color-neutral-500) 90%,
25
+ var(--g-color-background-shimmer) 10%,
26
+ var(--g-color-background-shimmer) 90%,
27
27
  transparent 100%
28
28
  );
29
29
  background-size: var(--inner-width) 100%;
@@ -9,10 +9,10 @@
9
9
  }
10
10
 
11
11
  .logo-cloud {
12
- padding-block: var(--g-theme-spacing-2xl);
13
- padding-inline: var(--g-theme-spacing-md);
14
- border-top: 1px solid var(--g-theme-color-border-default);
15
- border-bottom: 1px solid var(--g-theme-color-border-default);
12
+ padding-block: var(--g-spacing-2xl);
13
+ padding-inline: var(--g-spacing-md);
14
+ border-top: 1px solid var(--g-color-border-default);
15
+ border-bottom: 1px solid var(--g-color-border-default);
16
16
  }
17
17
 
18
18
  .logo-cloud__container {
@@ -21,13 +21,11 @@
21
21
  }
22
22
 
23
23
  .logo-cloud__title {
24
- margin: 0 0 var(--g-theme-spacing-xl);
25
- font-size: var(--g-theme-typography-meta-default-font-size);
26
- font-weight: var(--g-theme-typography-meta-default-font-weight);
27
- letter-spacing: var(--g-theme-typography-meta-default-letter-spacing);
24
+ margin: 0 0 var(--g-spacing-xl);
25
+ font: var(--g-typography-body-sm-font);
28
26
  text-transform: uppercase;
29
27
  text-align: center;
30
- color: var(--g-theme-color-content-subtle);
28
+ color: var(--g-color-content-subtle);
31
29
  }
32
30
 
33
31
  .logo-cloud__grid {
@@ -35,7 +33,7 @@
35
33
  flex-wrap: wrap;
36
34
  align-items: center;
37
35
  justify-content: center;
38
- gap: var(--g-theme-spacing-lg) var(--g-theme-spacing-2xl);
36
+ gap: var(--g-spacing-lg) var(--g-spacing-2xl);
39
37
  padding: 0;
40
38
  margin: 0;
41
39
  list-style: none;
@@ -53,7 +51,7 @@
53
51
 
54
52
  .logo-cloud__logo {
55
53
  display: block;
56
- block-size: var(--g-theme-spacing-xl);
54
+ block-size: var(--g-spacing-xl);
57
55
  inline-size: auto;
58
56
  max-inline-size: 140px;
59
57
  object-fit: contain;
@@ -48,31 +48,36 @@ export class GrantCodesMap extends LitElement {
48
48
  this.label = "Map";
49
49
  this["directions-url"] = "";
50
50
  this.height = "";
51
- this._darkQuery = window.matchMedia("(prefers-color-scheme: dark)");
51
+ this._darkQuery = null;
52
52
  this._onSchemeChange = () => this._updateFilter();
53
+ if (typeof window !== "undefined") {
54
+ this._darkQuery = window.matchMedia("(prefers-color-scheme: dark)");
55
+ }
53
56
  }
54
57
 
55
58
  connectedCallback() {
56
59
  super.connectedCallback();
60
+ if (typeof window === "undefined") return;
57
61
  this._observer = new MutationObserver(() => this._updateFilter());
58
62
  this._observer.observe(document.documentElement, {
59
63
  attributes: true,
60
64
  attributeFilter: ["class"],
61
65
  });
62
- this._darkQuery.addEventListener("change", this._onSchemeChange);
66
+ this._darkQuery?.addEventListener("change", this._onSchemeChange);
63
67
  this._updateFilter();
64
68
  }
65
69
 
66
70
  disconnectedCallback() {
67
71
  super.disconnectedCallback();
68
72
  this._observer?.disconnect();
69
- this._darkQuery.removeEventListener("change", this._onSchemeChange);
73
+ this._darkQuery?.removeEventListener("change", this._onSchemeChange);
70
74
  }
71
75
 
72
76
  _isDark() {
77
+ if (typeof document === "undefined") return false;
73
78
  if (document.documentElement.classList.contains("dark")) return true;
74
79
  if (document.documentElement.classList.contains("light")) return false;
75
- return this._darkQuery.matches;
80
+ return this._darkQuery?.matches ?? false;
76
81
  }
77
82
 
78
83
  _updateFilter() {
@@ -10,8 +10,8 @@
10
10
 
11
11
  .map {
12
12
  position: relative;
13
- border: 1px solid var(--g-theme-color-border-default);
14
- border-radius: var(--g-theme-border-radius-md, 0.5rem);
13
+ border: 1px solid var(--g-color-border-default);
14
+ border-radius: var(--g-border-radius-md, 0.5rem);
15
15
  overflow: hidden;
16
16
  }
17
17
 
@@ -26,16 +26,16 @@
26
26
 
27
27
  .map__directions {
28
28
  display: block;
29
- padding: var(--g-theme-spacing-sm) var(--g-theme-spacing-md);
30
- font-size: var(--g-theme-typography-body-sm-font-size);
31
- color: var(--g-theme-color-content-primary, #7c3aed);
29
+ padding: var(--g-spacing-sm) var(--g-spacing-md);
30
+ font-size: var(--g-typography-body-sm-font-size);
31
+ color: var(--g-color-content-primary, #7c3aed);
32
32
  text-decoration: none;
33
- border-block-start: 1px solid var(--g-theme-color-border-default);
34
- background: var(--g-theme-color-background-subtle);
33
+ border-block-start: 1px solid var(--g-color-border-default);
34
+ background: var(--g-color-background-subtle);
35
35
  transition: background-color 0.2s ease;
36
36
  }
37
37
 
38
38
  .map__directions:hover {
39
- background: var(--g-theme-color-background-subtle-hover);
39
+ background: var(--g-color-background-subtle-hover);
40
40
  text-decoration: underline;
41
41
  }
@@ -9,8 +9,8 @@
9
9
  }
10
10
 
11
11
  .media-text {
12
- padding-block: var(--g-theme-spacing-3xl);
13
- padding-inline: var(--g-theme-spacing-md);
12
+ padding-block: var(--g-spacing-3xl);
13
+ padding-inline: var(--g-spacing-md);
14
14
  }
15
15
 
16
16
  .media-text__container {
@@ -18,7 +18,7 @@
18
18
  margin: 0 auto;
19
19
  display: grid;
20
20
  grid-template-columns: 1fr 1fr;
21
- gap: var(--g-theme-spacing-3xl);
21
+ gap: var(--g-spacing-3xl);
22
22
  align-items: center;
23
23
  }
24
24
 
@@ -34,7 +34,7 @@
34
34
  @media (max-width: 768px) {
35
35
  .media-text__container {
36
36
  grid-template-columns: 1fr;
37
- gap: var(--g-theme-spacing-xl);
37
+ gap: var(--g-spacing-xl);
38
38
  direction: ltr;
39
39
  }
40
40
  }
@@ -44,7 +44,7 @@
44
44
  inline-size: 100%;
45
45
  block-size: auto;
46
46
  display: block;
47
- border-radius: var(--g-theme-border-radius-md, 0.5rem);
47
+ border-radius: var(--g-border-radius-md, 0.5rem);
48
48
  object-fit: cover;
49
49
  aspect-ratio: 4 / 3;
50
50
  }
@@ -52,17 +52,17 @@
52
52
  .media-text__content {
53
53
  display: flex;
54
54
  flex-direction: column;
55
- gap: var(--g-theme-spacing-lg);
55
+ gap: var(--g-spacing-lg);
56
56
  }
57
57
 
58
58
  .media-text__title {
59
59
  margin: 0;
60
60
  font: var(--g-typography-h4-font);
61
- color: var(--g-theme-color-content-default);
61
+ color: var(--g-color-content-default);
62
62
  }
63
63
 
64
64
  .media-text__text {
65
65
  margin: 0;
66
- font: var(--g-theme-typography-body-lg);
67
- color: var(--g-theme-color-content-subtle);
66
+ font: var(--g-typography-body-lg);
67
+ color: var(--g-color-content-subtle);
68
68
  }
@@ -9,9 +9,9 @@
9
9
  }
10
10
 
11
11
  .newsletter {
12
- padding-block: var(--g-theme-spacing-3xl);
13
- padding-inline: var(--g-theme-spacing-md);
14
- background: var(--g-theme-color-background-primary-knockout, #7c3aed);
12
+ padding-block: var(--g-spacing-3xl);
13
+ padding-inline: var(--g-spacing-md);
14
+ background: var(--g-color-background-primary-knockout);
15
15
  text-align: center;
16
16
  }
17
17
 
@@ -21,15 +21,15 @@
21
21
  }
22
22
 
23
23
  .newsletter__title {
24
- margin: 0 0 var(--g-theme-spacing-sm);
25
- font: var(--g-typography-h4-font);
26
- color: var(--g-theme-color-content-primary-knockout, #ffffff);
24
+ margin: 0 0 var(--g-spacing-sm);
25
+ font: var(--g-typography-h3-font);
26
+ color: var(--g-color-content-primary-knockout);
27
27
  }
28
28
 
29
29
  .newsletter__text {
30
- margin: 0 0 var(--g-theme-spacing-xl);
31
- font: var(--g-theme-typography-body-lg);
32
- color: var(--g-theme-color-content-primary-knockout, #ffffff);
30
+ margin: 0 0 var(--g-spacing-xl);
31
+ font: var(--g-typography-body-lg);
32
+ color: var(--g-color-content-primary-knockout);
33
33
  opacity: 0.9;
34
34
  }
35
35
 
@@ -52,35 +52,35 @@
52
52
  .newsletter__field {
53
53
  display: flex;
54
54
  flex-direction: column;
55
- gap: var(--g-theme-spacing-sm);
55
+ gap: var(--g-spacing-sm);
56
56
  }
57
57
 
58
58
  .newsletter__input-wrap {
59
59
  display: flex;
60
- gap: var(--g-theme-spacing-sm);
60
+ gap: var(--g-spacing-sm);
61
61
  }
62
62
 
63
63
  .newsletter__input {
64
64
  flex: 1;
65
65
  min-width: 0;
66
- padding-block: var(--g-theme-spacing-sm);
67
- padding-inline: var(--g-theme-spacing-md);
68
- font-size: var(--g-theme-typography-body-default-font-size);
66
+ padding-block: var(--g-spacing-sm);
67
+ padding-inline: var(--g-spacing-md);
68
+ font-size: var(--g-typography-body-default-font-size);
69
69
  border: 2px solid transparent;
70
- border-radius: var(--g-theme-border-radius-md, 0.5rem);
71
- background: var(--g-theme-color-background-default);
72
- color: var(--g-theme-color-content-default);
70
+ border-radius: var(--g-border-radius-md, 0.5rem);
71
+ background: var(--g-color-background-default);
72
+ color: var(--g-color-content-default);
73
73
  outline: none;
74
74
  }
75
75
 
76
76
  .newsletter__input:focus {
77
- border-color: var(--g-theme-color-border-default);
77
+ border-color: var(--g-color-border-default);
78
78
  }
79
79
 
80
80
  .newsletter__disclaimer {
81
- margin: var(--g-theme-spacing-md) 0 0;
82
- font: var(--g-theme-typography-body-sm);
83
- color: var(--g-theme-color-content-primary-knockout, #ffffff);
81
+ margin: var(--g-spacing-md) 0 0;
82
+ font: var(--g-typography-body-sm);
83
+ color: var(--g-color-content-primary-knockout);
84
84
  opacity: 0.75;
85
85
  }
86
86
 
@@ -8,40 +8,40 @@
8
8
  display: flex;
9
9
  flex-direction: row;
10
10
  align-items: flex-start;
11
- gap: var(--g-theme-spacing-md);
12
- padding: var(--g-theme-spacing-md);
13
- border-radius: var(--g-theme-border-radius-sm, 0.25rem);
11
+ gap: var(--g-spacing-md);
12
+ padding: var(--g-spacing-md);
13
+ border-radius: var(--g-border-radius-sm, 0.25rem);
14
14
  view-transition-name: notice;
15
15
  transition: opacity 0.5s;
16
16
  }
17
17
 
18
18
  .notice--info {
19
- color: var(--g-color-utility-blue-900);
20
- background-color: var(--g-color-utility-blue-100);
19
+ color: var(--g-color-content-utility-info);
20
+ background-color: var(--g-color-background-utility-info);
21
21
  }
22
22
 
23
23
  .notice--success {
24
- color: var(--g-color-utility-green-900);
25
- background-color: var(--g-color-utility-green-100);
24
+ color: var(--g-color-content-utility-success);
25
+ background-color: var(--g-color-background-utility-success);
26
26
  }
27
27
 
28
28
  .notice--warning {
29
- color: var(--g-color-utility-yellow-900);
30
- background-color: var(--g-color-utility-yellow-100);
29
+ color: var(--g-color-content-utility-warning);
30
+ background-color: var(--g-color-background-utility-warning);
31
31
  }
32
32
 
33
33
  .notice--error {
34
- color: var(--g-color-utility-red-900);
35
- background-color: var(--g-color-utility-red-100);
34
+ color: var(--g-color-content-utility-error);
35
+ background-color: var(--g-color-background-utility-error);
36
36
  }
37
37
 
38
38
  .notice__title {
39
- font-size: var(--g-theme-typography-body-default-font-size);
39
+ font-size: var(--g-typography-body-default-font-size);
40
40
  margin: 0;
41
41
  }
42
42
 
43
43
  .notice__icon {
44
- font-size: calc(var(--g-theme-typography-body-default-line-height) * 1em);
44
+ font-size: calc(var(--g-typography-body-default-line-height) * 1em);
45
45
  }
46
46
 
47
47
  .notice__content {
@@ -53,11 +53,11 @@
53
53
  color: inherit;
54
54
  opacity: 0.5;
55
55
  transition: 0.2s;
56
- font-size: calc(var(--g-theme-typography-body-default-line-height) * 1em);
56
+ font-size: calc(var(--g-typography-body-default-line-height) * 1em);
57
57
  line-height: 1;
58
- padding: var(--g-theme-spacing-sm);
59
- margin: calc(-1 * var(--g-theme-spacing-sm))
60
- calc(-1 * var(--g-theme-spacing-sm)) 0 0;
58
+ padding: var(--g-spacing-sm);
59
+ margin: calc(-1 * var(--g-spacing-sm))
60
+ calc(-1 * var(--g-spacing-sm)) 0 0;
61
61
  border: 0;
62
62
 
63
63
  &:hover,
@@ -0,0 +1 @@
1
+ export * from "./person.js";
@@ -0,0 +1,51 @@
1
+ import { LitElement, html } from "lit";
2
+ import { ifDefined } from "lit/directives/if-defined.js";
3
+ import personStyles from "./person.css" with { type: "css" };
4
+ import "../avatar/avatar.js";
5
+
6
+ export class GrantCodesPerson extends LitElement {
7
+ static properties = {
8
+ name: { type: String },
9
+ role: { type: String },
10
+ company: { type: String },
11
+ avatar: { type: String },
12
+ alt: { type: String },
13
+ size: { type: String },
14
+ };
15
+
16
+ static styles = [personStyles];
17
+
18
+ constructor() {
19
+ super();
20
+ this.name = "";
21
+ this.role = "";
22
+ this.company = "";
23
+ this.avatar = "";
24
+ this.alt = "";
25
+ this.size = "medium";
26
+ }
27
+
28
+ get meta() {
29
+ return [this.role, this.company].filter(Boolean).join(", ");
30
+ }
31
+
32
+ render() {
33
+ const meta = this.meta;
34
+
35
+ return html`
36
+ <div class="person">
37
+ <grantcodes-avatar
38
+ src=${ifDefined(this.avatar || undefined)}
39
+ name=${ifDefined(this.name || undefined)}
40
+ alt=${ifDefined(this.alt || undefined)}
41
+ size=${this.size}
42
+ ></grantcodes-avatar>
43
+ <div class="person__meta">
44
+ <cite class="person__name">${this.name}</cite>
45
+ ${meta ? html`<span class="person__details">${meta}</span>` : null}
46
+ <slot name="description" class="person__description"></slot>
47
+ </div>
48
+ </div>
49
+ `;
50
+ }
51
+ }
@@ -0,0 +1,39 @@
1
+ *,
2
+ *::before,
3
+ *::after {
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ :host {
8
+ display: block;
9
+ }
10
+
11
+ .person {
12
+ display: flex;
13
+ align-items: center;
14
+ gap: var(--g-spacing-sm);
15
+ }
16
+
17
+ .person__meta {
18
+ display: flex;
19
+ flex-direction: column;
20
+ gap: var(--g-spacing-xs);
21
+ min-inline-size: 0;
22
+ }
23
+
24
+ .person__name {
25
+ font: var(--g-typography-body-font);
26
+ font-style: normal;
27
+ color: var(--g-color-content-default);
28
+ }
29
+
30
+ .person__details {
31
+ font: var(--g-typography-body-sm-font);
32
+ color: var(--g-color-content-subtle);
33
+ }
34
+
35
+ ::slotted([slot="description"]) {
36
+ font: var(--g-typography-body-sm-font);
37
+ color: var(--g-color-content-subtle);
38
+ margin-block-start: var(--g-spacing-xs);
39
+ }
@@ -0,0 +1,8 @@
1
+ import { GrantCodesPerson } from "./person.component.js";
2
+
3
+ export * from "./person.component.js";
4
+ export default GrantCodesPerson;
5
+
6
+ if (!customElements.get("grantcodes-person")) {
7
+ customElements.define("grantcodes-person", GrantCodesPerson);
8
+ }
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ import { createComponent } from "@lit/react";
3
+ import { GrantCodesPerson } from "./person.js";
4
+
5
+ export const Person = createComponent({
6
+ tagName: "grantcodes-person",
7
+ elementClass: GrantCodesPerson,
8
+ react: React,
9
+ });
@@ -0,0 +1,59 @@
1
+ import { html } from "lit";
2
+ import { getStorybookHelpers } from "@wc-toolkit/storybook-helpers";
3
+ const { events, args, argTypes, template } =
4
+ getStorybookHelpers("grantcodes-person");
5
+ import "./person.js";
6
+
7
+ const meta = {
8
+ title: "Components/Person",
9
+ component: "grantcodes-person",
10
+ args: {
11
+ ...args,
12
+ name: "Tommy Tobasco",
13
+ role: "Designer",
14
+ company: "GrantCodes",
15
+ avatar: "https://placehold.co/160x160",
16
+ size: "small",
17
+ },
18
+ argTypes,
19
+ render: (storyArgs) => template(storyArgs),
20
+ parameters: {
21
+ actions: {
22
+ handles: events,
23
+ },
24
+ },
25
+ };
26
+
27
+ export default meta;
28
+
29
+ export const Default = {};
30
+
31
+ export const Initials = {
32
+ args: {
33
+ avatar: undefined,
34
+ },
35
+ };
36
+
37
+ export const WithDescription = {
38
+ args: {
39
+ name: "Sam Torres",
40
+ role: "CTO",
41
+ company: "Flowbase",
42
+ avatar: "https://i.pravatar.cc/80?img=12",
43
+ size: "medium",
44
+ },
45
+ render: (storyArgs) => html`
46
+ <grantcodes-person
47
+ name="${storyArgs.name}"
48
+ role="${storyArgs.role}"
49
+ company="${storyArgs.company}"
50
+ avatar="${storyArgs.avatar}"
51
+ size="${storyArgs.size}"
52
+ >
53
+ <span slot="description"
54
+ >Writes about engineering culture, distributed teams, and systems
55
+ thinking.</span
56
+ >
57
+ </grantcodes-person>
58
+ `,
59
+ };
@@ -0,0 +1,69 @@
1
+ import { afterEach, describe, it } from "node:test";
2
+ import { strict as assert } from "node:assert";
3
+ import { cleanup, fixture } from "../../test-utils/index.js";
4
+ import "./person.js";
5
+
6
+ describe("Person Component", () => {
7
+ let element;
8
+
9
+ afterEach(() => {
10
+ cleanup(element);
11
+ });
12
+
13
+ it("should render name and details", async () => {
14
+ element = await fixture("grantcodes-person", {
15
+ name: "Jane Doe",
16
+ role: "Designer",
17
+ company: "GrantCodes",
18
+ });
19
+
20
+ assert.strictEqual(
21
+ element.shadowRoot.querySelector(".person__name")?.textContent,
22
+ "Jane Doe",
23
+ );
24
+ assert.strictEqual(
25
+ element.shadowRoot.querySelector(".person__details")?.textContent,
26
+ "Designer, GrantCodes",
27
+ );
28
+ });
29
+
30
+ it("should omit details when role and company are missing", async () => {
31
+ element = await fixture("grantcodes-person", {
32
+ name: "Jane Doe",
33
+ });
34
+
35
+ assert.equal(
36
+ element.shadowRoot.querySelector(".person__details"),
37
+ null,
38
+ );
39
+ });
40
+
41
+ it("should render content in the description slot", async () => {
42
+ element = await fixture("grantcodes-person", {
43
+ name: "Jane Doe",
44
+ });
45
+
46
+ const description = document.createElement("span");
47
+ description.setAttribute("slot", "description");
48
+ description.textContent = "A short bio about Jane";
49
+ element.appendChild(description);
50
+
51
+ await element.updateComplete;
52
+
53
+ const slot = element.shadowRoot.querySelector(
54
+ 'slot[name="description"]',
55
+ );
56
+ assert.ok(slot, "description slot should exist");
57
+
58
+ const assignedNodes = slot.assignedNodes({ flatten: true });
59
+ const hasText = assignedNodes.some(
60
+ (node) =>
61
+ node.textContent &&
62
+ node.textContent.includes("A short bio about Jane"),
63
+ );
64
+ assert.ok(
65
+ hasText,
66
+ "description slot should contain the projected text",
67
+ );
68
+ });
69
+ });