@astronox/core 0.2.4 → 0.3.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 (112) hide show
  1. package/README.md +16 -1
  2. package/package.json +18 -4
  3. package/src/admin/authController.ts +166 -0
  4. package/src/admin/cache.ts +44 -17
  5. package/src/admin/index.ts +3 -0
  6. package/src/admin/preparedDestination.ts +114 -0
  7. package/src/admin/resourceClient.ts +107 -0
  8. package/src/ads/adsense/index.ts +1 -0
  9. package/src/ads/adsense.ts +112 -0
  10. package/src/ads/contracts.ts +11 -0
  11. package/src/ads/index.ts +2 -0
  12. package/src/ads/slot.ts +16 -0
  13. package/src/auth/contracts.ts +1 -4
  14. package/src/auth/firebase/client.ts +19 -6
  15. package/src/auth/firebase/index.ts +1 -0
  16. package/src/client/formSubmission.ts +6 -1
  17. package/src/client/preferences.ts +8 -4
  18. package/src/components/actions/Button/Button.astro +30 -42
  19. package/src/components/actions/CardAction/CardAction.css +5 -10
  20. package/src/components/actions/IdentityProviderAction/IdentityProviderAction.astro +6 -8
  21. package/src/components/actions/MediaTrigger/MediaTrigger.astro +1 -1
  22. package/src/components/actions/ScrollToTop/ScrollToTop.astro +2 -1
  23. package/src/components/actions/ScrollToTop/ScrollToTop.css +2 -6
  24. package/src/components/actions/ThemeToggle/ThemeToggle.astro +3 -3
  25. package/src/components/actions/ThemeToggle/themeToggleController.ts +2 -3
  26. package/src/components/content/CardFooter/CardFooter.css +2 -4
  27. package/src/components/content/CardHeader/CardHeader.astro +1 -4
  28. package/src/components/content/CardLink/CardLink.astro +3 -15
  29. package/src/components/content/CardLink/CardLink.css +4 -15
  30. package/src/components/content/CodeBlock/CodeBlock.astro +7 -6
  31. package/src/components/content/CodeBlock/CodeBlock.css +2 -5
  32. package/src/components/content/CodeBlock/codeBlockController.ts +1 -2
  33. package/src/components/content/MarkdownContent/MarkdownContent.astro +1 -3
  34. package/src/components/content/MarkdownContent/MarkdownContent.css +7 -2
  35. package/src/components/content/MarkdownContent/MarkdownInline.astro +11 -3
  36. package/src/components/content/MarkdownContent/MarkdownNode.astro +28 -33
  37. package/src/components/content/MarkdownContent/MarkdownSymbol.astro +30 -19
  38. package/src/components/content/MarkdownContent/contracts.ts +15 -15
  39. package/src/components/content/MarkdownContent/markdownContentRenderer.ts +27 -83
  40. package/src/components/feedback/PreferenceBootstrap/PreferenceBootstrap.astro +19 -22
  41. package/src/components/feedback/Spinner/Spinner.astro +1 -2
  42. package/src/components/feedback/Tooltip/tooltipController.ts +1 -4
  43. package/src/components/forms/ChoiceGroup/ChoiceGroup.css +1 -4
  44. package/src/components/forms/DateTimeInput/DateTimeInput.astro +2 -1
  45. package/src/components/forms/PreparedFormBootstrap/PreparedFormBootstrap.astro +10 -8
  46. package/src/components/forms/PreparedFormBootstrap/parsePreparedFormState.ts +48 -0
  47. package/src/components/forms/Textarea/Textarea.astro +2 -2
  48. package/src/components/icons/BurnIcon.astro +2 -1
  49. package/src/components/icons/FieldIcon.astro +10 -4
  50. package/src/components/icons/GrassIcon.astro +3 -2
  51. package/src/components/icons/SlowIcon.astro +1 -2
  52. package/src/components/icons/StunIcon.astro +2 -1
  53. package/src/components/icons/WaterIcon.astro +3 -2
  54. package/src/components/index.ts +2 -0
  55. package/src/components/media/CanvasFrame/CanvasFrame.astro +1 -2
  56. package/src/components/media/CanvasFrame/canvasFrameController.ts +1 -4
  57. package/src/components/media/EmbedFrame/EmbedFrame.astro +5 -12
  58. package/src/components/media/EmbedFrame/embedFrameController.ts +4 -4
  59. package/src/components/monetization/AdSlot/AdSlot.astro +33 -0
  60. package/src/components/monetization/AdSlot/AdSlot.css +53 -0
  61. package/src/components/navigation/DesktopNavigation.css +1 -2
  62. package/src/components/navigation/Footer/Footer.css +3 -9
  63. package/src/components/navigation/MobileNavigation.css +3 -12
  64. package/src/components/navigation/navigationTypes.ts +1 -4
  65. package/src/components/seo/SeoHead.astro +1 -6
  66. package/src/composition/SurfaceGroup/SurfaceGroup.css +4 -14
  67. package/src/config/environment.ts +52 -0
  68. package/src/config/index.ts +3 -0
  69. package/src/config/integrations.ts +9 -0
  70. package/src/config/siteConfig.ts +5 -0
  71. package/src/config/storage.ts +16 -0
  72. package/src/content/admin.ts +354 -0
  73. package/src/content/cache.ts +177 -0
  74. package/src/content/catalog.ts +72 -0
  75. package/src/content/contracts.ts +77 -0
  76. package/src/content/drafts.ts +49 -0
  77. package/src/content/index.ts +7 -0
  78. package/src/content/markdown.ts +146 -0
  79. package/src/content/snapshot.ts +139 -0
  80. package/src/data/firestore/client/contracts.ts +10 -2
  81. package/src/data/index.ts +1 -0
  82. package/src/data/memoizedLoader.ts +13 -0
  83. package/src/display/contracts.ts +1 -5
  84. package/src/i18n/config.ts +2 -8
  85. package/src/i18n/coreMessages.ts +2 -9
  86. package/src/i18n/preference.ts +2 -6
  87. package/src/patterns/Combobox/Combobox.astro +9 -12
  88. package/src/patterns/Combobox/comboboxController.ts +14 -8
  89. package/src/patterns/Fullscreen/FullscreenControl.astro +5 -1
  90. package/src/patterns/Fullscreen/fullscreenController.ts +1 -3
  91. package/src/patterns/MapViewport/geometry.ts +1 -4
  92. package/src/patterns/Modal/Modal.css +1 -4
  93. package/src/patterns/Modal/ModalOpen.astro +5 -1
  94. package/src/patterns/Table/Table.astro +10 -10
  95. package/src/patterns/Table/TableBody.astro +1 -4
  96. package/src/patterns/Table/TableBody.css +2 -4
  97. package/src/patterns/Table/TablePagination.astro +23 -30
  98. package/src/patterns/Table/TableSkeleton.astro +1 -5
  99. package/src/patterns/Table/tableClientController.ts +2 -2
  100. package/src/patterns/Table/tableFilterController.ts +1 -3
  101. package/src/patterns/Table/tableHeaderOverflowController.ts +1 -4
  102. package/src/patterns/Table/tableProgressiveController.ts +62 -60
  103. package/src/patterns/Table/tableScrollController.ts +3 -2
  104. package/src/primitives/text/TextBlock/TextBlock.astro +1 -3
  105. package/src/publication/contracts.ts +11 -0
  106. package/src/publication/http.ts +31 -0
  107. package/src/publication/index.ts +2 -0
  108. package/src/routing/localized.ts +9 -12
  109. package/src/seo/localizedMeta.ts +2 -6
  110. package/src/styles/interaction.css +7 -7
  111. package/src/styles/style.css +11 -20
  112. package/src/theme/contracts.ts +1 -6
