@astronox/core 0.2.0 → 0.2.2

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 (81) hide show
  1. package/README.md +9 -30
  2. package/package.json +7 -1
  3. package/src/admin/contracts.ts +28 -0
  4. package/src/admin/index.ts +2 -0
  5. package/src/admin/module.ts +28 -0
  6. package/src/auth/contracts.ts +53 -0
  7. package/src/auth/firebase/client.ts +53 -0
  8. package/src/auth/firebase/contracts.ts +8 -0
  9. package/src/auth/firebase/index.ts +1 -0
  10. package/src/auth/index.ts +2 -0
  11. package/src/auth/module.ts +99 -0
  12. package/src/client/index.ts +3 -0
  13. package/src/client/preferences.ts +400 -0
  14. package/src/components/actions/Button/Button.astro +42 -6
  15. package/src/components/actions/Button/Button.css +19 -0
  16. package/src/components/actions/IdentityProviderAction/IdentityProviderAction.astro +35 -0
  17. package/src/components/actions/MediaTrigger/MediaTrigger.astro +12 -4
  18. package/src/components/actions/MediaTrigger/MediaTrigger.css +1 -0
  19. package/src/components/content/CardBody/CardBody.astro +3 -0
  20. package/src/components/content/CardBody/CardBody.css +10 -0
  21. package/src/components/content/MarkdownContent/MarkdownContent.css +0 -1
  22. package/src/components/content/MarkdownContent/MarkdownNode.astro +1 -0
  23. package/src/components/content/MarkdownContent/markdownContentRenderer.ts +549 -0
  24. package/src/components/feedback/PreferenceBootstrap/PreferenceBootstrap.astro +403 -0
  25. package/src/components/forms/Checkbox/Checkbox.astro +66 -0
  26. package/src/components/forms/Checkbox/Checkbox.css +126 -0
  27. package/src/components/forms/ChoiceGroup/ChoiceGroup.astro +57 -0
  28. package/src/components/forms/ChoiceGroup/ChoiceGroup.css +108 -0
  29. package/src/components/forms/ChoiceGroup/contracts.ts +9 -0
  30. package/src/components/forms/DateTimeInput/DateTimeInput.astro +14 -0
  31. package/src/components/forms/FieldGroup/FieldGroup.astro +34 -0
  32. package/src/components/forms/FieldGroup/FieldGroup.css +30 -0
  33. package/src/components/forms/TextInput/TextInput.astro +7 -1
  34. package/src/components/forms/Textarea/Textarea.astro +32 -0
  35. package/src/components/forms/Textarea/Textarea.css +54 -0
  36. package/src/components/icons/BackIcon.astro +26 -0
  37. package/src/components/icons/CheckIcon.astro +22 -0
  38. package/src/components/icons/DeleteIcon.astro +22 -0
  39. package/src/components/icons/StatusIndicatorIcon.astro +16 -0
  40. package/src/components/index.ts +12 -0
  41. package/src/components/media/CanvasFrame/CanvasFrame.astro +1 -17
  42. package/src/components/media/EmbedFrame/EmbedFrame.astro +1 -19
  43. package/src/components/media/EmbedFrame/EmbedFrame.css +0 -2
  44. package/src/components/navigation/SubNavigation/SubNavigation.css +0 -5
  45. package/src/composition/Grid/Grid.astro +9 -0
  46. package/src/composition/Grid/Grid.css +52 -0
  47. package/src/composition/GridItem/GridItem.astro +9 -0
  48. package/src/composition/GridItem/GridItem.css +12 -0
  49. package/src/composition/SurfaceGroup/SurfaceGroup.css +21 -7
  50. package/src/composition/SurfaceGroup/stickySurfaceGroupController.ts +3 -0
  51. package/src/data/firestore/client/contracts.ts +55 -0
  52. package/src/data/firestore/client/firebase.ts +12 -0
  53. package/src/data/firestore/client/index.ts +3 -0
  54. package/src/data/firestore/client/repository.ts +111 -0
  55. package/src/display/contracts.ts +41 -0
  56. package/src/display/index.ts +1 -0
  57. package/src/display/tableProgressive.ts +62 -0
  58. package/src/patterns/Combobox/Combobox.astro +23 -0
  59. package/src/patterns/Combobox/comboboxController.ts +72 -0
  60. package/src/patterns/Fullscreen/FullscreenControl.astro +4 -14
  61. package/src/patterns/Fullscreen/FullscreenControl.css +0 -26
  62. package/src/patterns/Modal/Modal.astro +1 -1
  63. package/src/patterns/Modal/Modal.css +38 -1
  64. package/src/patterns/Table/Table.astro +122 -7
  65. package/src/patterns/Table/Table.css +2 -1
  66. package/src/patterns/Table/TableBody.astro +11 -1
  67. package/src/patterns/Table/TableBody.css +6 -0
  68. package/src/patterns/Table/TableCell.css +6 -0
  69. package/src/patterns/Table/TableCellValue.astro +31 -0
  70. package/src/patterns/Table/TableFooter.astro +3 -1
  71. package/src/patterns/Table/TableHeader.astro +1 -0
  72. package/src/patterns/Table/TablePagination.astro +63 -47
  73. package/src/patterns/Table/TableSkeleton.astro +18 -22
  74. package/src/patterns/Table/TableSkeleton.css +30 -5
  75. package/src/patterns/Table/tableClientController.ts +253 -0
  76. package/src/patterns/Table/tableFilterController.ts +83 -33
  77. package/src/patterns/Table/tableLayout.ts +10 -3
  78. package/src/patterns/Table/tableProgressiveController.ts +615 -227
  79. package/src/patterns/Table/tableSortController.ts +25 -7
  80. package/src/styles/interaction.css +16 -0
  81. package/src/styles/style.css +4 -0
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # AstroNox Core
2
2
 
