@grantcodes/ui 2.5.1 → 2.7.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 (56) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/custom-elements.json +617 -19
  3. package/package.json +4 -4
  4. package/src/components/accordion/accordion.component.js +4 -1
  5. package/src/components/accordion/accordion.css +26 -18
  6. package/src/components/app-bar/app-bar.component.js +45 -9
  7. package/src/components/app-bar/app-bar.css +119 -88
  8. package/src/components/app-bar/app-bar.stories.js +75 -37
  9. package/src/components/app-bar/app-bar.test.js +7 -1
  10. package/src/components/app-bar/nav-link.component.js +15 -0
  11. package/src/components/app-bar/nav-link.css +58 -0
  12. package/src/components/app-bar/nav-link.js +6 -0
  13. package/src/components/app-bar/nav-link.react.js +9 -0
  14. package/src/components/code-preview/code-preview.css +5 -0
  15. package/src/components/container/container.css +6 -0
  16. package/src/components/countdown/countdown.component.js +180 -0
  17. package/src/components/countdown/countdown.css +62 -0
  18. package/src/components/countdown/countdown.js +6 -0
  19. package/src/components/countdown/countdown.react.js +9 -0
  20. package/src/components/countdown/countdown.stories.js +65 -0
  21. package/src/components/countdown/index.js +1 -0
  22. package/src/components/cta/cta.css +6 -0
  23. package/src/components/dialog/dialog.css +5 -0
  24. package/src/components/feature-list/feature-list.css +6 -0
  25. package/src/components/footer/footer.css +3 -1
  26. package/src/components/form-field/form-field.css +6 -0
  27. package/src/components/gallery/gallery.css +5 -0
  28. package/src/components/hero/hero.component.js +7 -0
  29. package/src/components/hero/hero.css +18 -1
  30. package/src/components/hero/hero.stories.js +30 -0
  31. package/src/components/icon/icon.css +6 -0
  32. package/src/components/loading/loading.css +5 -0
  33. package/src/components/logo-cloud/logo-cloud.css +6 -0
  34. package/src/components/map/index.js +1 -0
  35. package/src/components/map/map.component.js +135 -0
  36. package/src/components/map/map.css +41 -0
  37. package/src/components/map/map.js +6 -0
  38. package/src/components/map/map.react.js +9 -0
  39. package/src/components/map/map.stories.js +68 -0
  40. package/src/components/media-text/media-text.css +6 -0
  41. package/src/components/newsletter/newsletter.css +6 -0
  42. package/src/components/notice/notice.css +5 -0
  43. package/src/components/pagination/pagination.css +5 -0
  44. package/src/components/pricing/pricing.css +6 -0
  45. package/src/components/stats/stats.css +6 -0
  46. package/src/components/testimonials/testimonials.css +6 -0
  47. package/src/components/tooltip/tooltip.css +5 -0
  48. package/src/css/all.css +3 -1
  49. package/src/css/base.css +6 -247
  50. package/src/css/elements/a.css +8 -8
  51. package/src/css/reset.css +246 -0
  52. package/src/lib/styles/all.css +2 -0
  53. package/src/main.js +2 -0
  54. package/src/pages/blog-post.stories.js +7 -19
  55. package/src/react.js +3 -0
  56. package/src/types.d.ts +18 -0