@@ -0,0 +1,112 @@
1
+ import type { AdConsentProvider, AdProvider } from './contracts';
2
+ import { setAdSlotState } from './slot';
3
+
4
+ declare global {
5
+ interface Window {
6
+ adsbygoogle?: Record<string, unknown>[];
7
+ }
8
+ }
9
+
10
+ export function createAdsenseProvider({
11
+ clientId,
12
+ consent,
13
+ loadTimeoutMs,
14
+ }: {
15
+ clientId: string;
16
+ consent: AdConsentProvider;
17
+ loadTimeoutMs: number;
18
+ }): AdProvider {
19
+ let loading: Promise<void> | null = null;
20
+
21
+ const load = async (): Promise<void> => {
22
+ if (!(await consent.canLoadAds())) {
23
+ throw new Error('Advertising consent was not granted');
24
+ }
25
+ loading ??= loadAdsenseScript(clientId, loadTimeoutMs).catch((reason) => {
26
+ loading = null;
27
+ throw reason;
28
+ });
29
+ return loading;
30
+ };
31
+
32
+ return {
33
+ load,
34
+ async request(slot) {
35
+ setAdSlotState(slot, 'loading');
36
+ try {
37
+ await load();
38
+ window.adsbygoogle ??= [];
39
+ window.adsbygoogle.push({});
40
+ observeAdsenseSlot(slot);
41
+ } catch (reason) {
42
+ setAdSlotState(slot, 'blocked');
43
+ throw reason;
44
+ }
45
+ },
46
+ };
47
+ }
48
+
49
+ function loadAdsenseScript(
50
+ clientId: string,
51
+ loadTimeoutMs: number,
52
+ ): Promise<void> {
53
+ const existing = document.querySelector<HTMLScriptElement>(
54
+ 'script[data-astronox-adsense]',
55
+ );
56
+ if (existing?.dataset.loaded === 'true') return Promise.resolve();
57
+
58
+ return new Promise((resolve, reject) => {
59
+ const script = existing ?? document.createElement('script');
60
+ script.async = true;
61
+ script.crossOrigin = 'anonymous';
62
+ script.dataset.astronoxAdsense = '';
63
+ script.src = `https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=${encodeURIComponent(clientId)}`;
64
+ const timeout = window.setTimeout(
65
+ () => reject(new Error('AdSense loading timed out')),
66
+ loadTimeoutMs,
67
+ );
68
+ script.addEventListener(
69
+ 'load',
70
+ () => {
71
+ window.clearTimeout(timeout);
72
+ script.dataset.loaded = 'true';
73
+ resolve();
74
+ },
75
+ { once: true },
76
+ );
77
+ script.addEventListener(
78
+ 'error',
79
+ () => {
80
+ window.clearTimeout(timeout);
81
+ reject(new Error('AdSense failed to load'));
82
+ },
83
+ { once: true },
84
+ );
85
+ if (!existing) document.head.append(script);
86
+ });
87
+ }
88
+
89
+ function observeAdsenseSlot(slot: HTMLElement): void {
90
+ const advertisement = slot.querySelector<HTMLElement>('.adsbygoogle');
91
+ if (!advertisement) {
92
+ setAdSlotState(slot, 'unavailable');
93
+ return;
94
+ }
95
+
96
+ const sync = (): void => {
97
+ const status = advertisement.dataset.adStatus;
98
+ if (status === 'filled') setAdSlotState(slot, 'filled');
99
+ if (status === 'unfilled') setAdSlotState(slot, 'unavailable');
100
+ };
101
+ sync();
102
+ if (advertisement.dataset.adStatus) return;
103
+
104
+ const observer = new MutationObserver(() => {
105
+ sync();
106
+ if (advertisement.dataset.adStatus) observer.disconnect();
107
+ });
108
+ observer.observe(advertisement, {
109
+ attributeFilter: ['data-ad-status'],
110
+ attributes: true,
111
+ });
112
+ }
@@ -0,0 +1,11 @@
1
+ export type AdSlotState =
2
+ 'idle' | 'loading' | 'filled' | 'blocked' | 'unavailable';
3
+
4
+ export type AdConsentProvider = {
5
+ canLoadAds(): boolean | Promise<boolean>;
6
+ };
7
+
8
+ export interface AdProvider {
9
+ load(): Promise<void>;
10
+ request(slot: HTMLElement): Promise<void>;
11
+ }
@@ -0,0 +1,2 @@
1
+ export * from './contracts';
2
+ export * from './slot';
@@ -0,0 +1,16 @@
1
+ import type { AdSlotState } from './contracts';
2
+
3
+ export function setAdSlotState(slot: HTMLElement, state: AdSlotState): void {
4
+ slot.dataset.adState = state;
5
+ slot.setAttribute('aria-busy', state === 'loading' ? 'true' : 'false');
6
+ }
7
+
8
+ export function getAdSlotState(slot: HTMLElement): AdSlotState {
9
+ const state = slot.dataset.adState;
10
+ return state === 'loading' ||
11
+ state === 'filled' ||
12
+ state === 'blocked' ||
13
+ state === 'unavailable'
14
+ ? state
15
+ : 'idle';
16
+ }
@@ -22,10 +22,7 @@ export interface IdentityProvider<TCredential = string> {
22
22
  }