3
- AstroNox Core is the shared Astro foundation used by AstroNox sites. It owns
4
- reusable components, composition primitives, interaction patterns, display
5
- contracts, SEO helpers, and the complete default stylesheet.
3
+ AstroNox Core is a reusable foundation for Astro applications. It provides
4
+ components, composition primitives, interaction patterns, display contracts,
5
+ internationalization and SEO infrastructure, and a complete shared stylesheet.
6
6
 
7
7
  Product data, product routes, branding, integrations, and business rules stay
8
8
  in the consuming site.
@@ -33,11 +33,13 @@ import { Stack, SurfaceGroup } from '@astronox/core/composition';
33
33
  </Stack>
34
34
  ```
35
35
 
36
- Load the shared visual system once from the consuming application's stylesheet:
36
+ Load the shared visual system once from the consuming application's root
37
+ layout:
37
38
 
38
- ```css
39
- @import '@astronox/core/styles.css';
40
- @import './site-theme.css';
39
+ ```astro
40
+ ---
41
+ import '@astronox/core/styles.css';
42
+ ---
41
43
  ```
42
44
 
43
45
  Use the remaining entry points for layouts, patterns, primitives, display
@@ -50,26 +52,3 @@ and responsive media presentation are available through the `i18n`, `routing`,
50
52
  definitions, translations, route names, product metadata, and assets. Generic
51
53
  component labels can extend `defaultCoreMessages` through
52
54
  `resolveCoreMessages()` while retaining the English fallback.
