@grantcodes/ui 2.0.2 → 2.1.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 (179) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/custom-elements.json +1926 -191
  3. package/package.json +22 -21
  4. package/src/components/accordion/accordion.component.js +33 -0
  5. package/src/components/accordion/accordion.js +6 -0
  6. package/src/components/accordion/accordion.stories.js +88 -0
  7. package/src/components/accordion/accordion.styles.js +66 -0
  8. package/src/components/accordion/index.js +6 -0
  9. package/src/components/app-bar/app-bar.component.js +1 -3
  10. package/src/components/app-bar/app-bar.js +0 -2
  11. package/src/components/app-bar/app-bar.styles.js +222 -221
  12. package/src/components/app-bar/app-bar.test.js +58 -17
  13. package/src/components/app-bar/index.js +0 -2
  14. package/src/components/avatar/avatar.js +0 -12
  15. package/src/components/avatar/avatar.stories.js +0 -12
  16. package/src/components/avatar/avatar.styles.js +19 -19
  17. package/src/components/avatar/avatar.test.js +4 -4
  18. package/src/components/avatar/index.js +1 -13
  19. package/src/components/badge/badge.js +0 -2
  20. package/src/components/badge/badge.styles.js +78 -81
  21. package/src/components/badge/badge.test.js +18 -5
  22. package/src/components/badge/index.js +0 -2
  23. package/src/components/breadcrumb/breadcrumb.component.js +9 -10
  24. package/src/components/breadcrumb/breadcrumb.js +6 -4
  25. package/src/components/breadcrumb/breadcrumb.styles.js +86 -90
  26. package/src/components/breadcrumb/breadcrumb.test.js +15 -5
  27. package/src/components/breadcrumb/index.js +0 -2
  28. package/src/components/button/button.component.js +2 -2
  29. package/src/components/button/button.styles.js +58 -86
  30. package/src/components/button/button.test.js +8 -4
  31. package/src/components/button/index.js +1 -1
  32. package/src/components/button-group/button-group.test.js +0 -2
  33. package/src/components/button-group/index.js +1 -1
  34. package/src/components/card/card.component.js +40 -9
  35. package/src/components/card/card.js +3 -1
  36. package/src/components/card/card.stories.js +18 -5
  37. package/src/components/card/card.styles.js +46 -20
  38. package/src/components/card/card.test.js +0 -2
  39. package/src/components/card/index.js +1 -1
  40. package/src/components/code-preview/code-preview.component.js +9 -9
  41. package/src/components/code-preview/code-preview.js +0 -1
  42. package/src/components/code-preview/code-preview.styles.js +3 -3
  43. package/src/components/code-preview/code-preview.test.js +29 -8
  44. package/src/components/code-preview/index.js +1 -1
  45. package/src/components/container/container.component.js +1 -0
  46. package/src/components/container/container.js +0 -1
  47. package/src/components/container/container.stories.js +12 -4
  48. package/src/components/container/container.styles.js +37 -35
  49. package/src/components/container/container.test.js +0 -2
  50. package/src/components/container/index.js +1 -1
  51. package/src/components/cta/cta.component.js +108 -0
  52. package/src/components/cta/cta.js +6 -0
  53. package/src/components/cta/cta.stories.js +56 -0
  54. package/src/components/cta/cta.styles.js +64 -0
  55. package/src/components/cta/index.js +1 -0
  56. package/src/components/dialog/dialog.js +0 -1
  57. package/src/components/dialog/dialog.styles.js +8 -8
  58. package/src/components/dialog/dialog.test.js +11 -5
  59. package/src/components/dialog/index.js +1 -1
  60. package/src/components/dropdown/dropdown.component.js +5 -3
  61. package/src/components/dropdown/dropdown.js +6 -4
  62. package/src/components/dropdown/dropdown.styles.js +5 -5
  63. package/src/components/dropdown/dropdown.test.js +20 -4
  64. package/src/components/dropdown/index.js +0 -2
  65. package/src/components/dropzone/dropzone.component.js +7 -6
  66. package/src/components/dropzone/dropzone.styles.js +4 -4
  67. package/src/components/dropzone/dropzone.test.js +6 -4
  68. package/src/components/dropzone/index.js +1 -1
  69. package/src/components/feature-list/feature-list.component.js +130 -0
  70. package/src/components/feature-list/feature-list.js +6 -0
  71. package/src/components/feature-list/feature-list.stories.js +117 -0
  72. package/src/components/feature-list/feature-list.styles.js +82 -0
  73. package/src/components/feature-list/index.js +1 -0
  74. package/src/components/footer/footer-column.styles.js +46 -47
  75. package/src/components/footer/footer.js +6 -2
  76. package/src/components/footer/footer.styles.js +6 -6
  77. package/src/components/footer/footer.test.js +9 -4
  78. package/src/components/footer/index.js +1 -1
  79. package/src/components/form-field/form-field.component.js +1 -3
  80. package/src/components/form-field/form-field.js +0 -1
  81. package/src/components/form-field/form-field.styles.js +35 -37
  82. package/src/components/form-field/form-field.test.js +9 -4
  83. package/src/components/form-field/index.js +1 -1
  84. package/src/components/gallery/gallery-image.js +0 -1
  85. package/src/components/gallery/gallery.js +0 -1
  86. package/src/components/gallery/gallery.styles.js +1 -1
  87. package/src/components/gallery/gallery.test.js +5 -3
  88. package/src/components/gallery/index.js +2 -2
  89. package/src/components/hero/hero.component.js +66 -0
  90. package/src/components/hero/hero.js +6 -0
  91. package/src/components/hero/hero.stories.js +53 -0
  92. package/src/components/hero/hero.styles.js +46 -0
  93. package/src/components/hero/index.js +1 -0
  94. package/src/components/icon/icon.js +3 -2
  95. package/src/components/icon/icon.stories.js +2 -1
  96. package/src/components/icon/icon.styles.js +23 -21
  97. package/src/components/icon/icon.test.js +2 -3
  98. package/src/components/icon/index.js +1 -1
  99. package/src/components/loading/index.js +1 -1
  100. package/src/components/loading/loading.js +3 -2
  101. package/src/components/loading/loading.styles.js +1 -1
  102. package/src/components/loading/loading.test.js +0 -2
  103. package/src/components/logo-cloud/index.js +1 -0
  104. package/src/components/logo-cloud/logo-cloud.component.js +81 -0
  105. package/src/components/logo-cloud/logo-cloud.js +6 -0
  106. package/src/components/logo-cloud/logo-cloud.stories.js +107 -0
  107. package/src/components/logo-cloud/logo-cloud.styles.js +68 -0
  108. package/src/components/media-text/index.js +1 -0
  109. package/src/components/media-text/media-text.component.js +100 -0
  110. package/src/components/media-text/media-text.js +6 -0
  111. package/src/components/media-text/media-text.stories.js +69 -0
  112. package/src/components/media-text/media-text.styles.js +66 -0
  113. package/src/components/newsletter/index.js +1 -0
  114. package/src/components/newsletter/newsletter.component.js +101 -0
  115. package/src/components/newsletter/newsletter.js +6 -0
  116. package/src/components/newsletter/newsletter.stories.js +59 -0
  117. package/src/components/newsletter/newsletter.styles.js +89 -0
  118. package/src/components/notice/index.js +1 -1
  119. package/src/components/notice/notice.js +0 -1
  120. package/src/components/notice/notice.styles.js +7 -7
  121. package/src/components/notice/notice.test.js +15 -5
  122. package/src/components/pagination/index.js +1 -1
  123. package/src/components/pagination/pagination.stories.js +1 -3
  124. package/src/components/pagination/pagination.styles.js +1 -1
  125. package/src/components/pagination/pagination.test.js +9 -4
  126. package/src/components/pricing/index.js +1 -0
  127. package/src/components/pricing/pricing.component.js +119 -0
  128. package/src/components/pricing/pricing.js +6 -0
  129. package/src/components/pricing/pricing.stories.js +123 -0
  130. package/src/components/pricing/pricing.styles.js +135 -0
  131. package/src/components/sidebar/index.js +0 -2
  132. package/src/components/sidebar/sidebar.component.js +12 -10
  133. package/src/components/sidebar/sidebar.js +3 -3
  134. package/src/components/sidebar/sidebar.stories.js +0 -2
  135. package/src/components/sidebar/sidebar.styles.js +181 -186
  136. package/src/components/sidebar/sidebar.test.js +48 -13
  137. package/src/components/stats/index.js +1 -0
  138. package/src/components/stats/stats.component.js +73 -0
  139. package/src/components/stats/stats.js +6 -0
  140. package/src/components/stats/stats.stories.js +64 -0
  141. package/src/components/stats/stats.styles.js +66 -0
  142. package/src/components/tabs/index.js +2 -2
  143. package/src/components/tabs/internal/tabs-button.component.js +1 -1
  144. package/src/components/tabs/internal/tabs-button.js +0 -1
  145. package/src/components/tabs/tab.js +0 -1
  146. package/src/components/tabs/tabs.js +3 -2
  147. package/src/components/tabs/tabs.styles.js +84 -74
  148. package/src/components/testimonials/index.js +1 -0
  149. package/src/components/testimonials/testimonials.component.js +97 -0
  150. package/src/components/testimonials/testimonials.js +6 -0
  151. package/src/components/testimonials/testimonials.stories.js +78 -0
  152. package/src/components/testimonials/testimonials.styles.js +82 -0
  153. package/src/components/toast/index.js +0 -2
  154. package/src/components/toast/toast.component.js +1 -3
  155. package/src/components/toast/toast.js +10 -5
  156. package/src/components/toast/toast.stories.js +9 -5
  157. package/src/components/toast/toast.styles.js +199 -201
  158. package/src/components/toast/toast.test.js +38 -10
  159. package/src/components/tooltip/index.js +1 -1
  160. package/src/components/tooltip/tooltip.js +3 -2
  161. package/src/components/tooltip/tooltip.styles.js +3 -3
  162. package/src/components/tooltip/tooltip.test.js +10 -4
  163. package/src/css/base.css +8 -5
  164. package/src/css/colors.stories.js +27 -28
  165. package/src/css/elements/forms/input.css +9 -41
  166. package/src/css/elements/media/image.css +1 -1
  167. package/src/css/themes/todomap.css +1 -0
  168. package/src/css/tokens.stories.js +26 -21
  169. package/src/css/typography.css +1 -3
  170. package/src/css/util/focus-ring.css +30 -0
  171. package/src/css/util/index.css +1 -2
  172. package/src/lib/styles/focus-ring.styles.js +34 -0
  173. package/src/main.js +10 -1
  174. package/src/pages/agency.stories.js +164 -0
  175. package/src/pages/blog-post.stories.js +381 -0
  176. package/src/pages/saas-landing.stories.js +307 -0
  177. package/src/test-utils/assert-helpers.js +10 -8
  178. package/src/css/util/functions.css +0 -16
  179. package/src/css/util/mixins.css +0 -63
