@grantcodes/ui 2.5.0 → 2.6.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.
@@ -40,6 +40,12 @@ describe("App Bar Component", () => {
40
40
  assert.ok(header, "Header element should exist");
41
41
  });
42
42
 
43
+ it("should not have sticky class by default", async () => {
44
+ element = await fixture("grantcodes-app-bar");
45
+ const appBar = element.shadowRoot.querySelector(".app-bar--sticky");
46
+ assert.ok(!appBar, "Should not have sticky class by default");
47
+ });
48
+
43
49
  it("should apply sticky class when sticky", async () => {
44
50
  element = await fixture("grantcodes-app-bar", {
45
51
  sticky: true,
@@ -108,7 +114,7 @@ describe("App Bar Component", () => {
108
114
  await element.updateComplete;
109
115
 
110
116
  const mobileNav = element.shadowRoot.querySelector(
111
- ".app-bar__nav--mobile-open",
117
+ ".app-bar__nav--open",
112
118
  );
113
119
  assert.ok(mobileNav, "Mobile nav should be rendered when open");
114
120
  });
@@ -0,0 +1,15 @@
1
+ import { LitElement, html } from "lit";
2
+ import focusRingStyles from "#styles/focus-ring.css" with { type: "css" };
3
+ import navLinkStyles from "./nav-link.css" with { type: "css" };
4
+
5
+ export class GrantCodesNavLink extends LitElement {
6
+ static styles = [focusRingStyles, navLinkStyles];
7
+
8
+ render() {
9
+ return html`
10
+ <span class="nav-link">
11
+ <slot></slot>
12
+ </span>
13
+ `;
14
+ }
15
+ }
@@ -0,0 +1,58 @@
1
+ *,
2
+ *::before,
3
+ *::after {
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ :host {
8
+ display: inline-flex;
9
+ }
10
+
11
+ .nav-link {
12
+ display: inline-flex;
13
+ align-items: center;
14
+ border-radius: var(--g-theme-border-radius-md);
15
+ background-color: transparent;
16
+ transition: background-color 0.15s ease;
17
+ }
18
+
19
+ .nav-link:hover {
20
+ background-color: color-mix(in srgb, var(--g-theme-color-content-default) 8%, transparent);
21
+ }
22
+
23
+ /*
24
+ * Override light-DOM :where(a) base styles for slotted links.
25
+ * ::slotted() has lower cascade priority than outer-tree normal rules,
26
+ * so !important is needed for properties that the base layer also sets
27
+ * (color, text-decoration, opacity). This is a legitimate encapsulation
28
+ * use-case — nav links must look like ghost buttons regardless of what
29
+ * global link styles exist in the light DOM.
30
+ */
31
+ ::slotted(a),
32
+ ::slotted(a:visited),
33
+ ::slotted(a:hover),
34
+ ::slotted(a:active),
35
+ ::slotted(a:focus-visible) {
36
+ display: inline-flex;
37
+ align-items: center;
38
+ color: var(--g-theme-color-content-default) !important;
39
+ text-decoration: none !important;
40
+ opacity: 1 !important;
41
+ padding-block: var(--g-theme-spacing-xs, 0.25rem);
42
+ padding-inline: var(--g-theme-spacing-sm);
43
+ font-family: var(--g-theme-typography-label-font-family);
44
+ font-weight: var(--g-theme-typography-label-font-weight);
45
+ white-space: nowrap;
46
+ }
47
+
48
+ ::slotted(a:focus-visible) {
49
+ outline: var(--g-theme-focus-ring-width-default) solid var(--g-theme-focus-ring-color-default);
50
+ outline-offset: var(--g-theme-focus-ring-offset-default);
51
+ border-radius: var(--g-theme-border-radius-md);
52
+ }
53
+
54
+ @media (prefers-reduced-motion: reduce) {
55
+ .nav-link {
56
+ transition: none;
57
+ }
58
+ }
@@ -0,0 +1,6 @@
1
+ import { GrantCodesNavLink } from "./nav-link.component.js";
2
+
3
+ export * from "./nav-link.component.js";
4
+ export default GrantCodesNavLink;
5
+
6
+ customElements.define("grantcodes-nav-link", GrantCodesNavLink);
@@ -0,0 +1,9 @@
1
+ import React from "react"
2
+ import { createComponent } from "@lit/react"
3
+ import { GrantCodesNavLink } from "./nav-link.js"
4
+
5
+ export const NavLink = createComponent({
6
+ tagName: "grantcodes-nav-link",
7
+ elementClass: GrantCodesNavLink,
8
+ react: React,
9
+ })
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-color-brand-purple-200);
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;
@@ -0,0 +1,246 @@
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. */
3
+
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
+ font: var(--g-theme-typography-body);
21
+ cursor: default;
22
+ line-height: 1.5;
23
+ overflow-wrap: break-word;
24
+ -moz-tab-size: 4;
25
+ tab-size: 4;
26
+ -webkit-tap-highlight-color: transparent;
27
+ -webkit-text-size-adjust: 100%;
28
+ }
29
+
30
+ :where(:root, body) {
31
+ padding: 0;
32
+ margin: 0;
33
+ }
34
+
35
+ code,
36
+ kbd,
37
+ samp,
38
+ pre {
39
+ font-family:
40
+ ui-monospace,
41
+ "Menlo",
42
+ "Consolas",
43
+ "Roboto Mono",
44
+ "Ubuntu Monospace",
45
+ "Noto Mono",
46
+ "Oxygen Mono",
47
+ "Liberation Mono",
48
+ monospace,
49
+ "Apple Color Emoji",
50
+ "Segoe UI Emoji",
51
+ "Segoe UI Symbol",
52
+ "Noto Color Emoji";
53
+ }
54
+
55
+
56
+ /* Grouping */
57
+ :where(dl, ol, ul) :where(dl, ol, ul) {
58
+ margin: 0;
59
+ }
60
+
61
+ :where(hr) {
62
+ color: inherit;
63
+ height: 0;
64
+ }
65
+
66
+ :where(nav) :where(ol, ul) {
67
+ list-style-type: none;
68
+ padding: 0;
69
+ }
70
+
71
+ /* Prevent VoiceOver from ignoring list semantics in Safari */
72
+ :where(nav li)::before {
73
+ content: "\200B";
74
+ float: left;
75
+ }
76
+
77
+ :where(pre) {
78
+ font-size: 1em;
79
+ overflow: auto;
80
+ }
81
+
82
+ /* Text-level semantics */
83
+ :where(abbr[title]) {
84
+ text-decoration: underline dotted;
85
+ }
86
+
87
+ :where(b, strong) {
88
+ font-weight: bolder;
89
+ }
90
+
91
+ :where(code, kbd, samp) {
92
+ font-size: 1em;
93
+ }
94
+
95
+ :where(small) {
96
+ font-size: 80%;
97
+ }
98
+
99
+ /* Embedded content */
100
+ :where(audio, canvas, iframe, img, svg, video) {
101
+ vertical-align: middle;
102
+ }
103
+
104
+ :where(iframe) {
105
+ border-style: none;
106
+ }
107
+
108
+ :where(svg:not([fill])) {
109
+ fill: currentColor;
110
+ }
111
+
112
+ /* Tabular data */
113
+ :where(table) {
114
+ border-collapse: collapse;
115
+ border-color: inherit;
116
+ text-indent: 0;
117
+ }
118
+
119
+ /* Forms — baseline normalisation */
120
+ :where(button, input, select) {
121
+ margin: 0;
122
+ }
123
+
124
+ :where(button, [type="button" i], [type="reset" i], [type="submit" i]) {
125
+ -webkit-appearance: button;
126
+ }
127
+
128
+ :where(fieldset) {
129
+ border: 1px solid #a0a0a0;
130
+ }
131
+
132
+ :where(progress) {
133
+ vertical-align: baseline;
134
+ }
135
+
136
+ :where(textarea) {
137
+ margin: 0;
138
+ resize: vertical;
139
+ }
140
+
141
+ :where([type="search" i]) {
142
+ -webkit-appearance: textfield;
143
+ outline-offset: -2px;
144
+ }
145
+
146
+ ::-webkit-inner-spin-button,
147
+ ::-webkit-outer-spin-button {
148
+ height: auto;
149
+ }
150
+
151
+ ::-webkit-input-placeholder {
152
+ color: inherit;
153
+ opacity: 0.54;
154
+ }
155
+
156
+ ::-webkit-search-decoration {
157
+ -webkit-appearance: none;
158
+ }
159
+
160
+ ::-webkit-file-upload-button {
161
+ -webkit-appearance: button;
162
+ font: inherit;
163
+ }
164
+
165
+ /* Forms — typography and colour inheritance */
166
+ :where(button, input, select, textarea) {
167
+ background-color: transparent;
168
+ border: 1px solid WindowFrame;
169
+ color: inherit;
170
+ font: inherit;
171
+ letter-spacing: inherit;
172
+ padding: 0.25em 0.375em;
173
+ }
174
+
175
+ :where([type="color" i], [type="range" i]) {
176
+ border-width: 0;
177
+ padding: 0;
178
+ }
179
+
180
+ /* Interactive */
181
+ :where(details > summary:first-of-type) {
182
+ display: list-item;
183
+ }
184
+
185
+ /* Accessibility */
186
+ :where([aria-busy="true" i]) {
187
+ cursor: progress;
188
+ }
189
+
190
+ :where([aria-controls]) {
191
+ cursor: pointer;
192
+ }
193
+
194
+ :where([aria-disabled="true" i], [disabled]) {
195
+ cursor: not-allowed;
196
+ }
197
+
198
+ :where([aria-hidden="false" i][hidden]) {
199
+ display: initial;
200
+ }
201
+
202
+ :where([aria-hidden="false" i][hidden]:not(:focus)) {
203
+ clip: rect(0, 0, 0, 0);
204
+ position: absolute;
205
+ }
206
+
207
+ /* Reduced motion — !important is required here to override any animation/transition regardless of specificity */
208
+ @media (prefers-reduced-motion: reduce) {
209
+ *,
210
+ ::before,
211
+ ::after {
212
+ animation-delay: -1ms !important;
213
+ animation-duration: 1ms !important;
214
+ animation-iteration-count: 1 !important;
215
+ background-attachment: initial !important;
216
+ scroll-behavior: auto !important;
217
+ transition-delay: 0s !important;
218
+ transition-duration: 0s !important;
219
+ }
220
+ }
221
+
222
+ /* Design tokens are provided by @grantcodes/style-dictionary and applied to :root */
223
+ :root {
224
+ color-scheme: light dark;
225
+ --grantcodes-ui-theme: "none";
226
+ background-color: var(--g-theme-color-background-default);
227
+ color: var(--g-theme-color-content-default);
228
+ fill: currentColor;
229
+ }
230
+
231
+ /* Allow transitioning auto */
232
+ :root {
233
+ @supports (interpolate-size: allow-keywords) {
234
+ interpolate-size: allow-keywords;
235
+ }
236
+ }
237
+
238
+ ::selection {
239
+ background-color: var(--g-theme-color-background-brand);
240
+ }
241
+
242
+ /* Default backdrop styles */
243
+ ::backdrop {
244
+ background-color: rgba(0, 0, 0, 0.4);
245
+ backdrop-filter: blur(6px);
246
+ }
@@ -1,4 +1,5 @@
1
1
  @import "@grantcodes/style-dictionary/grantcodes/css";
2
2
  @import "@grantcodes/style-dictionary/grantcodes/css/theme";
3
3
  @import "@grantcodes/style-dictionary/grantcodes/css/dark";
4
+ @import "@grantcodes/style-dictionary/grantcodes/css/light";
4
5
  @import "@grantcodes/style-dictionary/assets/fonts/greycliff";
@@ -1,4 +1,5 @@
1
1
  @import "@grantcodes/style-dictionary/grantina/css";
2
2
  @import "@grantcodes/style-dictionary/grantina/css/theme";
3
3
  @import "@grantcodes/style-dictionary/grantina/css/dark";
4
+ @import "@grantcodes/style-dictionary/grantina/css/light";
4
5
  @import "@grantcodes/style-dictionary/assets/fonts/grantina";
@@ -1,6 +1,7 @@
1
1
  @import "@grantcodes/style-dictionary/todomap/css";
2
2
  @import "@grantcodes/style-dictionary/todomap/css/theme";
3
3
  @import "@grantcodes/style-dictionary/todomap/css/dark";
4
+ @import "@grantcodes/style-dictionary/todomap/css/light";
4
5
  @import "@grantcodes/style-dictionary/assets/fonts/quicksand";
5
6
 
6
7
  html.todomap {
@@ -1,3 +1,4 @@
1
1
  @import "@grantcodes/style-dictionary/wireframe/css";
2
2
  @import "@grantcodes/style-dictionary/wireframe/css/theme";
3
3
  @import "@grantcodes/style-dictionary/wireframe/css/dark";
4
+ @import "@grantcodes/style-dictionary/wireframe/css/light";
package/src/react.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { Accordion } from "./components/accordion/accordion.react.js"
2
2
  export { AppBar } from "./components/app-bar/app-bar.react.js"
3
+ export { NavLink } from "./components/app-bar/nav-link.react.js"
3
4
  export { Avatar } from "./components/avatar/avatar.react.js"
4
5
  export { Badge } from "./components/badge/badge.react.js"
5
6
  export { Breadcrumb, BreadcrumbItem } from "./components/breadcrumb/breadcrumb.react.js"