titaned-frontend-library 1.0.1

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 (77) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/MEILISEARCH_INTEGRATION.md +261 -0
  3. package/README.md +236 -0
  4. package/SEARCH_INTEGRATION_README.md +208 -0
  5. package/dist/App.d.ts +3 -0
  6. package/dist/components/ButtonComponent.scss +3 -0
  7. package/dist/components/Footer.scss +87 -0
  8. package/dist/components/MainHeader.scss +915 -0
  9. package/dist/components/Sidebar.scss +170 -0
  10. package/dist/components/UserMenu.scss +0 -0
  11. package/dist/components/common/Button/ButtonComponent.d.ts +4 -0
  12. package/dist/components/common/Footer/Footer.d.ts +4 -0
  13. package/dist/components/common/Header/MainHeader.d.ts +4 -0
  14. package/dist/components/common/Header/SimpleSearchContext.d.ts +36 -0
  15. package/dist/components/common/NavDropdownMenu/NavDropdownMenu.d.ts +11 -0
  16. package/dist/components/common/SearchHighlight.d.ts +11 -0
  17. package/dist/components/common/SearchResultItem.d.ts +12 -0
  18. package/dist/components/common/Sidebar/Sidebar.d.ts +12 -0
  19. package/dist/components/common/UserMenu/UserMenu.d.ts +15 -0
  20. package/dist/components/common/menu/MenuComponent.d.ts +4 -0
  21. package/dist/contexts/SidebarContext.d.ts +6 -0
  22. package/dist/examples/MeiliSearchUsageExample.d.ts +3 -0
  23. package/dist/hooks/useIsMobileSize.d.ts +2 -0
  24. package/dist/hooks/useSidebar.d.ts +1 -0
  25. package/dist/index.cjs +27 -0
  26. package/dist/index.css +1 -0
  27. package/dist/index.d.ts +8 -0
  28. package/dist/index.js +1867 -0
  29. package/dist/interfaces/components.d.ts +181 -0
  30. package/dist/main.d.ts +0 -0
  31. package/dist/providers/SidebarProvider.d.ts +4 -0
  32. package/dist/services/meiliSearchService.d.ts +47 -0
  33. package/dist/utils/searchNavigation.d.ts +43 -0
  34. package/dist/vite.svg +1 -0
  35. package/eslint.config.js +28 -0
  36. package/index.html +13 -0
  37. package/next.config.ts +35 -0
  38. package/package.json +53 -0
  39. package/public/vite.svg +1 -0
  40. package/src/@edx-frontend-platform-i18n.d.ts +1 -0
  41. package/src/App.css +66 -0
  42. package/src/App.tsx +54 -0
  43. package/src/assets/react.svg +1 -0
  44. package/src/components/common/Button/ButtonComponent.scss +3 -0
  45. package/src/components/common/Button/ButtonComponent.tsx +34 -0
  46. package/src/components/common/Footer/Footer.scss +87 -0
  47. package/src/components/common/Footer/Footer.tsx +53 -0
  48. package/src/components/common/Header/MainHeader.scss +915 -0
  49. package/src/components/common/Header/MainHeader.tsx +701 -0
  50. package/src/components/common/Header/SimpleSearchContext.tsx +194 -0
  51. package/src/components/common/NavDropdownMenu/NavDropdownMenu.tsx +58 -0
  52. package/src/components/common/SearchHighlight.tsx +64 -0
  53. package/src/components/common/SearchResultItem.tsx +162 -0
  54. package/src/components/common/Sidebar/Sidebar.scss +170 -0
  55. package/src/components/common/Sidebar/Sidebar.tsx +137 -0
  56. package/src/components/common/UserMenu/UserMenu.scss +0 -0
  57. package/src/components/common/UserMenu/UserMenu.tsx +73 -0
  58. package/src/components/common/menu/MenuComponent.module.css +12 -0
  59. package/src/components/common/menu/MenuComponent.tsx +37 -0
  60. package/src/contexts/SidebarContext.tsx +9 -0
  61. package/src/declarations.d.ts +10 -0
  62. package/src/examples/MeiliSearchUsageExample.tsx +102 -0
  63. package/src/hooks/useIsMobileSize.ts +19 -0
  64. package/src/hooks/useSidebar.ts +10 -0
  65. package/src/index.css +68 -0
  66. package/src/index.ts +21 -0
  67. package/src/interfaces/components.ts +172 -0
  68. package/src/main.tsx +11 -0
  69. package/src/providers/SidebarProvider.tsx +20 -0
  70. package/src/services/meiliSearchService.ts +233 -0
  71. package/src/styles/global-overrides.scss +1131 -0
  72. package/src/utils/searchNavigation.ts +140 -0
  73. package/src/vite-env.d.ts +1 -0
  74. package/tsconfig.app.json +26 -0
  75. package/tsconfig.json +17 -0
  76. package/tsconfig.node.json +24 -0
  77. package/vite.config.ts +34 -0