53
-
54
- ## Local Development
55
-
56
- `astronox-core` is the only source of truth. A consuming project may link this
57
- package locally while developing a shared change, but committed dependencies
58
- must always reference a released package version.
59
-
60
- The expected workflow is:
61
-
62
- 1. Keep an exact published version in the consumer manifest and lockfile.
63
- 2. Link the sibling package locally without touching either file with
64
- `npm install --no-save --package-lock=false ../astronox-core`.
65
- 3. Implement and validate the generic change through the linked consumer.
66
- 4. Publish a prerelease or stable package version.
67
- 5. Update the exact consumer dependency before deployment.
68
-
69
- Do not edit installed package files or copy `src` folders between projects.
70
-
71
- ## Releases
72
-
73
- AstroNox Core follows semantic versioning. Public entry points are compatibility
74
- boundaries; internal files may change without notice. The initial `0.x` series
75
- is reserved for stabilizing the API with the first production consumers.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astronox/core",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Reusable Astro components, patterns, styles, and infrastructure for AstroNox sites.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -30,10 +30,15 @@
30
30
  "exports": {
31
31
  ".": "./src/index.ts",
32
32
  "./client": "./src/client/index.ts",
33
+ "./admin": "./src/admin/index.ts",
34
+ "./auth": "./src/auth/index.ts",
35
+ "./auth/firebase": "./src/auth/firebase/index.ts",
36
+ "./auth/firebase/client": "./src/auth/firebase/client.ts",
33
37
  "./components": "./src/components/index.ts",
34
38
  "./composition": "./src/composition/index.ts",
35
39
  "./config": "./src/config/index.ts",
36
40
  "./data": "./src/data/index.ts",
41
+ "./data/firestore/client": "./src/data/firestore/client/index.ts",
37
42
  "./display": "./src/display/index.ts",
38
43
  "./i18n": "./src/i18n/index.ts",
39
44
  "./layouts": "./src/layouts/index.ts",
@@ -62,6 +67,7 @@
62
67
  "astro": "^7.0.0"
63
68
  },
64
69
  "dependencies": {
70
+ "firebase": "^12.0.0",
65
71
  "zod": "4.4.3"
66
72
  },