@@ -0,0 +1,135 @@
1
+ import { LitElement, html, nothing } from "lit";
2
+ import mapStyles from "./map.css" with { type: "css" };
3
+
4
+ const DARK_FILTER = "invert(1) hue-rotate(180deg) brightness(0.95) contrast(0.9)";
5
+
6
+ export class GrantCodesMap extends LitElement {
7
+ static styles = [mapStyles];
8
+
9
+ static properties = {
10
+ /**
11
+ * Latitude coordinate.
12
+ * @type {string}
13
+ */
14
+ lat: { type: String },
15
+ /**
16
+ * Longitude coordinate.
17
+ * @type {string}
18
+ */
19
+ lng: { type: String },
20
+ /**
21
+ * Map zoom level (1-18).
22
+ * @type {number}
23
+ */
24
+ zoom: { type: Number },
25
+ /**
26
+ * Accessible label for the map iframe.
27
+ * @type {string}
28
+ */
29
+ label: { type: String },
30
+ /**
31
+ * URL for a "Get directions" link below the map.
32
+ * @type {string}
33
+ */
34
+ "directions-url": { type: String, attribute: "directions-url" },
35
+ /**
36
+ * Height of the map. Accepts any CSS length value.
37
+ * @type {string}
38
+ */
39
+ height: { type: String },
40
+ };
41
+
42
+ constructor() {
43
+ super();
44
+ this.lat = "";
45
+ this.lng = "";
46
+ this.zoom = 14;
47
+ this.label = "Map";
48
+ this["directions-url"] = "";
49
+ this.height = "";
50
+ this._darkQuery = window.matchMedia("(prefers-color-scheme: dark)");
51
+ this._onSchemeChange = () => this._updateFilter();
52
+ }
53
+
54
+ connectedCallback() {
55
+ super.connectedCallback();
56
+ this._observer = new MutationObserver(() => this._updateFilter());
57
+ this._observer.observe(document.documentElement, {
58
+ attributes: true,
59
+ attributeFilter: ["class"],
60
+ });
61
+ this._darkQuery.addEventListener("change", this._onSchemeChange);
62
+ this._updateFilter();
63
+ }
64
+
65
+ disconnectedCallback() {
66
+ super.disconnectedCallback();
67
+ this._observer?.disconnect();
68
+ this._darkQuery.removeEventListener("change", this._onSchemeChange);
69
+ }
70
+
71
+ _isDark() {
72
+ if (document.documentElement.classList.contains("dark")) return true;
73
+ if (document.documentElement.classList.contains("light")) return false;
74
+ return this._darkQuery.matches;
75
+ }
76
+
77
+ _updateFilter() {
78
+ this.style.setProperty(
79
+ "--g-map-filter",
80
+ this._isDark() ? DARK_FILTER : "none",
81
+ );
82
+ }
83
+
84
+ get _embedUrl() {
85
+ if (!this.lat || !this.lng) return "";
86
+ const bbox = this._getBoundingBox(
87
+ Number.parseFloat(this.lat),
88
+ Number.parseFloat(this.lng),
89
+ this.zoom,
90
+ );
91
+ return `https://www.openstreetmap.org/export/embed.html?bbox=${bbox}&marker=${this.lat},${this.lng}&layers=mapnik`;
92
+ }
93
+
94
+ /**
95
+ * Calculate a bounding box around a point for the given zoom level.
96
+ */
97
+ _getBoundingBox(lat, lng, zoom) {
98
+ const spread = 360 / 2 ** zoom;
99
+ return `${lng - spread},${lat - spread},${lng + spread},${lat + spread}`;
100
+ }
101
+
102
+ render() {
103
+ if (!this.lat || !this.lng) {
104
+ return nothing;
105
+ }
106
+
107
+ const heightStyle = this.height
108
+ ? `--map-height: ${this.height}`
109
+ : "";
110
+
111
+ return html`
112
+ <div class="map" style="${heightStyle}">
113
+ <iframe
114
+ class="map__iframe"
115
+ src="${this._embedUrl}"
116
+ title="${this.label}"
117
+ loading="lazy"
118
+ referrerpolicy="no-referrer"
119
+ frameborder="0"
120
+ seamless
121
+ ></iframe>
122
+ ${
123
+ this["directions-url"]
124
+ ? html`<a
125
+ class="map__directions"
126
+ href="${this["directions-url"]}"
127
+ target="_blank"
128
+ rel="noopener noreferrer"
129
+ >Get directions</a>`
130
+ : nothing
131
+ }
132
+ </div>
133
+ `;
134
+ }
135
+ }
@@ -0,0 +1,41 @@
1
+ *,
2
+ *::before,
3
+ *::after {
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ :host {
8
+ display: block;
9
+ }
10
+
11
+ .map {
12
+ position: relative;
13
+ border: 1px solid var(--g-theme-color-border-default);
14
+ border-radius: var(--g-theme-border-radius-md, 0.5rem);
15
+ overflow: hidden;
16
+ }
17
+
18
+ .map__iframe {
19
+ display: block;
20
+ vertical-align: top;
21
+ width: 100%;
22
+ height: var(--map-height, 21rem);
23
+ border: 0;
24
+ filter: var(--g-map-filter, none);
25
+ }
26
+
27
+ .map__directions {
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-brand, #7c3aed);
32
+ text-decoration: none;
33
+ border-block-start: 1px solid var(--g-theme-color-border-default);
34
+ background: var(--g-theme-color-background-subtle);
35
+ transition: background-color 0.2s ease;
36
+ }
37
+
38
+ .map__directions:hover {
39
+ background: var(--g-theme-color-background-subtle-hover);
40
+ text-decoration: underline;
41
+ }
@@ -0,0 +1,6 @@
1
+ import { GrantCodesMap } from "./map.component.js";
2
+
3
+ export * from "./map.component.js";
4
+ export default GrantCodesMap;
5
+
6
+ customElements.define("grantcodes-map", GrantCodesMap);
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ import { createComponent } from "@lit/react";
3
+ import { GrantCodesMap } from "./map.js";
4
+
5
+ export const MapEmbed = createComponent({
6
+ tagName: "grantcodes-map",
7
+ elementClass: GrantCodesMap,
8
+ react: React,
9
+ });
@@ -0,0 +1,68 @@
1
+ import { getStorybookHelpers } from "@wc-toolkit/storybook-helpers";
2
+ import "./map.js";
3
+
4
+ const { events, args, argTypes } = getStorybookHelpers("grantcodes-map");
5
+
6
+ const meta = {
7
+ title: "Components/Map",
8
+ component: "grantcodes-map",
9
+ args,
10
+ argTypes,
11
+ parameters: {
12
+ actions: {
13
+ handles: events,
14
+ },
15
+ },
16
+ };
17
+
18
+ export default meta;
19
+
20
+ /**
21
+ * Default map showing a location in Granollers, Catalonia.
22
+ */
23
+ export const Default = {
24
+ args: {
25
+ lat: "41.648747",
26
+ lng: "2.161975",
27
+ zoom: 14,
28
+ label: "Granollers, Catalonia",
29
+ },
30
+ };
31
+
32
+ /**
33
+ * Map with a directions link below.
34
+ */
35
+ export const WithDirections = {
36
+ args: {
37
+ lat: "41.648747",
38
+ lng: "2.161975",
39
+ zoom: 14,
40
+ label: "Granollers, Catalonia",
41
+ "directions-url": "https://maps.google.com/?q=41.648747,2.161975",
42
+ },
43
+ };
44
+
45
+ /**
46
+ * Map with a custom height.
47
+ */
48
+ export const CustomHeight = {
49
+ args: {
50
+ lat: "48.8566",
51
+ lng: "2.3522",
52
+ zoom: 12,
53
+ label: "Paris, France",
54
+ height: "600px",
55
+ },
56
+ };
57
+
58
+ /**
59
+ * Zoomed-out world view.
60
+ */
61
+ export const ZoomedOut = {
62
+ args: {
63
+ lat: "0",
64
+ lng: "0",
65
+ zoom: 2,
66
+ label: "World map",
67
+ },
68
+ };
@@ -1,3 +1,9 @@
1
+ *,
2
+ *::before,
3
+ *::after {
4
+ box-sizing: border-box;
5
+ }
6
+
1
7
  :host {
2
8
  display: block;
3
9
  }
@@ -1,3 +1,9 @@
1
+ *,
2
+ *::before,
3
+ *::after {
4
+ box-sizing: border-box;
5
+ }
6
+
1
7
  :host {
2
8
  display: block;
3
9
  }
@@ -1,3 +1,8 @@
1
+ *,
2
+ *::before,
3
+ *::after {
4
+ box-sizing: border-box;
5
+ }
1
6
 
2
7
  .notice {
3
8
  display: flex;
@@ -1,3 +1,8 @@
1
+ *,
2
+ *::before,
3
+ *::after {
4
+ box-sizing: border-box;
5
+ }
1
6
 
2
7
  .pagination {
3
8
  display: flex;
@@ -1,3 +1,9 @@
1
+ *,
2
+ *::before,
3
+ *::after {
4
+ box-sizing: border-box;
5
+ }
6
+
1
7
  :host {
2
8
  display: block;
3
9
  }
@@ -1,3 +1,9 @@
1
+ *,
2
+ *::before,
3
+ *::after {
4
+ box-sizing: border-box;
5
+ }
6
+
1
7
  :host {
2
8
  display: block;
3
9
  }
@@ -1,3 +1,9 @@
1
+ *,
2
+ *::before,
3
+ *::after {
4
+ box-sizing: border-box;
5
+ }
6
+
1
7
  :host {
2
8
  display: block;
3
9
  }
@@ -1,3 +1,8 @@
1
+ *,
2
+ *::before,
3
+ *::after {
4
+ box-sizing: border-box;
5
+ }
1
6
 
2
7
  :host,
3
8
  .tooltip,
package/src/css/all.css CHANGED
@@ -1,6 +1,8 @@
1
+ /* Basically the same as base.css but with component styles included */
2
+
1
3
  @layer reset, base, utilities, components;
2
4
 
3
- @import "./base.css" layer(reset);
5
+ @import "./reset.css" layer(reset);
4
6
  @import "./typography.css" layer(base);
5
7
  @import "./elements.css" layer(base);
6
8
  @import "./util/index.css" layer(utilities);
package/src/css/base.css CHANGED
@@ -1,248 +1,7 @@
1
- /* CSS reset inspired by sanitize.css (https://csstools.github.io/sanitize.css/)
2
- Inlined to avoid broken relative node_modules paths when installed externally. */
1
+ @layer reset, base, utilities;
3
2
 
4
- /* Box sizing and background */
5
- *,
6
- ::before,
7
- ::after {
8
- box-sizing: border-box;
9
- background-repeat: no-repeat;
10
- }
11
-
12
- ::before,
13
- ::after {
14
- text-decoration: inherit;
15
- vertical-align: inherit;
16
- }
17
-
18
- /* Root defaults */
19
- :where(:root) {
20
- cursor: default;
21
- line-height: 1.5;
22
- overflow-wrap: break-word;
23
- -moz-tab-size: 4;
24
- tab-size: 4;
25
- -webkit-tap-highlight-color: transparent;
26
- -webkit-text-size-adjust: 100%;
27
- }
28
-
29
- html {
30
- font: var(--g-theme-typography-body);
31
- }
32
-
33
- code,
34
- kbd,
35
- samp,
36
- pre {
37
- font-family:
38
- ui-monospace,
39
- "Menlo",
40
- "Consolas",
41
- "Roboto Mono",
42
- "Ubuntu Monospace",
43
- "Noto Mono",
44
- "Oxygen Mono",
45
- "Liberation Mono",
46
- monospace,
47
- "Apple Color Emoji",
48
- "Segoe UI Emoji",
49
- "Segoe UI Symbol",
50
- "Noto Color Emoji";
51
- }
52
-
53
- /* Sections */
54
- :where(body) {
55
- margin: 0;
56
- }
57
-
58
- /* Grouping */
59
- :where(dl, ol, ul) :where(dl, ol, ul) {
60
- margin: 0;
61
- }
62
-
63
- :where(hr) {
64
- color: inherit;
65
- height: 0;
66
- }
67
-
68
- :where(nav) :where(ol, ul) {
69
- list-style-type: none;
70
- padding: 0;
71
- }
72
-
73
- /* Prevent VoiceOver from ignoring list semantics in Safari */
74
- :where(nav li)::before {
75
- content: "\200B";
76
- float: left;
77
- }
78
-
79
- :where(pre) {
80
- font-size: 1em;
81
- overflow: auto;
82
- }
83
-
84
- /* Text-level semantics */
85
- :where(abbr[title]) {
86
- text-decoration: underline dotted;
87
- }
88
-
89
- :where(b, strong) {
90
- font-weight: bolder;
91
- }
92
-
93
- :where(code, kbd, samp) {
94
- font-size: 1em;
95
- }
96
-
97
- :where(small) {
98
- font-size: 80%;
99
- }
100
-
101
- /* Embedded content */
102
- :where(audio, canvas, iframe, img, svg, video) {
103
- vertical-align: middle;
104
- }
105
-
106
- :where(iframe) {
107
- border-style: none;
108
- }
109
-
110
- :where(svg:not([fill])) {
111
- fill: currentColor;
112
- }
113
-
114
- /* Tabular data */
115
- :where(table) {
116
- border-collapse: collapse;
117
- border-color: inherit;
118
- text-indent: 0;
119
- }
120
-
121
- /* Forms — baseline normalisation */
122
- :where(button, input, select) {
123
- margin: 0;
124
- }
125
-
126
- :where(button, [type="button" i], [type="reset" i], [type="submit" i]) {
127
- -webkit-appearance: button;
128
- }
129
-
130
- :where(fieldset) {
131
- border: 1px solid #a0a0a0;
132
- }
133
-
134
- :where(progress) {
135
- vertical-align: baseline;
136
- }
137
-
138
- :where(textarea) {
139
- margin: 0;
140
- resize: vertical;
141
- }
142
-
143
- :where([type="search" i]) {
144
- -webkit-appearance: textfield;
145
- outline-offset: -2px;
146
- }
147
-
148
- ::-webkit-inner-spin-button,
149
- ::-webkit-outer-spin-button {
150
- height: auto;
151
- }
152
-
153
- ::-webkit-input-placeholder {
154
- color: inherit;
155
- opacity: 0.54;
156
- }
157
-
158
- ::-webkit-search-decoration {
159
- -webkit-appearance: none;
160
- }
161
-
162
- ::-webkit-file-upload-button {
163
- -webkit-appearance: button;
164
- font: inherit;
165
- }
166
-
167
- /* Forms — typography and colour inheritance */
168
- :where(button, input, select, textarea) {
169
- background-color: transparent;
170
- border: 1px solid WindowFrame;
171
- color: inherit;
172
- font: inherit;
173
- letter-spacing: inherit;
174
- padding: 0.25em 0.375em;
175
- }
176
-
177
- :where([type="color" i], [type="range" i]) {
178
- border-width: 0;
179
- padding: 0;
180
- }
181
-
182
- /* Interactive */
183
- :where(details > summary:first-of-type) {
184
- display: list-item;
185
- }
186
-
187
- /* Accessibility */
188
- :where([aria-busy="true" i]) {
189
- cursor: progress;
190
- }
191
-
192
- :where([aria-controls]) {
193
- cursor: pointer;
194
- }
195
-
196
- :where([aria-disabled="true" i], [disabled]) {
197
- cursor: not-allowed;
198
- }
199
-
200
- :where([aria-hidden="false" i][hidden]) {
201
- display: initial;
202
- }
203
-
204
- :where([aria-hidden="false" i][hidden]:not(:focus)) {
205
- clip: rect(0, 0, 0, 0);
206
- position: absolute;
207
- }
208
-
209
- /* Reduced motion — !important is required here to override any animation/transition regardless of specificity */
210
- @media (prefers-reduced-motion: reduce) {
211
- *,
212
- ::before,
213
- ::after {
214
- animation-delay: -1ms !important;
215
- animation-duration: 1ms !important;
216
- animation-iteration-count: 1 !important;
217
- background-attachment: initial !important;
218
- scroll-behavior: auto !important;
219
- transition-delay: 0s !important;
220
- transition-duration: 0s !important;
221
- }
222
- }
223
-
224
- /* Design tokens are provided by @grantcodes/style-dictionary and applied to :root */
225
- :root {
226
- color-scheme: light dark;
227
- --grantcodes-ui-theme: "none";
228
- background-color: var(--g-theme-color-background-default);
229
- color: var(--g-theme-color-content-default);
230
- fill: currentColor;
231
- }
232
-
233
- /* Allow transitioning auto */
234
- :root {
235
- @supports (interpolate-size: allow-keywords) {
236
- interpolate-size: allow-keywords;
237
- }
238
- }
239
-
240
- ::selection {
241
- background-color: var(--g-theme-color-background-brand);
242
- }
243
-
244
- /* Default backdrop styles */
245
- ::backdrop {
246
- background-color: rgba(0, 0, 0, 0.4);
247
- backdrop-filter: blur(6px);
248
- }
3
+ @import "./reset.css" layer(reset);
4
+ @import "./typography.css" layer(base);
5
+ @import "./elements.css" layer(base);
6
+ @import "./util/index.css" layer(utilities);
7
+ @import "./helpers.css" layer(utilities);
@@ -1,4 +1,4 @@
1
- a {
1
+ :where(a) {
2
2
  color: var(--g-theme-link-color-content-default);
3
3
  text-decoration: underline;
4
4
  transition:
@@ -6,25 +6,25 @@ a {
6
6
  opacity 0.2s;
7
7
  }
8
8
 
9
- a:visited {
9
+ :where(a:visited) {
10
10
  color: var(--g-theme-link-color-content-visited);
11
11
  opacity: 0.6;
12
12
  }
13
13
 
14
- a:hover,
15
- a:focus-visible {
14
+ :where(a:hover),
15
+ :where(a:focus-visible) {
16
16
  opacity: 1;
17
17
  color: var(--g-theme-link-color-content-hover);
18
18
  text-decoration: underline wavy;
19
19
  }
20
20
 
21
- a:active {
21
+ :where(a:active) {
22
22
  color: var(--g-theme-link-color-content-active);
23
23
  }
24
24
 
25
25
  /* External links - special styling for links opening in new windows */
26
- a[target="_blank"]::after,
27
- a[rel~="external"]::after {
26
+ :where(a[target="_blank"]::after),
27
+ :where(a[rel~="external"]::after) {
28
28
  content: "↗";
29
29
  display: inline-block;
30
30
  margin-inline-start: 0.25em;
@@ -38,7 +38,7 @@ a[rel~="external"]::after {
38
38
  /* biome-ignore lint/correctness/noUnknownPseudoClass: :external is a future CSS feature */
39
39
  @supports selector(:external) {
40
40
  /* biome-ignore lint/correctness/noUnknownPseudoClass: :external is a future CSS feature */
41
- a:external::after {
41
+ :where(a:external::after) {
42
42
  content: "↗";
43
43
  display: inline-block;
44
44
  margin-inline-start: 0.25em;