@astronox/core 0.2.2 → 0.2.3
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.
- package/package.json +1 -1
- package/src/admin/cache.ts +217 -0
- package/src/admin/index.ts +1 -0
- package/src/auth/firebase/client.ts +37 -7
- package/src/client/formSubmission.ts +177 -0
- package/src/client/index.ts +1 -0
- package/src/components/actions/ScrollToTop/ScrollToTop.css +11 -0
- package/src/components/content/CardBody/CardBody.css +6 -2
- package/src/components/content/CardFooter/CardFooter.astro +9 -0
- package/src/components/content/CardFooter/CardFooter.css +45 -3
- package/src/components/content/CardLink/CardLink.astro +12 -3
- package/src/components/content/CardLink/CardLink.css +32 -0
- package/src/components/content/MarkdownContent/MarkdownContent.css +14 -4
- package/src/components/content/MarkdownContent/MarkdownInline.astro +1 -1
- package/src/components/content/MarkdownContent/contracts.ts +1 -1
- package/src/components/content/MarkdownContent/markdownContentRenderer.ts +7 -4
- package/src/components/data-display/TextPair/TextPair.css +8 -2
- package/src/components/forms/PreparedFormBootstrap/PreparedFormBootstrap.astro +203 -0
- package/src/components/forms/PreparedFormBootstrap/contracts.ts +15 -0
- package/src/components/forms/Textarea/Textarea.astro +3 -0
- package/src/components/forms/Textarea/Textarea.css +8 -0
- package/src/components/index.ts +8 -0
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.astro +5 -1
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.css +12 -17
- package/src/components/navigation/Footer/Footer.astro +102 -0
- package/src/components/navigation/Footer/Footer.css +155 -0
- package/src/components/navigation/Footer/contracts.ts +17 -0
- package/src/composition/Grid/Grid.css +16 -0
- package/src/data/firestore/client/contracts.ts +23 -0
- package/src/data/firestore/client/firebase.ts +45 -1
- package/src/display/contracts.ts +4 -1
- package/src/layouts/BaseLayout.astro +8 -1
- package/src/patterns/Combobox/comboboxController.ts +69 -0
- package/src/patterns/Table/TableBody.css +3 -3
- package/src/patterns/Table/TableCell.css +5 -1
- package/src/styles/base.css +24 -7
- package/src/styles/style.css +1 -0
- package/src/styles/tokens.css +5 -0
|
@@ -202,6 +202,20 @@
|
|
|
202
202
|
gap: var(--nox-space-1);
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
.card-link[data-density='relaxed'] .card-link__layout {
|
|
206
|
+
--card-link-size: calc(
|
|
207
|
+
var(--nox-navigation-block-size) + var(--nox-space-5)
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.card-link[data-density='relaxed'] .card-link__content {
|
|
212
|
+
padding: var(--nox-space-5) var(--nox-space-6);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.card-link[data-density='relaxed'] .card-link__text {
|
|
216
|
+
gap: var(--nox-space-3);
|
|
217
|
+
}
|
|
218
|
+
|
|
205
219
|
.card-link__compact-text {
|
|
206
220
|
display: none;
|
|
207
221
|
min-width: 0;
|
|
@@ -233,6 +247,14 @@
|
|
|
233
247
|
align-items: center;
|
|
234
248
|
}
|
|
235
249
|
|
|
250
|
+
.card-link[data-text-treatment='heading'] .card-link__text {
|
|
251
|
+
--text-pair-primary-font-size: var(--nox-font-size-lg);
|
|
252
|
+
--text-pair-primary-letter-spacing: var(--nox-letter-spacing-wide);
|
|
253
|
+
--text-pair-primary-text-transform: uppercase;
|
|
254
|
+
--text-pair-secondary-font-size: var(--nox-font-size-md);
|
|
255
|
+
line-height: var(--nox-line-height-tight);
|
|
256
|
+
}
|
|
257
|
+
|
|
236
258
|
.card-link[data-interactive='true']:active {
|
|
237
259
|
background: var(--card-link-active-background);
|
|
238
260
|
}
|
|
@@ -287,6 +309,16 @@
|
|
|
287
309
|
}
|
|
288
310
|
|
|
289
311
|
@media (width < 48rem) {
|
|
312
|
+
.card-link[data-density='relaxed'] .card-link__layout {
|
|
313
|
+
--card-link-size: calc(
|
|
314
|
+
var(--nox-navigation-block-size) + var(--nox-space-4)
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.card-link[data-density='relaxed'] .card-link__content {
|
|
319
|
+
padding: var(--nox-space-4) var(--nox-space-5);
|
|
320
|
+
}
|
|
321
|
+
|
|
290
322
|
.card-link[data-compact-content] .card-link__layout {
|
|
291
323
|
--card-link-size: var(--nox-navigation-compact-block-size);
|
|
292
324
|
--card-link-media-size: 2.125rem;
|
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
|
|
172
172
|
.markdown-content__resource-title {
|
|
173
173
|
font-size: var(--nox-font-size-md);
|
|
174
|
-
font-weight: var(--nox-font-weight-
|
|
174
|
+
font-weight: var(--nox-font-weight-regular);
|
|
175
175
|
line-height: var(--nox-line-height-tight);
|
|
176
176
|
}
|
|
177
177
|
|
|
@@ -298,13 +298,23 @@
|
|
|
298
298
|
}
|
|
299
299
|
|
|
300
300
|
.markdown-content__link {
|
|
301
|
-
color: var(--nox-
|
|
301
|
+
color: var(--nox-content-link-color);
|
|
302
302
|
overflow-wrap: anywhere;
|
|
303
303
|
text-decoration: none;
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
-
.markdown-content__link[data-markdown-link-treatment='
|
|
307
|
-
font-weight: var(--nox-font-weight
|
|
306
|
+
.markdown-content__link[data-markdown-link-treatment='primary'] {
|
|
307
|
+
font-weight: var(--nox-content-link-emphasis-font-weight);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.markdown-content .markdown-content__strong {
|
|
311
|
+
font-weight: var(--nox-font-weight-medium);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
@media (hover: hover) and (pointer: fine) {
|
|
315
|
+
.markdown-content__link:hover {
|
|
316
|
+
text-decoration-line: underline;
|
|
317
|
+
}
|
|
308
318
|
}
|
|
309
319
|
|
|
310
320
|
.markdown-content [data-tone] {
|
|
@@ -19,7 +19,7 @@ const { node }: Props = Astro.props;
|
|
|
19
19
|
) : node.type === 'symbol' ? (
|
|
20
20
|
<MarkdownSymbol node={node} />
|
|
21
21
|
) : node.type === 'strong' ? (
|
|
22
|
-
<strong>
|
|
22
|
+
<strong class="markdown-content__strong">
|
|
23
23
|
{node.children.map((child) => <Astro.self node={child} />)}
|
|
24
24
|
</strong>
|
|
25
25
|
) : node.type === 'emphasis' ? (
|
|
@@ -259,10 +259,13 @@ function renderInline(node: MarkdownInline): Node {
|
|
|
259
259
|
return text;
|
|
260
260
|
}
|
|
261
261
|
if (node.type === 'symbol') return renderSymbol(node);
|
|
262
|
-
if (node.type === 'strong'
|
|
263
|
-
const element =
|
|
264
|
-
|
|
265
|
-
|
|
262
|
+
if (node.type === 'strong') {
|
|
263
|
+
const element = createElement('strong', 'markdown-content__strong');
|
|
264
|
+
appendInline(element, node.children);
|
|
265
|
+
return element;
|
|
266
|
+
}
|
|
267
|
+
if (node.type === 'emphasis') {
|
|
268
|
+
const element = document.createElement('em');
|
|
266
269
|
appendInline(element, node.children);
|
|
267
270
|
return element;
|
|
268
271
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
.text-pair {
|
|
2
2
|
--text-pair-primary-color: var(--nox-text);
|
|
3
|
+
--text-pair-primary-font-size: inherit;
|
|
4
|
+
--text-pair-primary-letter-spacing: inherit;
|
|
5
|
+
--text-pair-primary-text-transform: none;
|
|
3
6
|
--text-pair-secondary-color: var(--nox-text-muted);
|
|
7
|
+
--text-pair-secondary-font-size: var(--nox-font-size-sm);
|
|
4
8
|
|
|
5
9
|
display: grid;
|
|
6
10
|
min-width: 0;
|
|
@@ -27,9 +31,11 @@
|
|
|
27
31
|
.text-pair__primary {
|
|
28
32
|
min-width: 0;
|
|
29
33
|
color: var(--text-pair-primary-color);
|
|
30
|
-
font-size:
|
|
34
|
+
font-size: var(--text-pair-primary-font-size);
|
|
35
|
+
letter-spacing: var(--text-pair-primary-letter-spacing);
|
|
31
36
|
line-height: inherit;
|
|
32
37
|
overflow-wrap: anywhere;
|
|
38
|
+
text-transform: var(--text-pair-primary-text-transform);
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
.text-pair__primary[data-text-color='muted'] {
|
|
@@ -58,7 +64,7 @@
|
|
|
58
64
|
.text-pair__secondary {
|
|
59
65
|
min-width: 0;
|
|
60
66
|
color: var(--text-pair-secondary-color);
|
|
61
|
-
font-size: var(--
|
|
67
|
+
font-size: var(--text-pair-secondary-font-size);
|
|
62
68
|
line-height: 1.35;
|
|
63
69
|
overflow-wrap: anywhere;
|
|
64
70
|
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
---
|
|
2
|
+
type Props = {
|
|
3
|
+
formId: string;
|
|
4
|
+
stateKey: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const { formId, stateKey }: Props = Astro.props;
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<script is:inline define:vars={{ formId, stateKey }}>
|
|
11
|
+
(() => {
|
|
12
|
+
const isRecord = (value) =>
|
|
13
|
+
typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
14
|
+
const readState = () => {
|
|
15
|
+
const state = window[stateKey];
|
|
16
|
+
return isRecord(state) ? state : null;
|
|
17
|
+
};
|
|
18
|
+
const createTag = (value, label) => {
|
|
19
|
+
const tag = document.createElement('span');
|
|
20
|
+
tag.className = 'tag';
|
|
21
|
+
tag.dataset.widget = 'tag';
|
|
22
|
+
tag.dataset.size = 'sm';
|
|
23
|
+
tag.dataset.comboboxTag = value;
|
|
24
|
+
const tagLabel = document.createElement('span');
|
|
25
|
+
tagLabel.className = 'tag__label';
|
|
26
|
+
tagLabel.dataset.tagLabel = '';
|
|
27
|
+
tagLabel.textContent = label;
|
|
28
|
+
tag.append(tagLabel);
|
|
29
|
+
return tag;
|
|
30
|
+
};
|
|
31
|
+
const getNamedFields = (form, name) => {
|
|
32
|
+
const item = form.elements.namedItem(name);
|
|
33
|
+
if (item instanceof RadioNodeList) return [...item];
|
|
34
|
+
return item ? [item] : [];
|
|
35
|
+
};
|
|
36
|
+
const getComboboxRoot = (form, key) =>
|
|
37
|
+
[...form.querySelectorAll('[data-prepared-form-combobox]')]
|
|
38
|
+
.find((candidate) => candidate.dataset.preparedFormCombobox === key);
|
|
39
|
+
const isComboboxReady = (root) =>
|
|
40
|
+
root instanceof HTMLElement &&
|
|
41
|
+
root.querySelector('[data-combobox-options]') instanceof HTMLElement &&
|
|
42
|
+
root.querySelector('[data-combobox-empty]') instanceof HTMLElement &&
|
|
43
|
+
root.querySelector('[data-combobox-values]') instanceof HTMLElement &&
|
|
44
|
+
root.querySelector('[data-combobox-placeholder]') instanceof HTMLElement;
|
|
45
|
+
const isFormReady = (form, state) => {
|
|
46
|
+
if (isRecord(state.values)) {
|
|
47
|
+
for (const name of Object.keys(state.values)) {
|
|
48
|
+
if (getNamedFields(form, name).length === 0) return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (isRecord(state.choices)) {
|
|
52
|
+
for (const [name, value] of Object.entries(state.choices)) {
|
|
53
|
+
if (
|
|
54
|
+
typeof value !== 'string' ||
|
|
55
|
+
!getNamedFields(form, name).some(
|
|
56
|
+
(field) =>
|
|
57
|
+
field instanceof HTMLInputElement && field.value === value,
|
|
58
|
+
)
|
|
59
|
+
) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (isRecord(state.comboboxes)) {
|
|
65
|
+
for (const key of Object.keys(state.comboboxes)) {
|
|
66
|
+
if (!isComboboxReady(getComboboxRoot(form, key))) return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
};
|
|
71
|
+
const applyCombobox = (form, key, state) => {
|
|
72
|
+
if (!isRecord(state) || !Array.isArray(state.options)) return;
|
|
73
|
+
const root = getComboboxRoot(form, key);
|
|
74
|
+
if (!(root instanceof HTMLElement)) return;
|
|
75
|
+
const optionsRoot = root.querySelector('[data-combobox-options]');
|
|
76
|
+
const empty = root.querySelector('[data-combobox-empty]');
|
|
77
|
+
const valuesRoot = root.querySelector('[data-combobox-values]');
|
|
78
|
+
const placeholder = root.querySelector('[data-combobox-placeholder]');
|
|
79
|
+
if (
|
|
80
|
+
!(optionsRoot instanceof HTMLElement) ||
|
|
81
|
+
!(empty instanceof HTMLElement) ||
|
|
82
|
+
!(valuesRoot instanceof HTMLElement) ||
|
|
83
|
+
!(placeholder instanceof HTMLElement)
|
|
84
|
+
) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
optionsRoot
|
|
89
|
+
.querySelectorAll('[data-combobox-option]')
|
|
90
|
+
.forEach((node) => node.remove());
|
|
91
|
+
root
|
|
92
|
+
.querySelectorAll('template[data-combobox-tag-template]')
|
|
93
|
+
.forEach((node) => node.remove());
|
|
94
|
+
const options = state.options.filter(
|
|
95
|
+
(option) =>
|
|
96
|
+
isRecord(option) &&
|
|
97
|
+
typeof option.label === 'string' &&
|
|
98
|
+
typeof option.value === 'string',
|
|
99
|
+
);
|
|
100
|
+
const selected = new Set(
|
|
101
|
+
Array.isArray(state.values)
|
|
102
|
+
? state.values.filter((value) => typeof value === 'string')
|
|
103
|
+
: [],
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
options.forEach((option, index) => {
|
|
107
|
+
const button = document.createElement('button');
|
|
108
|
+
button.className = 'combobox__option';
|
|
109
|
+
button.id = `${root.id || key}-option-${index}`;
|
|
110
|
+
button.type = 'button';
|
|
111
|
+
button.role = 'option';
|
|
112
|
+
button.tabIndex = -1;
|
|
113
|
+
button.setAttribute('aria-selected', String(selected.has(option.value)));
|
|
114
|
+
button.dataset.comboboxOption = option.value;
|
|
115
|
+
button.dataset.comboboxSearchText = option.label;
|
|
116
|
+
button.dataset.interactionAccent = 'start';
|
|
117
|
+
const content = document.createElement('span');
|
|
118
|
+
content.className = 'combobox__option-content';
|
|
119
|
+
const label = document.createElement('span');
|
|
120
|
+
label.className = 'combobox__option-label';
|
|
121
|
+
label.textContent = option.label;
|
|
122
|
+
content.append(label);
|
|
123
|
+
button.append(content);
|
|
124
|
+
optionsRoot.insertBefore(button, empty);
|
|
125
|
+
|
|
126
|
+
const template = document.createElement('template');
|
|
127
|
+
template.dataset.comboboxTagTemplate = option.value;
|
|
128
|
+
template.content.append(createTag(option.value, option.label));
|
|
129
|
+
root.append(template);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
valuesRoot
|
|
133
|
+
.querySelectorAll('[data-combobox-tag]')
|
|
134
|
+
.forEach((node) => node.remove());
|
|
135
|
+
for (const value of selected) {
|
|
136
|
+
const option = options.find((candidate) => candidate.value === value);
|
|
137
|
+
if (option) {
|
|
138
|
+
valuesRoot.insertBefore(createTag(value, option.label), placeholder);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
placeholder.hidden = selected.size > 0;
|
|
142
|
+
empty.hidden = options.length > 0;
|
|
143
|
+
};
|
|
144
|
+
const apply = () => {
|
|
145
|
+
const form = document.getElementById(formId);
|
|
146
|
+
const state = readState();
|
|
147
|
+
if (
|
|
148
|
+
!(form instanceof HTMLFormElement) ||
|
|
149
|
+
!state ||
|
|
150
|
+
!isFormReady(form, state)
|
|
151
|
+
) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (isRecord(state.values)) {
|
|
156
|
+
for (const [name, value] of Object.entries(state.values)) {
|
|
157
|
+
if (typeof value !== 'string') continue;
|
|
158
|
+
for (const field of getNamedFields(form, name)) {
|
|
159
|
+
if (
|
|
160
|
+
field instanceof HTMLInputElement ||
|
|
161
|
+
field instanceof HTMLTextAreaElement ||
|
|
162
|
+
field instanceof HTMLSelectElement
|
|
163
|
+
) {
|
|
164
|
+
field.value = value;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (isRecord(state.choices)) {
|
|
170
|
+
for (const [name, value] of Object.entries(state.choices)) {
|
|
171
|
+
if (typeof value !== 'string') continue;
|
|
172
|
+
for (const field of getNamedFields(form, name)) {
|
|
173
|
+
if (field instanceof HTMLInputElement) {
|
|
174
|
+
field.checked = field.value === value;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (isRecord(state.comboboxes)) {
|
|
180
|
+
for (const [key, combobox] of Object.entries(state.comboboxes)) {
|
|
181
|
+
applyCombobox(form, key, combobox);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
form.dataset.preparedFormBootstrap = 'true';
|
|
185
|
+
return true;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
if (apply()) return;
|
|
189
|
+
const observer = new MutationObserver(() => {
|
|
190
|
+
if (!apply()) return;
|
|
191
|
+
observer.disconnect();
|
|
192
|
+
});
|
|
193
|
+
observer.observe(document.documentElement, {
|
|
194
|
+
childList: true,
|
|
195
|
+
subtree: true,
|
|
196
|
+
});
|
|
197
|
+
document.addEventListener(
|
|
198
|
+
'DOMContentLoaded',
|
|
199
|
+
() => observer.disconnect(),
|
|
200
|
+
{ once: true },
|
|
201
|
+
);
|
|
202
|
+
})();
|
|
203
|
+
</script>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type PreparedFormOption = {
|
|
2
|
+
label: string;
|
|
3
|
+
value: string;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export type PreparedFormCombobox = {
|
|
7
|
+
options: readonly PreparedFormOption[];
|
|
8
|
+
values: readonly string[];
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type PreparedFormState = {
|
|
12
|
+
choices?: Readonly<Record<string, string>>;
|
|
13
|
+
comboboxes?: Readonly<Record<string, PreparedFormCombobox>>;
|
|
14
|
+
values?: Readonly<Record<string, string>>;
|
|
15
|
+
};
|
|
@@ -4,6 +4,7 @@ import type { Tone } from '../../../theme/contracts';
|
|
|
4
4
|
|
|
5
5
|
type Props = HTMLAttributes<'textarea'> & {
|
|
6
6
|
invalid?: boolean;
|
|
7
|
+
size?: 'default' | 'compact';
|
|
7
8
|
tone?: Tone;
|
|
8
9
|
value?: string;
|
|
9
10
|
};
|
|
@@ -13,6 +14,7 @@ const {
|
|
|
13
14
|
class: className,
|
|
14
15
|
'data-tone': dataTone,
|
|
15
16
|
invalid = false,
|
|
17
|
+
size = 'default',
|
|
16
18
|
tone,
|
|
17
19
|
value = '',
|
|
18
20
|
...attrs
|
|
@@ -27,6 +29,7 @@ const resolvedTone = tone ?? dataTone;
|
|
|
27
29
|
class:list={['textarea', className]}
|
|
28
30
|
data-widget="textarea"
|
|
29
31
|
data-invalid={resolvedInvalid ? true : undefined}
|
|
32
|
+
data-size={size}
|
|
30
33
|
data-tone={resolvedTone}
|
|
31
34
|
aria-invalid={resolvedInvalid ? 'true' : ariaInvalid}
|
|
32
35
|
>{value}</textarea>
|
|
@@ -25,6 +25,10 @@
|
|
|
25
25
|
opacity: 1;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
.textarea[data-size='compact'] {
|
|
29
|
+
min-height: calc(var(--nox-field-control-block-size) * 2);
|
|
30
|
+
}
|
|
31
|
+
|
|
28
32
|
.textarea:focus {
|
|
29
33
|
border-color: var(--nox-field-border-active);
|
|
30
34
|
box-shadow: var(--nox-field-focus-shadow);
|
|
@@ -51,4 +55,8 @@
|
|
|
51
55
|
min-height: 20rem;
|
|
52
56
|
font-size: var(--nox-font-size-md);
|
|
53
57
|
}
|
|
58
|
+
|
|
59
|
+
.textarea[data-size='compact'] {
|
|
60
|
+
min-height: calc(var(--nox-field-control-block-size) * 2);
|
|
61
|
+
}
|
|
54
62
|
}
|
package/src/components/index.ts
CHANGED
|
@@ -23,6 +23,12 @@ export { default as DateTimeInput } from './forms/DateTimeInput/DateTimeInput.as
|
|
|
23
23
|
export { default as FieldError } from './forms/FieldError/FieldError.astro';
|
|
24
24
|
export { default as FieldGroup } from './forms/FieldGroup/FieldGroup.astro';
|
|
25
25
|
export { default as FieldLabel } from './forms/FieldLabel/FieldLabel.astro';
|
|
26
|
+
export { default as PreparedFormBootstrap } from './forms/PreparedFormBootstrap/PreparedFormBootstrap.astro';
|
|
27
|
+
export type {
|
|
28
|
+
PreparedFormCombobox,
|
|
29
|
+
PreparedFormOption,
|
|
30
|
+
PreparedFormState,
|
|
31
|
+
} from './forms/PreparedFormBootstrap/contracts';
|
|
26
32
|
export { default as TextInput } from './forms/TextInput/TextInput.astro';
|
|
27
33
|
export { default as Textarea } from './forms/Textarea/Textarea.astro';
|
|
28
34
|
export { default as Spinner } from './feedback/Spinner/Spinner.astro';
|
|
@@ -41,6 +47,7 @@ export { default as NavigationLink } from './navigation/NavigationLink.astro';
|
|
|
41
47
|
export { default as NavigationLinks } from './navigation/NavigationLinks.astro';
|
|
42
48
|
export { default as NavigationMenuList } from './navigation/NavigationMenuList.astro';
|
|
43
49
|
export { default as SkipLink } from './navigation/SkipLink/SkipLink.astro';
|
|
50
|
+
export { default as Footer } from './navigation/Footer/Footer.astro';
|
|
44
51
|
export { default as SubNavigation } from './navigation/SubNavigation/SubNavigation.astro';
|
|
45
52
|
export { default as SeoHead } from './seo/SeoHead.astro';
|
|
46
53
|
export * from './content/CodeBlock/contracts';
|
|
@@ -55,6 +62,7 @@ export * from './media/EmbedFrame/embedFrameController';
|
|
|
55
62
|
export * from './navigation/mobileNavigationController';
|
|
56
63
|
export * from './navigation/navigationState';
|
|
57
64
|
export * from './navigation/navigationTypes';
|
|
65
|
+
export * from './navigation/Footer/contracts';
|
|
58
66
|
export * from './widgetTypes';
|
|
59
67
|
export { default as ArrowDownIcon } from './icons/ArrowDownIcon.astro';
|
|
60
68
|
export { default as ArrowLeftIcon } from './icons/ArrowLeftIcon.astro';
|
|
@@ -3,6 +3,7 @@ import type { HTMLAttributes } from 'astro/types';
|
|
|
3
3
|
import type { BreadcrumbItem } from '../../../display/contracts';
|
|
4
4
|
import type { Tone } from '../../../theme/contracts';
|
|
5
5
|
import { defaultCoreMessages } from '../../../i18n/coreMessages';
|
|
6
|
+
import ArrowRightIcon from '../../icons/ArrowRightIcon.astro';
|
|
6
7
|
|
|
7
8
|
type Props = HTMLAttributes<'nav'> & {
|
|
8
9
|
items: readonly BreadcrumbItem[];
|
|
@@ -18,7 +19,7 @@ const {
|
|
|
18
19
|
tone,
|
|
19
20
|
...attrs
|
|
20
21
|
} = Astro.props;
|
|
21
|
-
const resolvedTone = tone ?? dataTone;
|
|
22
|
+
const resolvedTone = tone ?? dataTone ?? 'info';
|
|
22
23
|
---
|
|
23
24
|
|
|
24
25
|
{
|
|
@@ -38,6 +39,9 @@ const resolvedTone = tone ?? dataTone;
|
|
|
38
39
|
|
|
39
40
|
return (
|
|
40
41
|
<li class="breadcrumbs__item">
|
|
42
|
+
{index > 0 ? (
|
|
43
|
+
<ArrowRightIcon class="breadcrumbs__separator" />
|
|
44
|
+
) : null}
|
|
41
45
|
{isNavigable ? (
|
|
42
46
|
<a href={href} aria-current={isCurrent ? 'page' : undefined}>
|
|
43
47
|
{item.label}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
.breadcrumbs {
|
|
2
2
|
min-width: 0;
|
|
3
3
|
overflow: hidden;
|
|
4
|
-
color: var(--nox-
|
|
4
|
+
color: var(--nox-tone-color);
|
|
5
5
|
font-size: var(--nox-font-size-sm);
|
|
6
6
|
font-weight: var(--nox-font-weight-medium);
|
|
7
7
|
line-height: 1.35;
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
.breadcrumbs__item {
|
|
26
26
|
display: inline-flex;
|
|
27
27
|
flex: 0 0 auto;
|
|
28
|
-
align-items:
|
|
28
|
+
align-items: center;
|
|
29
29
|
min-width: 0;
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -39,15 +39,13 @@
|
|
|
39
39
|
align-items: center;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
.
|
|
43
|
-
display:
|
|
44
|
-
|
|
42
|
+
.breadcrumbs__separator {
|
|
43
|
+
display: block;
|
|
44
|
+
width: 0.625rem;
|
|
45
|
+
height: 0.625rem;
|
|
46
|
+
flex: 0 0 auto;
|
|
45
47
|
margin-inline: var(--nox-space-2);
|
|
46
|
-
color:
|
|
47
|
-
content: '›';
|
|
48
|
-
font-size: var(--nox-font-size-md);
|
|
49
|
-
font-weight: var(--nox-font-weight-strong);
|
|
50
|
-
line-height: 1;
|
|
48
|
+
color: var(--nox-tone-color);
|
|
51
49
|
}
|
|
52
50
|
|
|
53
51
|
.breadcrumbs a {
|
|
@@ -56,7 +54,8 @@
|
|
|
56
54
|
align-items: center;
|
|
57
55
|
align-self: stretch;
|
|
58
56
|
border-radius: var(--nox-radius-sm);
|
|
59
|
-
color:
|
|
57
|
+
color: var(--nox-tone-color);
|
|
58
|
+
font-weight: var(--nox-content-link-emphasis-font-weight);
|
|
60
59
|
margin-inline: -0.1875rem;
|
|
61
60
|
padding-inline: 0.1875rem;
|
|
62
61
|
text-decoration: none;
|
|
@@ -71,19 +70,15 @@
|
|
|
71
70
|
|
|
72
71
|
@media (hover: hover) and (pointer: fine) {
|
|
73
72
|
.breadcrumbs a:hover {
|
|
74
|
-
color: var(--nox-tone-text);
|
|
75
73
|
text-decoration-line: underline;
|
|
76
|
-
text-decoration-thickness: 0.125rem;
|
|
77
74
|
}
|
|
78
75
|
}
|
|
79
76
|
|
|
80
77
|
.breadcrumbs a:focus-visible {
|
|
81
|
-
color: var(--nox-tone-
|
|
82
|
-
text-decoration-line: underline;
|
|
83
|
-
text-decoration-thickness: 0.125rem;
|
|
78
|
+
color: var(--nox-tone-color);
|
|
84
79
|
}
|
|
85
80
|
|
|
86
81
|
.breadcrumbs [aria-current='page'] {
|
|
87
|
-
color: var(--nox-tone-
|
|
82
|
+
color: var(--nox-tone-color);
|
|
88
83
|
font-weight: var(--nox-font-weight-strong);
|
|
89
84
|
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types';
|
|
3
|
+
import type { Tone } from '../../../theme/contracts';
|
|
4
|
+
import type { FooterGroup, FooterItem } from './contracts';
|
|
5
|
+
import ArrowRightIcon from '../../icons/ArrowRightIcon.astro';
|
|
6
|
+
|
|
7
|
+
type Props = HTMLAttributes<'footer'> & {
|
|
8
|
+
columnSeparator?: 'divider' | 'none';
|
|
9
|
+
columns?: 1 | 2;
|
|
10
|
+
groups: readonly FooterGroup[];
|
|
11
|
+
label: string;
|
|
12
|
+
tone?: Tone;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const {
|
|
16
|
+
class: className,
|
|
17
|
+
columnSeparator = 'none',
|
|
18
|
+
columns = 1,
|
|
19
|
+
'data-tone': dataTone,
|
|
20
|
+
groups,
|
|
21
|
+
label,
|
|
22
|
+
tone,
|
|
23
|
+
...attrs
|
|
24
|
+
}: Props = Astro.props;
|
|
25
|
+
const resolvedTone = tone ?? dataTone;
|
|
26
|
+
const visibleGroups = groups.filter(({ items }) => items.length > 0);
|
|
27
|
+
const resolveRel = (target: FooterItem['target'], rel?: FooterItem['rel']) =>
|
|
28
|
+
rel ?? (target === '_blank' ? 'noopener noreferrer' : undefined);
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
<footer
|
|
32
|
+
{...attrs}
|
|
33
|
+
class:list={['site-footer', className]}
|
|
34
|
+
data-tone={resolvedTone}
|
|
35
|
+
data-widget="site-footer"
|
|
36
|
+
>
|
|
37
|
+
<nav
|
|
38
|
+
class="site-footer__content"
|
|
39
|
+
aria-label={label}
|
|
40
|
+
data-footer-columns={columns}
|
|
41
|
+
data-footer-column-separator={columnSeparator}
|
|
42
|
+
>
|
|
43
|
+
{
|
|
44
|
+
visibleGroups.map(({ items }) => (
|
|
45
|
+
<span class="site-footer__group">
|
|
46
|
+
{items.map(
|
|
47
|
+
(
|
|
48
|
+
{
|
|
49
|
+
href,
|
|
50
|
+
label: itemLabel,
|
|
51
|
+
marker,
|
|
52
|
+
prefix,
|
|
53
|
+
rel,
|
|
54
|
+
separator = 'divider',
|
|
55
|
+
target,
|
|
56
|
+
tone: itemTone,
|
|
57
|
+
},
|
|
58
|
+
index,
|
|
59
|
+
) => (
|
|
60
|
+
<>
|
|
61
|
+
{index > 0 ? (
|
|
62
|
+
separator === 'divider' ? (
|
|
63
|
+
<span class="site-footer__separator" aria-hidden="true">
|
|
64
|
+
|
|
|
65
|
+
</span>
|
|
66
|
+
) : (
|
|
67
|
+
<span class="site-footer__spacer" aria-hidden="true" />
|
|
68
|
+
)
|
|
69
|
+
) : null}
|
|
70
|
+
<span class="site-footer__item">
|
|
71
|
+
{prefix ? (
|
|
72
|
+
<span class="site-footer__prefix">{prefix}</span>
|
|
73
|
+
) : null}
|
|
74
|
+
{href ? (
|
|
75
|
+
<a
|
|
76
|
+
href={href}
|
|
77
|
+
rel={resolveRel(target, rel)}
|
|
78
|
+
target={target}
|
|
79
|
+
data-tone={itemTone}
|
|
80
|
+
>
|
|
81
|
+
{marker === 'arrow' ? (
|
|
82
|
+
<ArrowRightIcon class="site-footer__marker" />
|
|
83
|
+
) : null}
|
|
84
|
+
<span>{itemLabel}</span>
|
|
85
|
+
</a>
|
|
86
|
+
) : (
|
|
87
|
+
<span class="site-footer__label" data-tone={itemTone}>
|
|
88
|
+
{marker === 'arrow' ? (
|
|
89
|
+
<ArrowRightIcon class="site-footer__marker" />
|
|
90
|
+
) : null}
|
|
91
|
+
<span>{itemLabel}</span>
|
|
92
|
+
</span>
|
|
93
|
+
)}
|
|
94
|
+
</span>
|
|
95
|
+
</>
|
|
96
|
+
),
|
|
97
|
+
)}
|
|
98
|
+
</span>
|
|
99
|
+
))
|
|
100
|
+
}
|
|
101
|
+
</nav>
|
|
102
|
+
</footer>
|