67
73
  "devDependencies": {
@@ -0,0 +1,28 @@
1
+ import type { AuthActor, AuthSession } from '../auth';
2
+
3
+ export type AdminPage<TItem, TCursor = string> = {
4
+ items: readonly TItem[];
5
+ nextCursor?: TCursor;
6
+ };
7
+
8
+ export interface AdminResource<
9
+ TItem,
10
+ TCreateInput,
11
+ TUpdateInput = TCreateInput,
12
+ TIndexInput = void,
13
+ > {
14
+ getIndex(input: TIndexInput): Promise<AdminPage<TItem>>;
15
+ getDetail(input: { id: string }): Promise<TItem | null>;
16
+ create(input: TCreateInput, actor: AuthActor): Promise<TItem>;
17
+ update(
18
+ input: { id: string; value: TUpdateInput },
19
+ actor: AuthActor,
20
+ ): Promise<TItem>;
21
+ delete(input: { id: string }, actor: AuthActor): Promise<void>;
22
+ }
23
+
24
+ export type AdminAccess<TResources extends Record<string, unknown>> = {
25
+ actor: AuthActor;
26
+ resources: TResources;
27
+ session: AuthSession;
28
+ };
@@ -0,0 +1,2 @@
1
+ export * from './contracts';
2
+ export * from './module';
@@ -0,0 +1,28 @@
1
+ import type { AuthModule } from '../auth';
2
+ import type { AdminAccess } from './contracts';
3
+
4
+ export function createAdminModule<
5
+ TCredential,
6
+ TCapability extends string,
7
+ TResources extends Record<string, unknown>,
8
+ >({
9
+ auth,
10
+ capability,
11
+ resources,
12
+ }: {
13
+ auth: AuthModule<TCredential, TCapability>;
14
+ capability: TCapability;
15
+ resources: TResources;
16
+ }) {
17
+ return {
18
+ async getActor(token?: string | null) {
19
+ return (await auth.getSession(token))?.actor ?? null;
20
+ },
21
+ async requireAccess(
22
+ token?: string | null,
23
+ ): Promise<AdminAccess<TResources>> {
24
+ const session = await auth.requireCapability(token, capability);
25
+ return { actor: session.actor, resources, session };
26
+ },
27
+ };
28
+ }
@@ -0,0 +1,53 @@
1
+ export type AuthClaims = Readonly<Record<string, unknown>>;
2
+
3
+ export type AuthActor = {
4
+ id: string;
5
+ claims: AuthClaims;
6
+ displayName?: string;
7
+ email?: string;
8
+ imageUrl?: string;
9
+ };
10
+
11
+ export type AuthSession = {
12
+ actor: AuthActor;
13
+ expiresAt: Date;
14
+ };
15
+
16
+ export type AuthSessionGrant = AuthSession & {
17
+ token: string;
18
+ };
19
+
20
+ export interface IdentityProvider<TCredential = string> {
21
+ authenticate(credential: TCredential): Promise<AuthActor>;
22
+ }
23
+
24
+ export interface SessionProvider<TCredential = string> {
25
+ create(
26
+ credential: TCredential,
27
+ actor: AuthActor,
28
+ ): Promise<AuthSessionGrant>;
29
+ get(token: string): Promise<AuthSession | null>;
30
+ revoke(token: string): Promise<void>;
31
+ }
32
+
33
+ export interface AuthorizationPolicy<TCapability extends string = string> {
34
+ allows(actor: AuthActor, capability: TCapability): boolean | Promise<boolean>;
35
+ }
36
+
37
+ export class AuthenticationError extends Error {
38
+ readonly code = 'AUTHENTICATION_REQUIRED';
39
+
40
+ constructor(message = 'Authentication is required') {
41
+ super(message);
42
+ this.name = 'AuthenticationError';
43
+ }
44
+ }
45
+
46
+ export class AuthorizationError extends Error {
47
+ readonly code = 'AUTHORIZATION_DENIED';
48
+
49
+ constructor(message = 'Access is denied') {
50
+ super(message);
51
+ this.name = 'AuthorizationError';
52
+ }
53
+ }
@@ -0,0 +1,53 @@
1
+ import { getApps, initializeApp } from 'firebase/app';
2
+ import {
3
+ GoogleAuthProvider,
4
+ getAuth,
5
+ onAuthStateChanged,
6
+ signInWithPopup,
7
+ signOut,
8
+ type User,
9
+ } from 'firebase/auth';
10
+ import type { AuthActor } from '../contracts';
11
+ import type { FirebaseClientConfig } from './contracts';
12
+
13
+ export type FirebaseGoogleAuthClient = {
14
+ observe(listener: (actor: AuthActor | null) => void): () => void;
15
+ signIn(): Promise<AuthActor>;
16
+ signOut(): Promise<void>;
17
+ };
18
+
19
+ function toAuthActor(user: User): AuthActor {
20
+ return {
21
+ id: user.uid,
22
+ claims: {},
23
+ displayName: user.displayName ?? undefined,
24
+ email: user.email ?? undefined,
25
+ imageUrl: user.photoURL ?? undefined,
26
+ };
27
+ }
28
+
29
+ export function createFirebaseGoogleAuthClient(
30
+ config: FirebaseClientConfig,
31
+ ): FirebaseGoogleAuthClient {
32
+ const app =
33
+ getApps().find((candidate) => candidate.name === config.projectId) ??
34
+ initializeApp(config, config.projectId);
35
+ const auth = getAuth(app);
36
+ const provider = new GoogleAuthProvider();
37
+ provider.setCustomParameters({ prompt: 'select_account' });
38
+
39
+ return {
40
+ observe(listener) {
41
+ return onAuthStateChanged(auth, (user) => {
42
+ listener(user ? toAuthActor(user) : null);
43
+ });
44
+ },
45
+ async signIn() {
46
+ const result = await signInWithPopup(auth, provider);
47
+ return toAuthActor(result.user);
48
+ },
49
+ async signOut() {
50
+ await signOut(auth);
51
+ },
52
+ };
53
+ }
@@ -0,0 +1,8 @@
1
+ export type FirebaseClientConfig = {
2
+ apiKey: string;
3
+ appId: string;
4
+ authDomain: string;
5
+ messagingSenderId?: string;
6
+ projectId: string;
7
+ storageBucket?: string;
8
+ };
@@ -0,0 +1 @@
1
+ export * from './contracts';
@@ -0,0 +1,2 @@
1
+ export * from './contracts';
2
+ export * from './module';
@@ -0,0 +1,99 @@
1
+ import {
2
+ AuthenticationError,
3
+ AuthorizationError,
4
+ type AuthActor,
5
+ type AuthSession,
6
+ type AuthSessionGrant,
7
+ type AuthorizationPolicy,
8
+ type IdentityProvider,
9
+ type SessionProvider,
10
+ } from './contracts';
11
+
12
+ export type AuthModule<TCredential, TCapability extends string> = {
13
+ createSession(credential: TCredential): Promise<AuthSessionGrant>;
14
+ createAuthorizedSession(
15
+ credential: TCredential,
16
+ capability: TCapability,
17
+ ): Promise<AuthSessionGrant>;
18
+ getSession(token?: string | null): Promise<AuthSession | null>;
19
+ requireCapability(
20
+ token: string | null | undefined,
21
+ capability: TCapability,
22
+ ): Promise<AuthSession>;
23
+ revokeSession(token?: string | null): Promise<void>;
24
+ };
25
+
26
+ export function createAuthModule<TCredential, TCapability extends string>({
27
+ authorizationPolicy,
28
+ identityProvider,
29
+ sessionProvider,
30
+ }: {
31
+ authorizationPolicy: AuthorizationPolicy<TCapability>;
32
+ identityProvider: IdentityProvider<TCredential>;
33
+ sessionProvider: SessionProvider<TCredential>;
34
+ }): AuthModule<TCredential, TCapability> {
35
+ async function authenticate(credential: TCredential): Promise<AuthActor> {
36
+ return identityProvider.authenticate(credential);
37
+ }
38
+
39
+ async function authorize(
40
+ actor: AuthActor,
41
+ capability: TCapability,
42
+ ): Promise<void> {
43
+ if (!(await authorizationPolicy.allows(actor, capability))) {
44
+ throw new AuthorizationError();
45
+ }
46
+ }
47
+
48
+ return {
49
+ async createSession(credential) {
50
+ const actor = await authenticate(credential);
51
+ return sessionProvider.create(credential, actor);
52
+ },
53
+
54
+ async createAuthorizedSession(credential, capability) {
55
+ const actor = await authenticate(credential);
56
+ await authorize(actor, capability);
57
+ return sessionProvider.create(credential, actor);
58
+ },
59
+
60
+ async getSession(token) {
61
+ if (!token) return null;
62
+
63
+ try {
64
+ return await sessionProvider.get(token);
65
+ } catch {
66
+ return null;
67
+ }
68
+ },
69
+
70
+ async requireCapability(token, capability) {
71
+ if (!token) throw new AuthenticationError();
72
+
73
+ const session = await sessionProvider.get(token);
74
+ if (!session) throw new AuthenticationError();
75
+ await authorize(session.actor, capability);
76
+ return session;
77
+ },
78
+
79
+ async revokeSession(token) {
80
+ if (!token) return;
81
+ await sessionProvider.revoke(token);
82
+ },
83
+ };
84
+ }
85
+
86
+ export function requireClaim<TCapability extends string>(
87
+ claim: string,
88
+ expected: unknown,
89
+ capability?: TCapability,
90
+ ): AuthorizationPolicy<TCapability> {
91
+ return {
92
+ allows(actor: AuthActor, requestedCapability: TCapability) {
93
+ if (capability !== undefined && requestedCapability !== capability) {
94
+ return false;
95
+ }
96
+ return actor.claims[claim] === expected;
97
+ },
98
+ };
99
+ }
@@ -1,3 +1,6 @@
1
1
  export * from './clipboard';
2
2
  export * from './focus';
3
+ export * from './preferences';
3
4
  export * from './scroll';
5
+ export * from '../components/content/MarkdownContent/markdownContentRenderer';
6
+ export * from '../patterns/Table/tableClientController';