23
23
 
24
24
  export interface SessionProvider<TCredential = string> {
25
- create(
26
- credential: TCredential,
27
- actor: AuthActor,
28
- ): Promise<AuthSessionGrant>;
25
+ create(credential: TCredential, actor: AuthActor): Promise<AuthSessionGrant>;
29
26
  get(token: string): Promise<AuthSession | null>;
30
27
  revoke(token: string): Promise<void>;
31
28
  }
@@ -5,9 +5,10 @@ import {
5
5
  GoogleAuthProvider,
6
6
  getAuth,
7
7
  initializeAuth,
8
- onAuthStateChanged,
8
+ onIdTokenChanged,
9
9
  signInWithPopup,
10
10
  signOut,
11
+ getIdTokenResult,
11
12
  type Auth,
12
13
  type User,
13
14
  } from 'firebase/auth';
@@ -42,10 +43,11 @@ function initializeBrowserAuth(app: FirebaseApp): Auth {
42
43
  }
43
44
  }
44
45
 
45
- function toAuthActor(user: User): AuthActor {
46
+ async function toAuthActor(user: User): Promise<AuthActor> {
47
+ const token = await getIdTokenResult(user);
46
48
  return {
47
49
  id: user.uid,
48
- claims: {},
50
+ claims: { ...token.claims },
49
51
  displayName: user.displayName ?? undefined,
50
52
  email: user.email ?? undefined,
51
53
  imageUrl: user.photoURL ?? undefined,
@@ -64,17 +66,28 @@ export function createFirebaseGoogleAuthClient(
64
66
 
65
67
  return {
66
68
  observe(listener, onError) {
67
- return onAuthStateChanged(
69
+ let revision = 0;
70
+ return onIdTokenChanged(
68
71
  auth,
69
72
  (user) => {
70
- listener(user ? toAuthActor(user) : null);
73
+ const currentRevision = ++revision;
74
+ if (!user) {
75
+ listener(null);
76
+ return;
77
+ }
78
+ void toAuthActor(user).then(
79
+ (actor) => {
80
+ if (revision === currentRevision) listener(actor);
81
+ },
82
+ (reason) => onError?.(reason),
83
+ );
71
84
  },
72
85
  onError,
73
86
  );
74
87
  },
75
88
  async signIn() {
76
89
  const result = await signInWithPopup(auth, provider);
77
- return toAuthActor(result.user);
90
+ return await toAuthActor(result.user);
78
91
  },
79
92
  async signOut() {
80
93
  await signOut(auth);
@@ -1 +1,2 @@
1
+ export * from './client';
1
2
  export * from './contracts';
@@ -140,7 +140,12 @@ function compareValues(
140
140
  left.getTime() === right.getTime()
141
141
  );
142
142
  }
143
- if (!left || !right || typeof left !== 'object' || typeof right !== 'object') {
143
+ if (
144
+ !left ||
145
+ !right ||
146
+ typeof left !== 'object' ||
147
+ typeof right !== 'object'
148
+ ) {
144
149
  return false;
145
150
  }
146
151
 
@@ -84,7 +84,9 @@ export function definePreference<T>(
84
84
  ): PreferenceDefinition<T> {
85
85
  assertDefinition(definition);
86
86
  if (!matchesPreferenceSchema(definition.defaultValue, definition.schema)) {
87
- throw new TypeError(`Invalid default value for preference ${definition.id}`);
87
+ throw new TypeError(
88
+ `Invalid default value for preference ${definition.id}`,
89
+ );
88
90
  }
89
91
  return definition;
90
92
  }
@@ -221,7 +223,10 @@ export function createPreferenceRegistry({
221
223
  };
222
224
  },
223
225
  destroy(): void {
224
- document.removeEventListener(preferenceChangeEvent, handlePreferenceChange);
226
+ document.removeEventListener(
227
+ preferenceChangeEvent,
228
+ handlePreferenceChange,
229
+ );
225
230
  window.removeEventListener('storage', handleStorage);
226
231
  listeners.clear();
227
232
  },
@@ -340,8 +345,7 @@ function applyMigration(
340
345
 
341
346
  function getGlobalSnapshot(): PreferenceSnapshot | undefined {
342
347
  return (globalThis as Record<string, unknown>)[snapshotGlobalKey] as
343
- | PreferenceSnapshot
344
- | undefined;
348
+ PreferenceSnapshot | undefined;
345
349
  }
346
350
 
347
351
  function setGlobalSnapshot(snapshot: PreferenceSnapshot): void {
@@ -57,21 +57,17 @@ const resolvedTone = tone ?? dataTone;
57
57
  rel={resolvedRel}
58
58
  target={target}
59
59
  >
60
- {
61
- hasLeading ? (
62
- <span class="nox-button__affix">
63
- <slot name="leading" />
64
- </span>
65
- ) : null
66
- }
60
+ {hasLeading ? (
61
+ <span class="nox-button__affix">
62
+ <slot name="leading" />
63
+ </span>
64
+ ) : null}
67
65
  <slot />
68
- {
69
- hasTrailing ? (
70
- <span class="nox-button__affix">
71
- <slot name="trailing" />
72
- </span>
73
- ) : null
74
- }
66
+ {hasTrailing ? (
67
+ <span class="nox-button__affix">
68
+ <slot name="trailing" />
69
+ </span>
70
+ ) : null}
75
71
  </a>
76
72
  ) : href ? (
77
73
  <span
@@ -86,21 +82,17 @@ const resolvedTone = tone ?? dataTone;
86
82
  data-button-size={size}
87
83
  data-tone={resolvedTone}
88
84
  >
89
- {
90
- hasLeading ? (
91
- <span class="nox-button__affix">
92
- <slot name="leading" />
93
- </span>
94
- ) : null
95
- }
85
+ {hasLeading ? (
86
+ <span class="nox-button__affix">
87
+ <slot name="leading" />
88
+ </span>
89
+ ) : null}
96
90
  <slot />
97
- {
98
- hasTrailing ? (
99
- <span class="nox-button__affix">
100
- <slot name="trailing" />
101
- </span>
102
- ) : null
103
- }
91
+ {hasTrailing ? (
92
+ <span class="nox-button__affix">
93
+ <slot name="trailing" />
94
+ </span>
95
+ ) : null}
104
96
  </span>
105
97
  ) : (
106
98
  <button
@@ -116,21 +108,17 @@ const resolvedTone = tone ?? dataTone;
116
108
  disabled={disabled}
117
109
  type={type}
118
110
  >
119
- {
120
- hasLeading ? (
121
- <span class="nox-button__affix">
122
- <slot name="leading" />
123
- </span>
124
- ) : null
125
- }
111
+ {hasLeading ? (
112
+ <span class="nox-button__affix">
113
+ <slot name="leading" />
114
+ </span>
115
+ ) : null}
126
116
  <slot />
127
- {
128
- hasTrailing ? (
129
- <span class="nox-button__affix">
130
- <slot name="trailing" />
131
- </span>
132
- ) : null
133
- }
117
+ {hasTrailing ? (
118
+ <span class="nox-button__affix">
119
+ <slot name="trailing" />
120
+ </span>
121
+ ) : null}
134
122
  </button>
135
123
  )
136
124
  }
@@ -59,8 +59,7 @@
59
59
  color: var(--nox-tone-color);
60
60
  }
61
61
 
62
- .card-action[data-card-action-visual-position='end']
63
- .card-action__visual {
62
+ .card-action[data-card-action-visual-position='end'] .card-action__visual {
64
63
  grid-column: 2;
65
64
  border-inline-start: var(--surface-border-width) solid
66
65
  var(--surface-border-color);
@@ -98,8 +97,7 @@
98
97
  overflow-wrap: anywhere;
99
98
  }
100
99
 
101
- .card-action[data-card-action-label-treatment='heading']
102
- .card-action__label {
100
+ .card-action[data-card-action-label-treatment='heading'] .card-action__label {
103
101
  font-size: var(--nox-font-size-lg);
104
102
  letter-spacing: var(--nox-letter-spacing-wide);
105
103
  text-transform: uppercase;
@@ -110,8 +108,7 @@
110
108
  grid-row: 1;
111
109
  }
112
110
 
113
- .card-action[data-card-action-visual-position='end']
114
- .card-action__label {
111
+ .card-action[data-card-action-visual-position='end'] .card-action__label {
115
112
  grid-column: 1;
116
113
  }
117
114
 
@@ -172,13 +169,11 @@
172
169
  }
173
170
 
174
171
  @media (width < 48rem) {
175
- .card-action[data-card-action-compact-label]
176
- .card-action__label-full {
172
+ .card-action[data-card-action-compact-label] .card-action__label-full {
177
173
  display: none;
178
174
  }
179
175
 
180
- .card-action[data-card-action-compact-label]
181
- .card-action__label-compact {
176
+ .card-action[data-card-action-compact-label] .card-action__label-compact {
182
177
  display: block;
183
178
  }
184
179
 
@@ -3,18 +3,14 @@ import type { HTMLAttributes } from 'astro/types';
3
3
  import type { Tone } from '../../../theme/contracts';
4
4
  import Button from '../Button/Button.astro';
5
5
 
6
- type Props = HTMLAttributes<'button'> & {
6
+ type Props = Omit<HTMLAttributes<'button'>, 'disabled'> & {
7
+ disabled?: boolean;
7
8
  label: string;
8
9
  pendingLabel?: string;
9
10
  tone?: Tone;
10
11
  };
11
12
 
12
- const {
13
- label,
14
- pendingLabel = label,
15
- tone,
16
- ...attrs
17
- }: Props = Astro.props;
13
+ const { label, pendingLabel = label, tone, ...attrs }: Props = Astro.props;
18
14
  ---
19
15
 
20
16
  <Button
@@ -28,7 +24,9 @@ const {
28
24
  >
29
25
  {
30
26
  Astro.slots.has('leading') ? (
31
- <span slot="leading"><slot name="leading" /></span>
27
+ <span slot="leading">
28
+ <slot name="leading" />
29
+ </span>
32
30
  ) : null
33
31
  }
34
32
  <span data-auth-provider-action-label>{label}</span>
@@ -12,7 +12,7 @@ type SharedProps = {
12
12
  };
13
13
 
14
14
  type Props = SharedProps &
15
- Omit<HTMLAttributes<'a'>, 'href' | 'type'> & {
15
+ Omit<HTMLAttributes<'a'>, 'href' | 'media' | 'type'> & {
16
16
  href?: string;
17
17
  };
18
18
 
@@ -3,7 +3,8 @@ import type { HTMLAttributes } from 'astro/types';
3
3
  import Button from '../Button/Button.astro';
4
4
  import ArrowUpIcon from '../../icons/ArrowUpIcon.astro';
5
5
 
6
- type Props = Omit<HTMLAttributes<'button'>, 'type'> & {
6
+ type Props = Omit<HTMLAttributes<'button'>, 'disabled' | 'type'> & {
7
+ disabled?: boolean;
7
8
  label: string;
8
9
  };
9
10
 
@@ -1,12 +1,8 @@
1
1
  .scroll-to-top.nox-button {
2
2
  position: fixed;
3
3
  z-index: 45;
4
- inset-inline-end: calc(
5
- var(--nox-space-5) + env(safe-area-inset-right)
6
- );
7
- inset-block-end: calc(
8
- var(--nox-space-5) + env(safe-area-inset-bottom)
9
- );
4
+ inset-inline-end: calc(var(--nox-space-5) + env(safe-area-inset-right));
5
+ inset-block-end: calc(var(--nox-space-5) + env(safe-area-inset-bottom));
10
6
  inline-size: var(--nox-section-compact-block-size);
11
7
  block-size: var(--nox-section-compact-block-size);
12
8
  min-block-size: 0;
@@ -30,9 +30,9 @@ const {
30
30
  data-theme-toggle-dark-label={darkLabel}
31
31
  data-theme-toggle-light-label={lightLabel}
32
32
  data-navigation-control={appearance === 'circle' ? '' : undefined}
33
- data-navigation-control-appearance={
34
- appearance === 'circle' ? 'bar' : undefined
35
- }
33
+ data-navigation-control-appearance={appearance === 'circle'
34
+ ? 'bar'
35
+ : undefined}
36
36
  >
37
37
  <span
38
38
  class="theme-toggle__icon"
@@ -11,8 +11,7 @@ function isColorTheme(value: string | null): value is ColorTheme {
11
11
 
12
12
  function getStorageKey(): string {
13
13
  return (
14
- document.documentElement.dataset.themeStorageKey ??
15
- defaultThemeStorageKey
14
+ document.documentElement.dataset.themeStorageKey ?? defaultThemeStorageKey
16
15
  );
17
16
  }
18
17
 
@@ -31,7 +30,7 @@ function getSystemTheme(): ColorTheme {
31
30
 
32
31
  function getCurrentTheme(): ColorTheme {
33
32
  const value = document.documentElement.dataset.theme ?? null;
34
- return isColorTheme(value) ? value : readStoredTheme() ?? getSystemTheme();
33
+ return isColorTheme(value) ? value : (readStoredTheme() ?? getSystemTheme());
35
34
  }
36
35
 
37
36
  function updateToggle(toggle: HTMLElement, theme: ColorTheme): void {
@@ -42,16 +42,14 @@
42
42
  var(--card-footer-size);
43
43
  }
44
44
 
45
- .card-footer[data-has-leading-action][data-has-action]
46
- .card-footer__layout {
45
+ .card-footer[data-has-leading-action][data-has-action] .card-footer__layout {
47
46
  grid-template-columns:
48
47
  var(--card-footer-size)
49
48
  minmax(0, 1fr)
50
49
  var(--card-footer-size);
51
50
  }
52
51
 
53
- .card-footer[data-has-leading-action][data-has-media]
54
- .card-footer__layout {
52
+ .card-footer[data-has-leading-action][data-has-media] .card-footer__layout {
55
53
  grid-template-columns:
56
54
  var(--card-footer-size)
57
55
  var(--card-footer-size)
@@ -2,10 +2,7 @@
2
2
  import type { HTMLAttributes } from 'astro/types';
3
3
  import type { Tone } from '../../../theme/contracts';
4
4
  import type { DisplayMedia, TextWeight } from '../../../display/contracts';
5
- import type {
6
- AstroComponent,
7
- HeadingLevel,
8
- } from '../../widgetTypes';
5
+ import type { AstroComponent, HeadingLevel } from '../../widgetTypes';
9
6
  import Inline from '../../../composition/Inline/Inline.astro';
10
7
  import Media from '../../../primitives/media/Media/Media.astro';
11
8
 
@@ -90,11 +90,7 @@ const resolvedTone = tone ?? dataTone;
90
90
  {Icon ? (
91
91
  <Icon class="card-link__icon" data-tone={iconTone} />
92
92
  ) : media ? (
93
- <Media
94
- media={media}
95
- fit="contain"
96
- size="intrinsic"
97
- />
93
+ <Media media={media} fit="contain" size="intrinsic" />
98
94
  ) : null}
99
95
  </span>
100
96
  ) : null
@@ -104,11 +100,7 @@ const resolvedTone = tone ?? dataTone;
104
100
  {
105
101
  hasContentMedia && media ? (
106
102
  <span class="card-link__content-media" aria-hidden="true">
107
- <Media
108
- media={media}
109
- fit="contain"
110
- size="intrinsic"
111
- />
103
+ <Media media={media} fit="contain" size="intrinsic" />
112
104
  </span>
113
105
  ) : null
114
106
  }
@@ -134,11 +126,7 @@ const resolvedTone = tone ?? dataTone;
134
126
  {Icon ? (
135
127
  <Icon class="card-link__icon" data-tone={iconTone} />
136
128
  ) : media ? (
137
- <Media
138
- media={media}
139
- fit="contain"
140
- size="intrinsic"
141
- />
129
+ <Media media={media} fit="contain" size="intrinsic" />
142
130
  ) : null}
143
131
  </span>
144
132
  ) : null
@@ -154,26 +154,17 @@
154
154
  );
155
155
  }
156
156
 
157
- .card-link:is(
158
- [data-media-size='compact'],
159
- [data-media-size='small']
160
- )
157
+ .card-link:is([data-media-size='compact'], [data-media-size='small'])
161
158
  .card-link__layout {
162
159
  --card-link-artwork-max-size: var(--card-link-resolved-media-size);
163
160
  }
164
161
 
165
- .card-link:is(
166
- [data-media-size='compact'],
167
- [data-media-size='small']
168
- )
162
+ .card-link:is([data-media-size='compact'], [data-media-size='small'])
169
163
  .card-link__content {
170
164
  --card-link-content-media-size: var(--card-link-resolved-media-size);
171
165
  }
172
166
 
173
- .card-link:is(
174
- [data-media-size='compact'],
175
- [data-media-size='small']
176
- )
167
+ .card-link:is([data-media-size='compact'], [data-media-size='small'])
177
168
  :is(.card-link__media, .card-link__content-media)
178
169
  [data-widget='media'] {
179
170
  --media-object-inline-size: 100%;
@@ -203,9 +194,7 @@
203
194
  }
204
195
 
205
196
  .card-link[data-density='relaxed'] .card-link__layout {
206
- --card-link-size: calc(
207
- var(--nox-navigation-block-size) + var(--nox-space-5)
208
- );
197
+ --card-link-size: calc(var(--nox-navigation-block-size) + var(--nox-space-5));
209
198
  }
210
199
 
211
200
  .card-link[data-density='relaxed'] .card-link__content {
@@ -3,10 +3,7 @@ import type { HTMLAttributes } from 'astro/types';
3
3
  import Button from '../../actions/Button/Button.astro';
4
4
  import CopyIcon from '../../icons/CopyIcon.astro';
5
5
  import type { Tone } from '../../../theme/contracts';
6
- import type {
7
- CodeBlockCopy,
8
- CodeBlockPresentation,
9
- } from './contracts';
6
+ import type { CodeBlockCopy, CodeBlockPresentation } from './contracts';
10
7
 
11
8
  type Props = HTMLAttributes<'pre'> & {
12
9
  copy?: CodeBlockCopy;
@@ -42,7 +39,9 @@ const resolvedTone = tone ?? dataTone;
42
39
  data-code-block-content
43
40
  data-code-block-presentation={presentation}
44
41
  data-wrap={wrap ? true : undefined}
45
- >{text}</pre>
42
+ >
43
+ {text}
44
+ </pre>
46
45
  <Button
47
46
  class="code-block__copy"
48
47
  type="button"
@@ -75,7 +74,9 @@ const resolvedTone = tone ?? dataTone;
75
74
  data-code-block-presentation={presentation}
76
75
  data-tone={resolvedTone}
77
76
  data-wrap={wrap ? true : undefined}
78
- >{text}</pre>
77
+ >
78
+ {text}
79
+ </pre>
79
80
  )
80
81
  }
81
82