astrogators-shared-ui 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/README.md ADDED
@@ -0,0 +1,318 @@
1
+ # @psytor/astrogators-shared-ui
2
+
3
+ Shared UI components and utilities for Astrogator's Table applications.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @psytor/astrogators-shared-ui
9
+ ```
10
+
11
+ ## Setup
12
+
13
+ ### 1. Initialize API Client
14
+
15
+ In your application's entry point (e.g., `main.tsx` or `App.tsx`):
16
+
17
+ ```tsx
18
+ import { initializeApiClient } from '@psytor/astrogators-shared-ui';
19
+
20
+ initializeApiClient({
21
+ baseURL: 'http://localhost:8000', // Your backend API URL
22
+ onUnauthorized: () => {
23
+ // Handle unauthorized (e.g., redirect to login)
24
+ window.location.href = '/login';
25
+ },
26
+ });
27
+ ```
28
+
29
+ ### 2. Wrap Application with AuthProvider
30
+
31
+ ```tsx
32
+ import { AuthProvider } from '@psytor/astrogators-shared-ui';
33
+
34
+ function App() {
35
+ return (
36
+ <AuthProvider>
37
+ {/* Your app content */}
38
+ </AuthProvider>
39
+ );
40
+ }
41
+ ```
42
+
43
+ ### 3. Import Global Styles
44
+
45
+ ```tsx
46
+ import '@psytor/astrogators-shared-ui/styles';
47
+ ```
48
+
49
+ ## Components
50
+
51
+ ### Layout Components
52
+
53
+ #### TopBar
54
+
55
+ ```tsx
56
+ import { TopBar } from '@psytor/astrogators-shared-ui';
57
+
58
+ <TopBar
59
+ logo={<div>Astrogator's Table</div>}
60
+ rightContent={<button>Login</button>}
61
+ />
62
+ ```
63
+
64
+ #### Container
65
+
66
+ ```tsx
67
+ import { Container } from '@psytor/astrogators-shared-ui';
68
+
69
+ <Container maxWidth="lg" padding>
70
+ {/* Content */}
71
+ </Container>
72
+ ```
73
+
74
+ #### Footer
75
+
76
+ ```tsx
77
+ import { Footer } from '@psytor/astrogators-shared-ui';
78
+
79
+ <Footer />
80
+ ```
81
+
82
+ ### Form Components
83
+
84
+ #### Button
85
+
86
+ ```tsx
87
+ import { Button } from '@psytor/astrogators-shared-ui';
88
+
89
+ <Button variant="primary" size="md" onClick={handleClick}>
90
+ Click Me
91
+ </Button>
92
+
93
+ <Button variant="outline" loading>
94
+ Loading...
95
+ </Button>
96
+ ```
97
+
98
+ #### Input
99
+
100
+ ```tsx
101
+ import { Input } from '@psytor/astrogators-shared-ui';
102
+
103
+ <Input
104
+ label="Email"
105
+ type="email"
106
+ placeholder="Enter your email"
107
+ required
108
+ error={errors.email}
109
+ />
110
+ ```
111
+
112
+ #### Select
113
+
114
+ ```tsx
115
+ import { Select } from '@psytor/astrogators-shared-ui';
116
+
117
+ <Select
118
+ label="Choose Profile"
119
+ options={[
120
+ { value: 'standard', label: 'Standard' },
121
+ { value: 'speed', label: 'Speed Focus' },
122
+ ]}
123
+ placeholder="Select a profile"
124
+ />
125
+ ```
126
+
127
+ ### Display Components
128
+
129
+ #### Card
130
+
131
+ ```tsx
132
+ import { Card } from '@psytor/astrogators-shared-ui';
133
+
134
+ <Card variant="elevated" chamfered chamferSize="md" padding="lg" hoverable>
135
+ <h3>The Mod Ledger</h3>
136
+ <p>Analyze your mods</p>
137
+ </Card>
138
+ ```
139
+
140
+ #### Badge
141
+
142
+ ```tsx
143
+ import { Badge } from '@psytor/astrogators-shared-ui';
144
+
145
+ <Badge variant="success">Active</Badge>
146
+ <Badge variant="warning" size="sm">Beta</Badge>
147
+ ```
148
+
149
+ #### Modal
150
+
151
+ ```tsx
152
+ import { Modal } from '@psytor/astrogators-shared-ui';
153
+
154
+ <Modal
155
+ isOpen={isOpen}
156
+ onClose={() => setIsOpen(false)}
157
+ title="Login"
158
+ size="md"
159
+ >
160
+ {/* Modal content */}
161
+ </Modal>
162
+ ```
163
+
164
+ ### Feedback Components
165
+
166
+ #### Loader
167
+
168
+ ```tsx
169
+ import { Loader } from '@psytor/astrogators-shared-ui';
170
+
171
+ <Loader size="md" variant="spinner" />
172
+ <Loader size="lg" variant="dots" />
173
+ ```
174
+
175
+ ## Authentication
176
+
177
+ ### useAuth Hook
178
+
179
+ ```tsx
180
+ import { useAuth } from '@psytor/astrogators-shared-ui';
181
+
182
+ function MyComponent() {
183
+ const { user, isAuthenticated, login, logout, isLoading } = useAuth();
184
+
185
+ const handleLogin = async () => {
186
+ try {
187
+ await login({
188
+ email: 'user@example.com',
189
+ password: 'password',
190
+ });
191
+ } catch (error) {
192
+ console.error('Login failed:', error);
193
+ }
194
+ };
195
+
196
+ if (isLoading) return <Loader />;
197
+
198
+ return (
199
+ <div>
200
+ {isAuthenticated ? (
201
+ <div>
202
+ <p>Welcome, {user?.username}!</p>
203
+ <button onClick={logout}>Logout</button>
204
+ </div>
205
+ ) : (
206
+ <button onClick={handleLogin}>Login</button>
207
+ )}
208
+ </div>
209
+ );
210
+ }
211
+ ```
212
+
213
+ ## API Client
214
+
215
+ ### Using the API Client
216
+
217
+ ```tsx
218
+ import { apiClient } from '@psytor/astrogators-shared-ui';
219
+
220
+ // GET request
221
+ const data = await apiClient.get('/api/v1/game-data/characters');
222
+
223
+ // POST request
224
+ const result = await apiClient.post('/api/v1/mod-ledger/evaluate/123456789', {
225
+ profile_name: 'standard',
226
+ });
227
+ ```
228
+
229
+ The API client automatically:
230
+ - Injects JWT access token in Authorization header
231
+ - Refreshes expired tokens
232
+ - Retries failed requests after token refresh
233
+ - Calls `onUnauthorized` callback on auth failure
234
+
235
+ ## TypeScript Types
236
+
237
+ All TypeScript types are exported:
238
+
239
+ ```tsx
240
+ import type {
241
+ User,
242
+ LoginRequest,
243
+ LoginResponse,
244
+ ParsedMod,
245
+ ModEvaluation,
246
+ ApiError,
247
+ } from '@psytor/astrogators-shared-ui';
248
+ ```
249
+
250
+ ## CSS Variables
251
+
252
+ Customize the design system by overriding CSS variables:
253
+
254
+ ```css
255
+ :root {
256
+ --color-primary: #1a73e8;
257
+ --color-secondary: #34a853;
258
+ --font-primary: 'Your Font', sans-serif;
259
+ --spacing-md: 1rem;
260
+ --chamfer-size: 8px;
261
+ }
262
+ ```
263
+
264
+ ## Chamfered Boxes
265
+
266
+ Use the chamfered box effect on any element:
267
+
268
+ ```tsx
269
+ <div className="chamfered-box">
270
+ {/* Content with cut corners */}
271
+ </div>
272
+
273
+ <div className="chamfered-box-lg">
274
+ {/* Large chamfered corners */}
275
+ </div>
276
+
277
+ <div className="chamfered-box-sm">
278
+ {/* Small chamfered corners */}
279
+ </div>
280
+ ```
281
+
282
+ Or use the Card component:
283
+
284
+ ```tsx
285
+ <Card chamfered chamferSize="lg">
286
+ {/* Content */}
287
+ </Card>
288
+ ```
289
+
290
+ ## Development
291
+
292
+ ```bash
293
+ # Install dependencies
294
+ npm install
295
+
296
+ # Build library
297
+ npm run build
298
+
299
+ # Type check
300
+ npm run type-check
301
+ ```
302
+
303
+ ## Publishing
304
+
305
+ ```bash
306
+ # Bump version
307
+ npm version patch # or minor, or major
308
+
309
+ # Build
310
+ npm run build
311
+
312
+ # Publish to GitHub Packages
313
+ npm publish
314
+ ```
315
+
316
+ ## License
317
+
318
+ MIT
@@ -0,0 +1,13 @@
1
+ import { default as React } from 'react';
2
+
3
+ export interface BadgeProps {
4
+ children: React.ReactNode;
5
+ variant?: 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info';
6
+ size?: 'sm' | 'md' | 'lg';
7
+ className?: string;
8
+ }
9
+ /**
10
+ * Badge component
11
+ * Small label for status, tags, or categories
12
+ */
13
+ export declare const Badge: React.FC<BadgeProps>;
@@ -0,0 +1,17 @@
1
+ import { default as React } from 'react';
2
+
3
+ export interface CardProps {
4
+ children: React.ReactNode;
5
+ variant?: 'default' | 'elevated' | 'outline';
6
+ chamfered?: boolean;
7
+ chamferSize?: 'sm' | 'md' | 'lg';
8
+ padding?: 'none' | 'sm' | 'md' | 'lg';
9
+ hoverable?: boolean;
10
+ className?: string;
11
+ onClick?: () => void;
12
+ }
13
+ /**
14
+ * Card component
15
+ * Container for content with optional chamfered corners
16
+ */
17
+ export declare const Card: React.FC<CardProps>;
@@ -0,0 +1,16 @@
1
+ import { default as React } from 'react';
2
+
3
+ export interface ModalProps {
4
+ isOpen: boolean;
5
+ onClose: () => void;
6
+ title?: string;
7
+ children: React.ReactNode;
8
+ size?: 'sm' | 'md' | 'lg' | 'xl';
9
+ closeOnOverlayClick?: boolean;
10
+ className?: string;
11
+ }
12
+ /**
13
+ * Modal component
14
+ * Overlay dialog for focused user interaction
15
+ */
16
+ export declare const Modal: React.FC<ModalProps>;
@@ -0,0 +1,6 @@
1
+ export { Card } from './Card';
2
+ export type { CardProps } from './Card';
3
+ export { Badge } from './Badge';
4
+ export type { BadgeProps } from './Badge';
5
+ export { Modal } from './Modal';
6
+ export type { ModalProps } from './Modal';
@@ -0,0 +1,12 @@
1
+ import { default as React } from 'react';
2
+
3
+ export interface LoaderProps {
4
+ size?: 'sm' | 'md' | 'lg';
5
+ variant?: 'spinner' | 'dots';
6
+ className?: string;
7
+ }
8
+ /**
9
+ * Loader component
10
+ * Loading indicator with spinner or dots animation
11
+ */
12
+ export declare const Loader: React.FC<LoaderProps>;
@@ -0,0 +1,2 @@
1
+ export { Loader } from './Loader';
2
+ export type { LoaderProps } from './Loader';
@@ -0,0 +1,12 @@
1
+ import { default as React } from 'react';
2
+
3
+ export interface AllyCodeDropdownProps {
4
+ onAllyCodeSelected?: (allyCode: string | null) => void;
5
+ className?: string;
6
+ }
7
+ /**
8
+ * AllyCodeDropdown component
9
+ * Dropdown for selecting and managing saved ally codes
10
+ * Shows all saved codes with player names, allows quick switching
11
+ */
12
+ export declare const AllyCodeDropdown: React.FC<AllyCodeDropdownProps>;
@@ -0,0 +1,16 @@
1
+ import { default as React } from 'react';
2
+
3
+ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
4
+ children: React.ReactNode;
5
+ variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
6
+ size?: 'sm' | 'md' | 'lg';
7
+ fullWidth?: boolean;
8
+ loading?: boolean;
9
+ disabled?: boolean;
10
+ className?: string;
11
+ }
12
+ /**
13
+ * Button component
14
+ * Reusable button with multiple variants and sizes
15
+ */
16
+ export declare const Button: React.FC<ButtonProps>;
@@ -0,0 +1,14 @@
1
+ import { default as React } from 'react';
2
+
3
+ export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
4
+ label?: string;
5
+ error?: string;
6
+ helperText?: string;
7
+ fullWidth?: boolean;
8
+ className?: string;
9
+ }
10
+ /**
11
+ * Input component
12
+ * Text input field with label, error, and helper text support
13
+ */
14
+ export declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,21 @@
1
+ import { default as React } from 'react';
2
+
3
+ export interface SelectOption {
4
+ value: string;
5
+ label: string;
6
+ disabled?: boolean;
7
+ }
8
+ export interface SelectProps extends React.SelectHTMLAttributes<HTMLSelectElement> {
9
+ label?: string;
10
+ error?: string;
11
+ helperText?: string;
12
+ options: SelectOption[];
13
+ placeholder?: string;
14
+ fullWidth?: boolean;
15
+ className?: string;
16
+ }
17
+ /**
18
+ * Select component
19
+ * Dropdown select with label, error, and helper text support
20
+ */
21
+ export declare const Select: React.ForwardRefExoticComponent<SelectProps & React.RefAttributes<HTMLSelectElement>>;
@@ -0,0 +1,8 @@
1
+ export { Button } from './Button';
2
+ export type { ButtonProps } from './Button';
3
+ export { Input } from './Input';
4
+ export type { InputProps } from './Input';
5
+ export { Select } from './Select';
6
+ export type { SelectProps, SelectOption } from './Select';
7
+ export { AllyCodeDropdown } from './AllyCodeDropdown';
8
+ export type { AllyCodeDropdownProps } from './AllyCodeDropdown';
@@ -0,0 +1,13 @@
1
+ import { default as React } from 'react';
2
+
3
+ export interface ContainerProps {
4
+ children: React.ReactNode;
5
+ maxWidth?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
6
+ padding?: boolean;
7
+ className?: string;
8
+ }
9
+ /**
10
+ * Container component
11
+ * Centers content and applies max-width constraints
12
+ */
13
+ export declare const Container: React.FC<ContainerProps>;
@@ -0,0 +1,11 @@
1
+ import { default as React } from 'react';
2
+
3
+ export interface FooterProps {
4
+ children?: React.ReactNode;
5
+ className?: string;
6
+ }
7
+ /**
8
+ * Footer component
9
+ * Global footer shown across all applications
10
+ */
11
+ export declare const Footer: React.FC<FooterProps>;
@@ -0,0 +1,13 @@
1
+ import { default as React } from 'react';
2
+
3
+ export interface TopBarProps {
4
+ logo?: React.ReactNode;
5
+ leftContent?: React.ReactNode;
6
+ rightContent?: React.ReactNode;
7
+ className?: string;
8
+ }
9
+ /**
10
+ * TopBar component
11
+ * Global navigation bar shown across all applications
12
+ */
13
+ export declare const TopBar: React.FC<TopBarProps>;
@@ -0,0 +1,6 @@
1
+ export { Container } from './Container';
2
+ export type { ContainerProps } from './Container';
3
+ export { TopBar } from './TopBar';
4
+ export type { TopBarProps } from './TopBar';
5
+ export { Footer } from './Footer';
6
+ export type { FooterProps } from './Footer';
@@ -0,0 +1,40 @@
1
+ import { default as React } from 'react';
2
+ import { User, LoginRequest, RegisterRequest, ForgotPasswordRequest, ResetPasswordRequest, ResendVerificationRequest, AllyCode, AllyCodeMigrationPrompt } from '../types';
3
+ import { StoredAllyCode } from '../services/allyCodeStorage';
4
+
5
+ export interface AuthContextValue {
6
+ user: User | null;
7
+ isAuthenticated: boolean;
8
+ isLoading: boolean;
9
+ login: (credentials: LoginRequest) => Promise<void>;
10
+ register: (data: RegisterRequest) => Promise<void>;
11
+ logout: () => void;
12
+ refreshUser: () => Promise<void>;
13
+ forgotPassword: (data: ForgotPasswordRequest) => Promise<string>;
14
+ resetPassword: (data: ResetPasswordRequest) => Promise<string>;
15
+ resendVerification: (data: ResendVerificationRequest) => Promise<string>;
16
+ allyCodes: AllyCode[] | StoredAllyCode[];
17
+ selectedAllyCode: string | null;
18
+ isLoadingAllyCodes: boolean;
19
+ fetchAllyCodes: () => Promise<void>;
20
+ addAllyCode: (allyCode: string) => Promise<void>;
21
+ removeAllyCode: (allyCodeId: number | string) => Promise<void>;
22
+ selectAllyCode: (allyCode: string | null) => void;
23
+ updateAllyCodeLastUsed: (allyCodeId: number | string) => Promise<void>;
24
+ migrationPrompt: AllyCodeMigrationPrompt;
25
+ dismissMigrationPrompt: () => void;
26
+ migrateLocalStorageCodes: () => Promise<void>;
27
+ }
28
+ export interface AuthProviderProps {
29
+ children: React.ReactNode;
30
+ }
31
+ /**
32
+ * AuthProvider
33
+ * Provides authentication state and methods to the entire application
34
+ */
35
+ export declare const AuthProvider: React.FC<AuthProviderProps>;
36
+ /**
37
+ * useAuth hook
38
+ * Access authentication state and methods
39
+ */
40
+ export declare const useAuth: () => AuthContextValue;
@@ -0,0 +1,18 @@
1
+
2
+ export { Container, TopBar, Footer } from './components/layout';
3
+ export type { ContainerProps, TopBarProps, FooterProps } from './components/layout';
4
+ export { Button, Input, Select, AllyCodeDropdown } from './components/forms';
5
+ export type { ButtonProps, InputProps, SelectProps, SelectOption, AllyCodeDropdownProps } from './components/forms';
6
+ export { Card, Badge, Modal } from './components/display';
7
+ export type { CardProps, BadgeProps, ModalProps } from './components/display';
8
+ export { Loader } from './components/feedback';
9
+ export type { LoaderProps } from './components/feedback';
10
+ export { AuthProvider, useAuth } from './contexts/AuthContext';
11
+ export type { AuthContextValue, AuthProviderProps } from './contexts/AuthContext';
12
+ export { ApiClient, apiClient, initializeApiClient } from './services/api';
13
+ export type { ApiClientConfig } from './services/api';
14
+ export { getAccessToken, setAccessToken, removeAccessToken, getRefreshToken, setRefreshToken, removeRefreshToken, setTokens, clearTokens, isAuthenticated, } from './services/auth';
15
+ export { getAllyCodesFromStorage, saveAllyCodeToStorage, removeAllyCodeFromStorage, updateAllyCodeLastUsed, getSelectedAllyCode, setSelectedAllyCode, clearAllyCodes, } from './services/allyCodeStorage';
16
+ export type { StoredAllyCode } from './services/allyCodeStorage';
17
+ export { formatAllyCode, unformatAllyCode } from './utils/formatAllyCode';
18
+ export type { User, LoginRequest, LoginResponse, RegisterRequest, RegisterResponse, RefreshTokenRequest, RefreshTokenResponse, ForgotPasswordRequest, ForgotPasswordResponse, ResetPasswordRequest, ResetPasswordResponse, VerifyEmailRequest, VerifyEmailResponse, ResendVerificationRequest, ResendVerificationResponse, AllyCode, AllyCodeCreate, AllyCodeListResponse, AllyCodeMigrationPrompt, ApiResponse, ApiError, PaginatedResponse, ModStat, ParsedMod, ModListResponse, EvaluationScores, ModEvaluation, EvaluationRequest, EvaluationResponse, } from './types';