@astronox/core 0.2.1 → 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 +7 -1
- package/src/admin/cache.ts +217 -0
- package/src/admin/contracts.ts +28 -0
- package/src/admin/index.ts +3 -0
- package/src/admin/module.ts +28 -0
- package/src/auth/contracts.ts +53 -0
- package/src/auth/firebase/client.ts +83 -0
- package/src/auth/firebase/contracts.ts +8 -0
- package/src/auth/firebase/index.ts +1 -0
- package/src/auth/index.ts +2 -0
- package/src/auth/module.ts +99 -0
- package/src/client/formSubmission.ts +177 -0
- package/src/client/index.ts +4 -0
- package/src/client/preferences.ts +400 -0
- package/src/components/actions/Button/Button.astro +42 -6
- package/src/components/actions/Button/Button.css +19 -0
- package/src/components/actions/IdentityProviderAction/IdentityProviderAction.astro +35 -0
- package/src/components/actions/ScrollToTop/ScrollToTop.css +11 -0
- package/src/components/content/CardBody/CardBody.astro +3 -0
- package/src/components/content/CardBody/CardBody.css +16 -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 +552 -0
- package/src/components/data-display/TextPair/TextPair.css +8 -2
- package/src/components/feedback/PreferenceBootstrap/PreferenceBootstrap.astro +403 -0
- package/src/components/forms/Checkbox/Checkbox.astro +6 -0
- package/src/components/forms/ChoiceGroup/ChoiceGroup.astro +57 -0
- package/src/components/forms/ChoiceGroup/ChoiceGroup.css +108 -0
- package/src/components/forms/ChoiceGroup/contracts.ts +9 -0
- package/src/components/forms/DateTimeInput/DateTimeInput.astro +14 -0
- package/src/components/forms/PreparedFormBootstrap/PreparedFormBootstrap.astro +203 -0
- package/src/components/forms/PreparedFormBootstrap/contracts.ts +15 -0
- package/src/components/forms/TextInput/TextInput.astro +7 -1
- package/src/components/forms/Textarea/Textarea.astro +35 -0
- package/src/components/forms/Textarea/Textarea.css +62 -0
- package/src/components/icons/BackIcon.astro +26 -0
- package/src/components/icons/DeleteIcon.astro +22 -0
- package/src/components/icons/StatusIndicatorIcon.astro +16 -0
- package/src/components/index.ts +18 -0
- package/src/components/media/CanvasFrame/CanvasFrame.astro +1 -17
- package/src/components/media/EmbedFrame/EmbedFrame.astro +1 -19
- package/src/components/media/EmbedFrame/EmbedFrame.css +0 -2
- 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/components/navigation/SubNavigation/SubNavigation.css +0 -5
- package/src/composition/Grid/Grid.css +16 -0
- package/src/composition/SurfaceGroup/SurfaceGroup.css +21 -7
- package/src/composition/SurfaceGroup/stickySurfaceGroupController.ts +3 -0
- package/src/data/firestore/client/contracts.ts +78 -0
- package/src/data/firestore/client/firebase.ts +56 -0
- package/src/data/firestore/client/index.ts +3 -0
- package/src/data/firestore/client/repository.ts +111 -0
- package/src/display/contracts.ts +44 -1
- package/src/display/index.ts +1 -0
- package/src/display/tableProgressive.ts +62 -0
- package/src/layouts/BaseLayout.astro +8 -1
- package/src/patterns/Combobox/Combobox.astro +23 -0
- package/src/patterns/Combobox/comboboxController.ts +69 -0
- package/src/patterns/Fullscreen/FullscreenControl.astro +4 -14
- package/src/patterns/Fullscreen/FullscreenControl.css +0 -26
- package/src/patterns/Modal/Modal.astro +1 -1
- package/src/patterns/Modal/Modal.css +38 -1
- package/src/patterns/Table/Table.astro +122 -7
- package/src/patterns/Table/TableBody.astro +8 -0
- package/src/patterns/Table/TableBody.css +3 -3
- package/src/patterns/Table/TableCell.css +11 -1
- package/src/patterns/Table/TableCellValue.astro +31 -0
- package/src/patterns/Table/TableFooter.astro +3 -1
- package/src/patterns/Table/TableHeader.astro +1 -0
- package/src/patterns/Table/TablePagination.astro +63 -47
- package/src/patterns/Table/TableSkeleton.astro +18 -22
- package/src/patterns/Table/TableSkeleton.css +30 -5
- package/src/patterns/Table/tableClientController.ts +253 -0
- package/src/patterns/Table/tableFilterController.ts +23 -31
- package/src/patterns/Table/tableProgressiveController.ts +615 -227
- package/src/patterns/Table/tableSortController.ts +25 -7
- package/src/styles/base.css +24 -7
- package/src/styles/interaction.css +16 -0
- package/src/styles/style.css +3 -0
- package/src/styles/tokens.css +5 -0
|
@@ -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>
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
.site-footer {
|
|
2
|
+
color: var(--nox-text-muted);
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.site-footer__content {
|
|
6
|
+
--site-footer-item-gap: var(--nox-space-2);
|
|
7
|
+
--site-footer-marker-gap: 0.0625rem;
|
|
8
|
+
|
|
9
|
+
display: grid;
|
|
10
|
+
width: var(--nox-page-width);
|
|
11
|
+
margin-inline: auto;
|
|
12
|
+
justify-items: center;
|
|
13
|
+
gap: var(--nox-space-1);
|
|
14
|
+
padding-block-end: calc(
|
|
15
|
+
var(--nox-section-compact-block-size) + var(--nox-space-7)
|
|
16
|
+
);
|
|
17
|
+
font-size: var(--nox-font-size-sm);
|
|
18
|
+
line-height: 1.25;
|
|
19
|
+
text-align: center;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.site-footer__group {
|
|
23
|
+
display: inline-flex;
|
|
24
|
+
min-width: 0;
|
|
25
|
+
align-items: center;
|
|
26
|
+
justify-content: center;
|
|
27
|
+
flex-wrap: wrap;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.site-footer__content[data-footer-columns='2'] {
|
|
31
|
+
grid-template-columns: repeat(2, max-content);
|
|
32
|
+
justify-content: center;
|
|
33
|
+
column-gap: var(--nox-space-4);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.site-footer__content[data-footer-columns='2'][
|
|
37
|
+
data-footer-column-separator='divider'
|
|
38
|
+
] {
|
|
39
|
+
column-gap: 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.site-footer__content[data-footer-columns='2'][
|
|
43
|
+
data-footer-column-separator='divider'
|
|
44
|
+
]
|
|
45
|
+
> .site-footer__group:nth-child(even)::before {
|
|
46
|
+
display: inline-grid;
|
|
47
|
+
width: calc(
|
|
48
|
+
var(--site-footer-item-gap) + var(--site-footer-item-gap) + 0.5em
|
|
49
|
+
);
|
|
50
|
+
flex: 0 0 auto;
|
|
51
|
+
place-items: center;
|
|
52
|
+
color: var(--nox-border);
|
|
53
|
+
content: '|';
|
|
54
|
+
line-height: 1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.site-footer__content[data-footer-columns='2']
|
|
58
|
+
> .site-footer__group:last-child:nth-child(odd) {
|
|
59
|
+
grid-column: 1 / -1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.site-footer__item {
|
|
63
|
+
display: inline-flex;
|
|
64
|
+
min-width: 0;
|
|
65
|
+
align-items: center;
|
|
66
|
+
gap: 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.site-footer__separator {
|
|
70
|
+
display: inline-grid;
|
|
71
|
+
width: calc(
|
|
72
|
+
var(--site-footer-item-gap) + var(--site-footer-item-gap) + 0.5em
|
|
73
|
+
);
|
|
74
|
+
flex: 0 0 auto;
|
|
75
|
+
place-items: center;
|
|
76
|
+
color: var(--nox-border);
|
|
77
|
+
line-height: 1;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.site-footer__spacer {
|
|
81
|
+
width: var(--site-footer-item-gap);
|
|
82
|
+
flex: 0 0 auto;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.site-footer__prefix + :is(a, .site-footer__label) {
|
|
86
|
+
margin-inline-start: var(--nox-space-1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.site-footer a,
|
|
90
|
+
.site-footer__label {
|
|
91
|
+
display: inline-flex;
|
|
92
|
+
min-width: 0;
|
|
93
|
+
align-items: center;
|
|
94
|
+
gap: var(--site-footer-marker-gap);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.site-footer__marker {
|
|
98
|
+
display: block;
|
|
99
|
+
width: 0.625rem;
|
|
100
|
+
height: 0.625rem;
|
|
101
|
+
flex: 0 0 auto;
|
|
102
|
+
color: var(--nox-text-muted);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.site-footer a {
|
|
106
|
+
color: var(--nox-content-link-color);
|
|
107
|
+
font-weight: var(--nox-content-link-emphasis-font-weight);
|
|
108
|
+
text-decoration: none;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.site-footer a[data-tone],
|
|
112
|
+
.site-footer__label[data-tone] {
|
|
113
|
+
color: var(--nox-tone-color);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.site-footer a:focus-visible {
|
|
117
|
+
border-radius: var(--nox-radius-sm);
|
|
118
|
+
outline: var(--nox-focus-ring-width) solid var(--nox-tone-focus-ring);
|
|
119
|
+
outline-offset: var(--nox-focus-ring-offset);
|
|
120
|
+
color: var(--nox-content-link-color);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.site-footer a[data-tone]:focus-visible {
|
|
124
|
+
color: var(--nox-tone-color);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
@media (hover: hover) and (pointer: fine) {
|
|
128
|
+
.site-footer a:hover {
|
|
129
|
+
color: var(--nox-content-link-color);
|
|
130
|
+
text-decoration-line: underline;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.site-footer a[data-tone]:hover {
|
|
134
|
+
color: var(--nox-tone-color);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@media (width < 48rem) {
|
|
139
|
+
.site-footer__content[data-footer-columns='2'] {
|
|
140
|
+
grid-template-columns: minmax(0, 1fr);
|
|
141
|
+
column-gap: 0;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.site-footer__content[data-footer-columns='2']
|
|
145
|
+
> .site-footer__group:last-child:nth-child(odd) {
|
|
146
|
+
grid-column: auto;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.site-footer__content[data-footer-columns='2'][
|
|
150
|
+
data-footer-column-separator='divider'
|
|
151
|
+
]
|
|
152
|
+
> .site-footer__group:nth-child(even)::before {
|
|
153
|
+
content: none;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'astro/types';
|
|
2
|
+
import type { Tone } from '../../../theme/contracts';
|
|
3
|
+
|
|
4
|
+
export type FooterItem = {
|
|
5
|
+
href?: HTMLAttributes<'a'>['href'];
|
|
6
|
+
label: string;
|
|
7
|
+
marker?: 'arrow';
|
|
8
|
+
prefix?: string;
|
|
9
|
+
rel?: HTMLAttributes<'a'>['rel'];
|
|
10
|
+
separator?: 'divider' | 'none';
|
|
11
|
+
target?: HTMLAttributes<'a'>['target'];
|
|
12
|
+
tone?: Tone;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type FooterGroup = {
|
|
16
|
+
items: readonly FooterItem[];
|
|
17
|
+
};
|
|
@@ -115,6 +115,10 @@
|
|
|
115
115
|
grid-template-columns: 1fr;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
.grid[data-layout='fixed'][data-collapse-at='sm'][data-last-row='fill'] > * {
|
|
119
|
+
grid-column: auto;
|
|
120
|
+
}
|
|
121
|
+
|
|
118
122
|
.grid:is(
|
|
119
123
|
[data-layout='fixed'],
|
|
120
124
|
[data-layout='content-sidebar']
|
|
@@ -163,6 +167,10 @@
|
|
|
163
167
|
grid-template-columns: 1fr;
|
|
164
168
|
}
|
|
165
169
|
|
|
170
|
+
.grid[data-layout='fixed'][data-collapse-at='md'][data-last-row='fill'] > * {
|
|
171
|
+
grid-column: auto;
|
|
172
|
+
}
|
|
173
|
+
|
|
166
174
|
.grid:is(
|
|
167
175
|
[data-layout='fixed'],
|
|
168
176
|
[data-layout='content-sidebar']
|
|
@@ -211,6 +219,10 @@
|
|
|
211
219
|
grid-template-columns: 1fr;
|
|
212
220
|
}
|
|
213
221
|
|
|
222
|
+
.grid[data-layout='fixed'][data-collapse-at='lg'][data-last-row='fill'] > * {
|
|
223
|
+
grid-column: auto;
|
|
224
|
+
}
|
|
225
|
+
|
|
214
226
|
.grid:is(
|
|
215
227
|
[data-layout='fixed'],
|
|
216
228
|
[data-layout='content-sidebar']
|
|
@@ -259,6 +271,10 @@
|
|
|
259
271
|
grid-template-columns: 1fr;
|
|
260
272
|
}
|
|
261
273
|
|
|
274
|
+
.grid[data-layout='fixed'][data-collapse-at='xl'][data-last-row='fill'] > * {
|
|
275
|
+
grid-column: auto;
|
|
276
|
+
}
|
|
277
|
+
|
|
262
278
|
.grid:is(
|
|
263
279
|
[data-layout='fixed'],
|
|
264
280
|
[data-layout='content-sidebar']
|
|
@@ -34,34 +34,48 @@
|
|
|
34
34
|
content: '';
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
.surface-group:has(> [data-sticky-surface]):not(
|
|
38
|
+
[data-sticky-measured]
|
|
39
|
+
)::before,
|
|
37
40
|
.surface-group[data-sticky-active]::before {
|
|
38
41
|
visibility: visible;
|
|
39
42
|
}
|
|
40
43
|
|
|
41
|
-
.surface-group > [data-surface] {
|
|
44
|
+
.surface-group > [data-surface]:not([hidden]) {
|
|
42
45
|
flex: 0 0 auto;
|
|
43
46
|
min-width: 0;
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
.surface-group
|
|
47
|
-
> [data-surface]:not(
|
|
48
|
-
:has(~ [data-surface]),
|
|
50
|
+
> [data-surface]:not([hidden]):not(
|
|
51
|
+
:has(~ [data-surface]:not([hidden])),
|
|
49
52
|
[data-surface-sizing='content']
|
|
50
53
|
),
|
|
51
54
|
.surface-group
|
|
52
|
-
> [data-surface]:
|
|
53
|
-
|
|
55
|
+
> [data-surface]:not([hidden]):not(
|
|
56
|
+
[data-surface-sizing='content']
|
|
57
|
+
):has(
|
|
58
|
+
~ [data-surface]:not([hidden])[data-surface-sizing='content']
|
|
59
|
+
):not(
|
|
60
|
+
:has(
|
|
61
|
+
~ [data-surface]:not([hidden]):not(
|
|
62
|
+
[data-surface-sizing='content']
|
|
63
|
+
)
|
|
64
|
+
)
|
|
54
65
|
) {
|
|
55
66
|
flex: 1 1 auto;
|
|
56
67
|
min-height: 0;
|
|
57
68
|
}
|
|
58
69
|
|
|
59
|
-
.surface-group
|
|
70
|
+
.surface-group
|
|
71
|
+
> [data-surface]:not([hidden]):has(~ [data-surface]:not([hidden])) {
|
|
60
72
|
border-end-start-radius: 0;
|
|
61
73
|
border-end-end-radius: 0;
|
|
62
74
|
}
|
|
63
75
|
|
|
64
|
-
.surface-group
|
|
76
|
+
.surface-group
|
|
77
|
+
> [data-surface]:not([hidden])
|
|
78
|
+
~ [data-surface]:not([hidden]) {
|
|
65
79
|
border-block-start-width: 0;
|
|
66
80
|
border-start-start-radius: 0;
|
|
67
81
|
border-start-end-radius: 0;
|
|
@@ -42,6 +42,7 @@ export const initStickySurfaceGroup = (root: HTMLElement): (() => void) => {
|
|
|
42
42
|
const observe = () => {
|
|
43
43
|
resizeFrame = 0;
|
|
44
44
|
observer?.disconnect();
|
|
45
|
+
root.removeAttribute('data-sticky-measured');
|
|
45
46
|
|
|
46
47
|
const stickyOffset =
|
|
47
48
|
Number.parseFloat(window.getComputedStyle(stickySurface).top) || 0;
|
|
@@ -54,6 +55,7 @@ export const initStickySurfaceGroup = (root: HTMLElement): (() => void) => {
|
|
|
54
55
|
'data-sticky-active',
|
|
55
56
|
entry.boundingClientRect.top <= activationBoundary + 0.5,
|
|
56
57
|
);
|
|
58
|
+
root.setAttribute('data-sticky-measured', 'true');
|
|
57
59
|
},
|
|
58
60
|
{
|
|
59
61
|
root: scrollParent instanceof Window ? null : scrollParent,
|
|
@@ -85,5 +87,6 @@ export const initStickySurfaceGroup = (root: HTMLElement): (() => void) => {
|
|
|
85
87
|
|
|
86
88
|
root.removeAttribute('data-sticky-active');
|
|
87
89
|
root.removeAttribute('data-sticky-initialized');
|
|
90
|
+
root.removeAttribute('data-sticky-measured');
|
|
88
91
|
};
|
|
89
92
|
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { Firestore } from 'firebase/firestore';
|
|
2
|
+
|
|
3
|
+
export type ClientFirestorePage<TItem> = {
|
|
4
|
+
items: readonly TItem[];
|
|
5
|
+
nextCursor?: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type ClientFirestoreOrder = {
|
|
9
|
+
direction?: 'asc' | 'desc';
|
|
10
|
+
field: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type ClientFirestoreCursor<TDocument> = {
|
|
14
|
+
serialize(input: { document: TDocument; id: string }): string;
|
|
15
|
+
toQueryValues(cursor: string): readonly unknown[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type ClientFirestoreDocumentMapper<
|
|
19
|
+
TItem,
|
|
20
|
+
TCreateInput,
|
|
21
|
+
TUpdateInput,
|
|
22
|
+
> = {
|
|
23
|
+
fromDocument(id: string, value: unknown): TItem;
|
|
24
|
+
toCreate(input: TCreateInput): Record<string, unknown>;
|
|
25
|
+
toUpdate(input: TUpdateInput): Record<string, unknown>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type ClientFirestoreSchema<TDocument> = {
|
|
29
|
+
parse(input: unknown): TDocument;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type ClientFirestoreRepository<TItem, TCreateInput, TUpdateInput> = {
|
|
33
|
+
getIndex(input?: {
|
|
34
|
+
cursor?: string;
|
|
35
|
+
limit?: number;
|
|
36
|
+
}): Promise<ClientFirestorePage<TItem>>;
|
|
37
|
+
getDetail(input: { id: string }): Promise<TItem | null>;
|
|
38
|
+
create(input: TCreateInput): Promise<TItem>;
|
|
39
|
+
update(input: { id: string; value: TUpdateInput }): Promise<TItem>;
|
|
40
|
+
delete(input: { id: string }): Promise<void>;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type ClientFirestoreRepositoryConfig<
|
|
44
|
+
TDocument,
|
|
45
|
+
TItem,
|
|
46
|
+
TCreateInput,
|
|
47
|
+
TUpdateInput,
|
|
48
|
+
> = {
|
|
49
|
+
collection: string;
|
|
50
|
+
cursor?: ClientFirestoreCursor<TDocument>;
|
|
51
|
+
database: Firestore;
|
|
52
|
+
mapper: ClientFirestoreDocumentMapper<TItem, TCreateInput, TUpdateInput>;
|
|
53
|
+
orderBy?: readonly ClientFirestoreOrder[];
|
|
54
|
+
schema: ClientFirestoreSchema<TDocument>;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type ClientFirestoreBatchOperation =
|
|
58
|
+
| {
|
|
59
|
+
collection: string;
|
|
60
|
+
id?: string;
|
|
61
|
+
kind: 'create';
|
|
62
|
+
value: Record<string, unknown>;
|
|
63
|
+
}
|
|
64
|
+
| {
|
|
65
|
+
collection: string;
|
|
66
|
+
id: string;
|
|
67
|
+
kind: 'update';
|
|
68
|
+
value: Record<string, unknown>;
|
|
69
|
+
}
|
|
70
|
+
| {
|
|
71
|
+
collection: string;
|
|
72
|
+
id: string;
|
|
73
|
+
kind: 'delete';
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export type ClientFirestoreBatchResult = {
|
|
77
|
+
createdIds: readonly string[];
|
|
78
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { getApps, initializeApp } from 'firebase/app';
|
|
2
|
+
import {
|
|
3
|
+
collection,
|
|
4
|
+
doc,
|
|
5
|
+
getFirestore,
|
|
6
|
+
serverTimestamp,
|
|
7
|
+
writeBatch,
|
|
8
|
+
} from 'firebase/firestore';
|
|
9
|
+
|
|
10
|
+
import type { FirebaseClientConfig } from '../../../auth/firebase/contracts';
|
|
11
|
+
import type {
|
|
12
|
+
ClientFirestoreBatchOperation,
|
|
13
|
+
ClientFirestoreBatchResult,
|
|
14
|
+
} from './contracts';
|
|
15
|
+
|
|
16
|
+
export function createFirebaseClientFirestore(config: FirebaseClientConfig) {
|
|
17
|
+
const app =
|
|
18
|
+
getApps().find((candidate) => candidate.name === config.projectId) ??
|
|
19
|
+
initializeApp(config, config.projectId);
|
|
20
|
+
|
|
21
|
+
return getFirestore(app);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function createClientFirestoreServerTimestamp() {
|
|
25
|
+
return serverTimestamp();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function commitClientFirestoreBatch(
|
|
29
|
+
database: ReturnType<typeof createFirebaseClientFirestore>,
|
|
30
|
+
operations: readonly ClientFirestoreBatchOperation[],
|
|
31
|
+
): Promise<ClientFirestoreBatchResult> {
|
|
32
|
+
const batch = writeBatch(database);
|
|
33
|
+
const createdIds: string[] = [];
|
|
34
|
+
|
|
35
|
+
for (const operation of operations) {
|
|
36
|
+
const documents = collection(database, operation.collection);
|
|
37
|
+
if (operation.kind === 'create') {
|
|
38
|
+
const reference = operation.id
|
|
39
|
+
? doc(documents, operation.id)
|
|
40
|
+
: doc(documents);
|
|
41
|
+
batch.set(reference, operation.value);
|
|
42
|
+
createdIds.push(reference.id);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const reference = doc(documents, operation.id);
|
|
47
|
+
if (operation.kind === 'update') {
|
|
48
|
+
batch.update(reference, operation.value);
|
|
49
|
+
} else {
|
|
50
|
+
batch.delete(reference);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
await batch.commit();
|
|
55
|
+
return { createdIds };
|
|
56
|
+
}
|