@@ -0,0 +1,20 @@
1
+ import React, { useState, ReactNode } from 'react';
2
+ import { SidebarContext } from '../contexts/SidebarContext';
3
+
4
+ export const SidebarProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
5
+ const [isCollapsed, setIsCollapsed] = useState(false);
6
+
7
+ const toggleSidebar = () => {
8
+ setIsCollapsed(prev => !prev);
9
+ };
10
+
11
+ const setSidebarCollapsed = (collapsed: boolean) => {
12
+ setIsCollapsed(collapsed);
13
+ };
14
+
15
+ return (
16
+ <SidebarContext.Provider value={{ isCollapsed, toggleSidebar, setSidebarCollapsed }}>
17
+ {children}
18
+ </SidebarContext.Provider>
19
+ );
20
+ };
@@ -0,0 +1,233 @@
1
+ import { MeiliSearchConfig, MeiliSearchResponse, MeiliSearchResult } from '../interfaces/components';
2
+
3
+ // Interface for MeiliSearch client headers
4
+ export interface MeiliSearchClientHeaders {
5
+ 'Content-Type': string;
6
+ 'Authorization': string;
7
+ 'X-Meilisearch-Client': string;
8
+ }
9
+
10
+ export interface SearchOptions {
11
+ limit?: number;
12
+ offset?: number;
13
+ filter?: string[];
14
+ attributesToHighlight?: string[];
15
+ attributesToCrop?: string[];
16
+ sort?: string[];
17
+ }
18
+
19
+ export interface SearchResult {
20
+ hits: MeiliSearchResult[];
21
+ totalHits: number;
22
+ facets?: {
23
+ block_type?: Record<string, number>;
24
+ 'content.problem_types'?: Record<string, number>;
25
+ };
26
+ processingTimeMs: number;
27
+ }
28
+
29
+ export class MeiliSearchService {
30
+ private config: MeiliSearchConfig;
31
+ private headers: MeiliSearchClientHeaders;
32
+
33
+ constructor(config: MeiliSearchConfig) {
34
+ // Validate required configuration
35
+ if (!config.host) {
36
+ throw new Error('MeiliSearch host is required');
37
+ }
38
+ if (!config.apiKey) {
39
+ throw new Error('MeiliSearch apiKey is required');
40
+ }
41
+ if (!config.indexName) {
42
+ throw new Error('MeiliSearch indexName is required');
43
+ }
44
+
45
+ this.config = config;
46
+ this.headers = {
47
+ 'Content-Type': 'application/json',
48
+ 'Authorization': `Bearer ${config.apiKey}`,
49
+ 'X-Meilisearch-Client': 'Meilisearch JavaScript (v0.41.0)'
50
+ };
51
+
52
+ }
53
+
54
+ /**
55
+ * Update headers from MeiliSearch client object
56
+ * This allows you to use the exact headers from your console logs
57
+ */
58
+ updateHeadersFromClient(client: any) {
59
+ if (client && client.httpRequest && client.httpRequest.headers) {
60
+ this.headers = {
61
+ 'Content-Type': client.httpRequest.headers['Content-Type'] || 'application/json',
62
+ 'Authorization': client.httpRequest.headers['Authorization'] || `Bearer ${this.config.apiKey}`,
63
+ 'X-Meilisearch-Client': client.httpRequest.headers['X-Meilisearch-Client'] || 'Meilisearch JavaScript (v0.41.0)'
64
+ };
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Performs a search using MeiliSearch multi-search API
70
+ * This matches the exact payload structure from your console logs
71
+ */
72
+ async search(
73
+ query: string,
74
+ options: SearchOptions = {}
75
+ ): Promise<SearchResult> {
76
+ const {
77
+ limit = 20,
78
+ offset = 0,
79
+ filter = ['type = "course_block"'],
80
+ attributesToHighlight = ['display_name', 'description', 'published'],
81
+ attributesToCrop = ['description', 'published'],
82
+ sort = []
83
+ } = options;
84
+
85
+ // Validate configuration
86
+ if (!this.config.indexName) {
87
+ throw new Error('MeiliSearch indexName is required');
88
+ }
89
+
90
+ // Construct the exact payload structure from your console logs
91
+ const payload = {
92
+ queries: [
93
+ {
94
+ indexUid: this.config.indexName,
95
+ q: query,
96
+ filter: [[], ...filter],
97
+ attributesToHighlight,
98
+ highlightPreTag: '__meili-highlight__',
99
+ highlightPostTag: '__/meili-highlight__',
100
+ attributesToCrop,
101
+ sort,
102
+ offset,
103
+ limit
104
+ },
105
+ {
106
+ indexUid: this.config.indexName,
107
+ facets: ['block_type', 'content.problem_types'],
108
+ filter: filter,
109
+ limit: 0
110
+ }
111
+ ]
112
+ };
113
+
114
+
115
+ // Use the configured headers
116
+ const headers = { ...this.headers };
117
+
118
+ try {
119
+ const response = await fetch(`${this.config.host}/multi-search`, {
120
+ method: 'POST',
121
+ headers,
122
+ body: JSON.stringify(payload)
123
+ });
124
+
125
+ if (!response.ok) {
126
+ throw new Error(`MeiliSearch API error: ${response.status} ${response.statusText}`);
127
+ }
128
+
129
+ const data: MeiliSearchResponse = await response.json();
130
+
131
+ // Extract search results from the first query
132
+ const searchResult = data.results[0];
133
+ const facetResult = data.results[1];
134
+
135
+ return {
136
+ hits: searchResult.hits || [],
137
+ totalHits: searchResult.estimatedTotalHits || 0,
138
+ facets: facetResult.facetDistribution,
139
+ processingTimeMs: searchResult.processingTimeMs
140
+ };
141
+
142
+ } catch (error) {
143
+ console.error('MeiliSearch service error:', error);
144
+ throw new Error(`Search failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Performs a simple search without facets (for basic search functionality)
150
+ */
151
+ async simpleSearch(
152
+ query: string,
153
+ options: SearchOptions = {}
154
+ ): Promise<SearchResult> {
155
+ const {
156
+ limit = 20,
157
+ offset = 0,
158
+ filter = ['type = "course_block"'],
159
+ attributesToHighlight = ['display_name', 'description', 'published'],
160
+ attributesToCrop = ['description', 'published'],
161
+ sort = []
162
+ } = options;
163
+
164
+ const payload = {
165
+ queries: [
166
+ {
167
+ indexUid: this.config.indexName,
168
+ q: query,
169
+ filter: [[], ...filter],
170
+ attributesToHighlight,
171
+ highlightPreTag: '__meili-highlight__',
172
+ highlightPostTag: '__/meili-highlight__',
173
+ attributesToCrop,
174
+ sort,
175
+ offset,
176
+ limit
177
+ }
178
+ ]
179
+ };
180
+
181
+ // Use the configured headers
182
+ const headers = { ...this.headers };
183
+
184
+ try {
185
+ const response = await fetch(`${this.config.host}/multi-search`, {
186
+ method: 'POST',
187
+ headers,
188
+ body: JSON.stringify(payload)
189
+ });
190
+
191
+ if (!response.ok) {
192
+ throw new Error(`MeiliSearch API error: ${response.status} ${response.statusText}`);
193
+ }
194
+
195
+ const data: MeiliSearchResponse = await response.json();
196
+ const searchResult = data.results[0];
197
+
198
+ return {
199
+ hits: searchResult.hits || [],
200
+ totalHits: searchResult.estimatedTotalHits || 0,
201
+ processingTimeMs: searchResult.processingTimeMs
202
+ };
203
+
204
+ } catch (error) {
205
+ console.error('MeiliSearch service error:', error);
206
+ throw new Error(`Search failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Check if the MeiliSearch service is available
212
+ */
213
+ async checkConnection(): Promise<boolean> {
214
+ try {
215
+ const response = await fetch(`${this.config.host}/health`, {
216
+ method: 'GET',
217
+ headers: {
218
+ 'Authorization': `Bearer ${this.config.apiKey}`,
219
+ 'X-Meilisearch-Client': 'Meilisearch JavaScript (v0.41.0)'
220
+ }
221
+ });
222
+ return response.ok;
223
+ } catch (error) {
224
+ console.error('MeiliSearch connection check failed:', error);
225
+ return false;
226
+ }
227
+ }
228
+ }
229
+
230
+ // Factory function to create a MeiliSearch service instance
231
+ export const createMeiliSearchService = (config: MeiliSearchConfig): MeiliSearchService => {
232
+ return new MeiliSearchService(config);
233
+ };