@@ -1,192 +1,187 @@
1
1
  import { css } from "lit";
2
+ import { focusRingStyles } from "../../lib/styles/focus-ring.styles.js";
2
3
 
3
4
  export const sidebarStyles = css`
4
- *,
5
+ ${focusRingStyles}
6
+
7
+ *,
5
8
  *::before,
6
9
  *::after {
7
- box-sizing: border-box;
8
- }
9
-
10
- :host {
11
- display: contents;
12
- }
13
-
14
- .sidebar {
15
- position: relative;
16
- inline-size: var(--sidebar-width, 280px);
17
- block-size: 100%;
18
- background-color: var(--g-theme-color-background-default);
19
- border-inline-end: 1px solid var(--g-theme-color-border-default);
20
- overflow-y: auto;
21
- transition: inline-size 0.3s ease;
22
- flex-shrink: 0;
23
- }
24
-
25
- .sidebar--right {
26
- border-inline-end: none;
27
- border-inline-start: 1px solid var(--g-theme-color-border-default);
28
- }
29
-
30
- .sidebar--collapsed {
31
- inline-size: 60px;
32
- }
33
-
34
- .sidebar--collapsed .sidebar__content {
35
- padding-inline: 0.5rem;
36
- }
37
-
38
- .sidebar--collapsed .sidebar__content > *:not(.sidebar__toggle):not(.sidebar__mobile-toggle) {
39
- display: none;
40
- }
41
-
42
- .sidebar--collapsed .sidebar__toggle {
43
- inset-inline-end: 50%;
44
- transform: translateX(50%);
45
- }
46
-
47
- /* Mobile Toggle Button */
48
- .sidebar__mobile-toggle {
49
- all: unset;
50
- display: none;
51
- position: fixed;
52
- inset-block-start: 1rem;
53
- inset-inline-start: 1rem;
54
- z-index: 1001;
55
- inline-size: 2.5rem;
56
- block-size: 2.5rem;
57
- align-items: center;
58
- justify-content: center;
59
- background-color: var(--g-theme-color-background-default);
60
- border: 1px solid var(--g-theme-color-border-default);
61
- border-radius: var(--g-theme-border-radius-md);
62
- cursor: pointer;
63
- font-size: 1.5rem;
64
- transition: all 0.2s ease;
65
- }
66
-
67
- .sidebar__mobile-toggle:hover {
68
- background-color: var(--g-theme-color-background-subtle-hover);
69
- }
70
-
71
- .sidebar__mobile-toggle:focus-visible {
72
- outline: 2px solid var(--component-focus-ring-color);
73
- outline-offset: 2px;
74
- }
75
-
76
- /* Show mobile toggle on small screens */
77
- @media (max-width: 768px) {
78
- .sidebar__mobile-toggle {
79
- display: flex;
80
- }
81
-
82
- .sidebar {
83
- position: fixed;
84
- inset-block-start: 0;
85
- inset-block-end: 0;
86
- inset-inline-start: -100%;
87
- z-index: 1000;
88
- transition: inset-inline-start 0.3s ease;
89
- }
90
-
91
- .sidebar--right {
92
- inset-inline-start: auto;
93
- inset-inline-end: -100%;
94
- transition: inset-inline-end 0.3s ease;
95
- }
96
-
97
- .sidebar--drawer-open {
98
- inset-inline-start: 0;
99
- }
100
-
101
- .sidebar--right.sidebar--drawer-open {
102
- inset-inline-end: 0;
103
- inset-inline-start: auto;
104
- }
105
- }
106
-
107
- /* Overlay */
108
- .sidebar__overlay {
109
- display: none;
110
- position: fixed;
111
- inset: 0;
112
- background-color: rgba(0, 0, 0, 0.5);
113
- z-index: 999;
114
- animation: overlay-fade-in 0.2s ease-out;
115
- }
116
-
117
- @media (max-width: 768px) {
118
- .sidebar__overlay {
119
- display: block;
120
- }
121
- }
122
-
123
- @keyframes overlay-fade-in {
124
- from {
125
- opacity: 0;
126
- }
127
- to {
128
- opacity: 1;
129
- }
130
- }
131
-
132
- /* Collapse Toggle */
133
- .sidebar__toggle {
134
- all: unset;
135
- position: absolute;
136
- inset-block-start: 1rem;
137
- inset-inline-end: 0.5rem;
138
- inline-size: 1.5rem;
139
- block-size: 1.5rem;
140
- display: flex;
141
- align-items: center;
142
- justify-content: center;
143
- background-color: var(--g-theme-color-background-default);
144
- border: 1px solid var(--g-theme-color-border-default);
145
- border-radius: var(--g-theme-border-radius-md);
146
- cursor: pointer;
147
- font-size: 0.875rem;
148
- transition: all 0.2s ease;
149
- z-index: 10;
150
- }
151
-
152
- .sidebar__toggle:hover {
153
- background-color: var(--g-theme-color-background-subtle-hover);
154
- }
155
-
156
- .sidebar__toggle:focus-visible {
157
- outline: 2px solid var(--component-focus-ring-color);
158
- outline-offset: 2px;
159
- }
160
-
161
- .sidebar--right .sidebar__toggle {
162
- inset-inline-end: auto;
163
- inset-inline-start: 0.5rem;
164
- }
165
-
166
- .sidebar--right.sidebar--collapsed .sidebar__toggle {
167
- inset-inline-start: 50%;
168
- inset-inline-end: auto;
169
- transform: translateX(-50%);
170
- }
171
-
172
- @media (max-width: 768px) {
173
- .sidebar__toggle {
174
- display: none;
175
- }
176
- }
177
-
178
- /* Content */
179
- .sidebar__content {
180
- padding-block: 1rem;
181
- padding-inline: 1rem;
182
- }
183
-
184
- /* View transitions */
185
- @media (prefers-reduced-motion: no-preference) {
186
- @supports (view-transition-name: auto) {
187
- .sidebar {
188
- view-transition-name: sidebar;
189
- }
190
- }
191
- }
10
+ box-sizing: border-box;
11
+ }
12
+
13
+ :host {
14
+ display: contents;
15
+ }
16
+
17
+ .sidebar {
18
+ position: relative;
19
+ inline-size: var(--sidebar-width, 280px);
20
+ block-size: 100%;
21
+ background-color: var(--g-theme-color-background-default);
22
+ border-inline-end: 1px solid var(--g-theme-color-border-default);
23
+ overflow-y: auto;
24
+ transition: inline-size 0.3s ease;
25
+ flex-shrink: 0;
26
+ }
27
+
28
+ .sidebar--right {
29
+ border-inline-end: none;
30
+ border-inline-start: 1px solid var(--g-theme-color-border-default);
31
+ }
32
+
33
+ .sidebar--collapsed {
34
+ inline-size: 60px;
35
+ }
36
+
37
+ .sidebar--collapsed .sidebar__content {
38
+ padding-inline: var(--g-theme-spacing-sm);
39
+ }
40
+
41
+ .sidebar--collapsed
42
+ .sidebar__content
43
+ > *:not(.sidebar__toggle):not(.sidebar__mobile-toggle) {
44
+ display: none;
45
+ }
46
+
47
+ .sidebar--collapsed .sidebar__toggle {
48
+ inset-inline-end: 50%;
49
+ transform: translateX(50%);
50
+ }
51
+
52
+ /* Mobile Toggle Button */
53
+ .sidebar__mobile-toggle {
54
+ all: unset;
55
+ display: none;
56
+ position: fixed;
57
+ inset-block-start: var(--g-theme-spacing-md);
58
+ inset-inline-start: var(--g-theme-spacing-md);
59
+ z-index: 1001;
60
+ inline-size: 2.5rem;
61
+ block-size: 2.5rem;
62
+ align-items: center;
63
+ justify-content: center;
64
+ background-color: var(--g-theme-color-background-default);
65
+ border: 1px solid var(--g-theme-color-border-default);
66
+ border-radius: var(--g-theme-border-radius-md);
67
+ cursor: pointer;
68
+ font-size: var(--g-theme-typography-title-default-font-size);
69
+ transition: all 0.2s ease;
70
+ }
71
+
72
+ .sidebar__mobile-toggle:hover {
73
+ background-color: var(--g-theme-color-background-subtle-hover);
74
+ }
75
+
76
+ /* Show mobile toggle on small screens */
77
+ @media (max-width: 768px) {
78
+ .sidebar__mobile-toggle {
79
+ display: flex;
80
+ }
81
+
82
+ .sidebar {
83
+ position: fixed;
84
+ inset-block-start: 0;
85
+ inset-block-end: 0;
86
+ inset-inline-start: -100%;
87
+ z-index: 1000;
88
+ transition: inset-inline-start 0.3s ease;
89
+ }
90
+
91
+ .sidebar--right {
92
+ inset-inline-start: auto;
93
+ inset-inline-end: -100%;
94
+ transition: inset-inline-end 0.3s ease;
95
+ }
96
+
97
+ .sidebar--drawer-open {
98
+ inset-inline-start: 0;
99
+ }
100
+
101
+ .sidebar--right.sidebar--drawer-open {
102
+ inset-inline-end: 0;
103
+ inset-inline-start: auto;
104
+ }
105
+ }
106
+
107
+ /* Overlay */
108
+ .sidebar__overlay {
109
+ display: none;
110
+ position: fixed;
111
+ inset: 0;
112
+ background-color: rgba(0, 0, 0, 0.5);
113
+ z-index: 999;
114
+ animation: overlay-fade-in 0.2s ease-out;
115
+ }
116
+
117
+ @media (max-width: 768px) {
118
+ .sidebar__overlay {
119
+ display: block;
120
+ }
121
+ }
122
+
123
+ @keyframes overlay-fade-in {
124
+ from {
125
+ opacity: 0;
126
+ }
127
+ to {
128
+ opacity: 1;
129
+ }
130
+ }
131
+
132
+ /* Collapse Toggle */
133
+ .sidebar__toggle {
134
+ all: unset;
135
+ position: absolute;
136
+ inset-block-start: var(--g-theme-spacing-md);
137
+ inset-inline-end: var(--g-theme-spacing-sm);
138
+ inline-size: 1.5rem;
139
+ block-size: 1.5rem;
140
+ display: flex;
141
+ align-items: center;
142
+ justify-content: center;
143
+ background-color: var(--g-theme-color-background-default);
144
+ border: 1px solid var(--g-theme-color-border-default);
145
+ border-radius: var(--g-theme-border-radius-md);
146
+ cursor: pointer;
147
+ font-size: var(--g-typography-font-size-14);
148
+ transition: all 0.2s ease;
149
+ z-index: 10;
150
+ }
151
+
152
+ .sidebar__toggle:hover {
153
+ background-color: var(--g-theme-color-background-subtle-hover);
154
+ }
155
+
156
+ .sidebar--right .sidebar__toggle {
157
+ inset-inline-end: auto;
158
+ inset-inline-start: var(--g-theme-spacing-sm);
159
+ }
160
+
161
+ .sidebar--right.sidebar--collapsed .sidebar__toggle {
162
+ inset-inline-start: 50%;
163
+ inset-inline-end: auto;
164
+ transform: translateX(-50%);
165
+ }
166
+
167
+ @media (max-width: 768px) {
168
+ .sidebar__toggle {
169
+ display: none;
170
+ }
171
+ }
172
+
173
+ /* Content */
174
+ .sidebar__content {
175
+ padding-block: var(--g-theme-spacing-md);
176
+ padding-inline: var(--g-theme-spacing-md);
177
+ }
178
+
179
+ /* View transitions */
180
+ @media (prefers-reduced-motion: no-preference) {
181
+ @supports (view-transition-name: auto) {
182
+ .sidebar {
183
+ view-transition-name: sidebar;
184
+ }
185
+ }
186
+ }
192
187
  `;
@@ -18,17 +18,29 @@ describe("Sidebar Component", () => {
18
18
 
19
19
  it("should have left position by default", async () => {
20
20
  element = await fixture("grantcodes-sidebar");
21
- assert.strictEqual(element.position, "left", "Should have left position by default");
21
+ assert.strictEqual(
22
+ element.position,
23
+ "left",
24
+ "Should have left position by default",
25
+ );
22
26
  });
23
27
 
24
28
  it("should not be collapsed by default", async () => {
25
29
  element = await fixture("grantcodes-sidebar");
26
- assert.strictEqual(element.collapsed, false, "Should not be collapsed by default");
30
+ assert.strictEqual(
31
+ element.collapsed,
32
+ false,
33
+ "Should not be collapsed by default",
34
+ );
27
35
  });
28
36
 
29
37
  it("should be collapsible by default", async () => {
30
38
  element = await fixture("grantcodes-sidebar");
31
- assert.strictEqual(element.collapsible, true, "Should be collapsible by default");
39
+ assert.strictEqual(
40
+ element.collapsible,
41
+ true,
42
+ "Should be collapsible by default",
43
+ );
32
44
  });
33
45
 
34
46
  it("should have default width", async () => {
@@ -99,7 +111,11 @@ describe("Sidebar Component", () => {
99
111
  click(toggle);
100
112
  await element.updateComplete;
101
113
 
102
- assert.strictEqual(element.collapsed, true, "Should be collapsed after click");
114
+ assert.strictEqual(
115
+ element.collapsed,
116
+ true,
117
+ "Should be collapsed after click",
118
+ );
103
119
  });
104
120
 
105
121
  it("should emit toggle event when collapsed state changes", async () => {
@@ -117,7 +133,11 @@ describe("Sidebar Component", () => {
117
133
 
118
134
  await element.updateComplete;
119
135
 
120
- assert.strictEqual(toggledState, true, "Toggle event should fire with collapsed state");
136
+ assert.strictEqual(
137
+ toggledState,
138
+ true,
139
+ "Toggle event should fire with collapsed state",
140
+ );
121
141
  });
122
142
 
123
143
  it("should apply custom width", async () => {
@@ -127,12 +147,17 @@ describe("Sidebar Component", () => {
127
147
 
128
148
  const aside = element.shadowRoot.querySelector("aside");
129
149
  const style = aside.getAttribute("style");
130
- assert.ok(style.includes("--sidebar-width: 350px"), "Should set custom width");
150
+ assert.ok(
151
+ style.includes("--sidebar-width: 350px"),
152
+ "Should set custom width",
153
+ );
131
154
  });
132
155
 
133
156
  it("should have mobile toggle button", async () => {
134
157
  element = await fixture("grantcodes-sidebar");
135
- const mobileToggle = element.shadowRoot.querySelector(".sidebar__mobile-toggle");
158
+ const mobileToggle = element.shadowRoot.querySelector(
159
+ ".sidebar__mobile-toggle",
160
+ );
136
161
  assert.ok(mobileToggle, "Mobile toggle button should exist");
137
162
  });
138
163
 
@@ -150,14 +175,24 @@ describe("Sidebar Component", () => {
150
175
 
151
176
  it("should toggle mobile drawer when mobile toggle is clicked", async () => {
152
177
  element = await fixture("grantcodes-sidebar");
153
- const mobileToggle = element.shadowRoot.querySelector(".sidebar__mobile-toggle");
178
+ const mobileToggle = element.shadowRoot.querySelector(
179
+ ".sidebar__mobile-toggle",
180
+ );
154
181
 
155
- assert.strictEqual(element._drawerOpen, false, "Drawer should start closed");
182
+ assert.strictEqual(
183
+ element._drawerOpen,
184
+ false,
185
+ "Drawer should start closed",
186
+ );
156
187
 
157
188
  click(mobileToggle);
158
189
  await element.updateComplete;
159
190
 
160
- assert.strictEqual(element._drawerOpen, true, "Drawer should be open after click");
191
+ assert.strictEqual(
192
+ element._drawerOpen,
193
+ true,
194
+ "Drawer should be open after click",
195
+ );
161
196
  });
162
197
 
163
198
  it("should emit drawer-toggle event", async () => {
@@ -168,7 +203,9 @@ describe("Sidebar Component", () => {
168
203
  drawerState = e.detail.open;
169
204
  });
170
205
 
171
- const mobileToggle = element.shadowRoot.querySelector(".sidebar__mobile-toggle");
206
+ const mobileToggle = element.shadowRoot.querySelector(
207
+ ".sidebar__mobile-toggle",
208
+ );
172
209
  click(mobileToggle);
173
210
 
174
211
  await element.updateComplete;
@@ -192,5 +229,3 @@ describe("Sidebar Component", () => {
192
229
  assert.ok(!overlay, "Overlay should not exist when drawer is closed");
193
230
  });
194
231
  });
195
-
196
-
@@ -0,0 +1 @@
1
+ export * from "./stats.js";
@@ -0,0 +1,73 @@
1
+ import { LitElement, html } from "lit";
2
+ import { statsStyles } from "./stats.styles.js";
3
+
4
+ export class GrantCodesStats extends LitElement {
5
+ static styles = [statsStyles];
6
+
7
+ static properties = {
8
+ /**
9
+ * Optional section heading.
10
+ * @type {string}
11
+ */
12
+ title: { type: String },
13
+ /**
14
+ * Stat items as a JSON string array: `[{"label":"...","value":"...","context":"..."}]`.
15
+ * @type {string}
16
+ */
17
+ items: { type: String },
18
+ /**
19
+ * Number of columns in the grid (1–6).
20
+ * @type {number}
21
+ */
22
+ columns: { type: Number },
23
+ };
24
+
25
+ constructor() {
26
+ super();
27
+ this.title = "";
28
+ this.items = "[]";
29
+ this.columns = 4;
30
+ }
31
+
32
+ get _items() {
33
+ try {
34
+ return JSON.parse(this.items);
35
+ } catch {
36
+ return [];
37
+ }
38
+ }
39
+
40
+ render() {
41
+ const items = this._items;
42
+ return html`
43
+ <section class="stats">
44
+ <div class="stats__container">
45
+ ${
46
+ this.title
47
+ ? html`<h2 class="stats__title">${this.title}</h2>`
48
+ : null
49
+ }
50
+ <ul
51
+ class="stats__grid"
52
+ style="--columns: ${this.columns}"
53
+ role="list"
54
+ >
55
+ ${items.map(
56
+ (item) => html`
57
+ <li class="stats__item">
58
+ <span class="stats__value">${item.value}</span>
59
+ <span class="stats__label">${item.label}</span>
60
+ ${
61
+ item.context
62
+ ? html`<span class="stats__context">${item.context}</span>`
63
+ : null
64
+ }
65
+ </li>
66
+ `,
67
+ )}
68
+ </ul>
69
+ </div>
70
+ </section>
71
+ `;
72
+ }
73
+ }
@@ -0,0 +1,6 @@
1
+ import { GrantCodesStats } from "./stats.component.js";
2
+
3
+ export * from "./stats.component.js";
4
+ export default GrantCodesStats;
5
+
6
+ customElements.define("grantcodes-stats", GrantCodesStats);
@@ -0,0 +1,64 @@
1
+ import { getStorybookHelpers } from "@wc-toolkit/storybook-helpers";
2
+ import "./stats.js";
3
+
4
+ const { events, args, argTypes } = getStorybookHelpers("grantcodes-stats");
5
+
6
+ const meta = {
7
+ title: "Blocks/Stats",
8
+ component: "grantcodes-stats",
9
+ args,
10
+ argTypes,
11
+ parameters: {
12
+ actions: {
13
+ handles: events,
14
+ },
15
+ layout: "fullscreen",
16
+ },
17
+ };
18
+
19
+ export default meta;
20
+
21
+ /**
22
+ * Default stats block with 4 metrics.
23
+ */
24
+ export const Default = {
25
+ args: {
26
+ title: "By the numbers",
27
+ items: JSON.stringify([
28
+ { value: "10k+", label: "Developers", context: "using our components" },
29
+ { value: "99.9%", label: "Uptime", context: "in the last 12 months" },
30
+ { value: "42ms", label: "Avg load time" },
31
+ { value: "24/7", label: "Support" },
32
+ ]),
33
+ columns: 4,
34
+ },
35
+ };
36
+
37
+ /**
38
+ * Three stats — useful when you have fewer key metrics.
39
+ */
40
+ export const ThreeStats = {
41
+ args: {
42
+ items: JSON.stringify([
43
+ { value: "500+", label: "Components" },
44
+ { value: "15", label: "Themes" },
45
+ { value: "0", label: "Dependencies" },
46
+ ]),
47
+ columns: 3,
48
+ },
49
+ };
50
+
51
+ /**
52
+ * Stats without a heading — embed directly into a page section.
53
+ */
54
+ export const NoHeading = {
55
+ args: {
56
+ items: JSON.stringify([
57
+ { value: "$0", label: "Cost to start" },
58
+ { value: "MIT", label: "License" },
59
+ { value: "100%", label: "Open source" },
60
+ { value: "∞", label: "Customisable" },
61
+ ]),
62
+ columns: 4,
63
+ },
64
+ };