@seekora-ai/ui-sdk-vanilla 0.2.11 → 0.2.13

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/dist/index.js CHANGED
@@ -2,6 +2,199 @@
2
2
 
3
3
  var uiSdkCore = require('@seekora-ai/ui-sdk-core');
4
4
 
5
+ /**
6
+ * RatingDisplay - Vanilla JS
7
+ *
8
+ * Star rating display with review count and multiple variants
9
+ */
10
+ const sizeMap = {
11
+ small: 14,
12
+ medium: 18,
13
+ large: 24,
14
+ };
15
+ const fontSizeMap = {
16
+ small: '0.75rem',
17
+ medium: '0.875rem',
18
+ large: '1rem',
19
+ };
20
+ class RatingDisplay {
21
+ constructor(containerOrSelector, options) {
22
+ this.hoverRating = null;
23
+ this.container = typeof containerOrSelector === 'string'
24
+ ? document.querySelector(containerOrSelector)
25
+ : containerOrSelector;
26
+ if (!this.container) {
27
+ throw new Error('RatingDisplay: Container element not found');
28
+ }
29
+ this.options = {
30
+ variant: 'compact',
31
+ size: 'medium',
32
+ maxRating: 5,
33
+ showNumeric: false,
34
+ showHalfStars: true,
35
+ interactive: false,
36
+ starColor: '#f59e0b',
37
+ emptyStarColor: '#d1d5db',
38
+ textColor: 'var(--seekora-text-secondary, #6b7280)',
39
+ showReviewCount: true,
40
+ onRatingChange: () => { },
41
+ reviewCountFormat: this.defaultReviewCountFormat,
42
+ className: '',
43
+ ...options,
44
+ };
45
+ this.render();
46
+ }
47
+ defaultReviewCountFormat(count) {
48
+ if (count >= 1000000)
49
+ return `${(count / 1000000).toFixed(1)}M`;
50
+ if (count >= 1000)
51
+ return `${(count / 1000).toFixed(1)}K`;
52
+ return count.toString();
53
+ }
54
+ createStarSVG(filled, half, size) {
55
+ const { starColor, emptyStarColor } = this.options;
56
+ if (half) {
57
+ return `
58
+ <svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
59
+ <defs>
60
+ <linearGradient id="half-fill-${Math.random()}">
61
+ <stop offset="50%" stop-color="${starColor}" />
62
+ <stop offset="50%" stop-color="${emptyStarColor}" />
63
+ </linearGradient>
64
+ </defs>
65
+ <path
66
+ d="M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z"
67
+ fill="url(#half-fill-${Math.random()})"
68
+ stroke="${starColor}"
69
+ stroke-width="1"
70
+ />
71
+ </svg>
72
+ `;
73
+ }
74
+ return `
75
+ <svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="${filled ? starColor : 'none'}" xmlns="http://www.w3.org/2000/svg">
76
+ <path
77
+ d="M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z"
78
+ stroke="${filled ? starColor : emptyStarColor}"
79
+ stroke-width="1.5"
80
+ stroke-linecap="round"
81
+ stroke-linejoin="round"
82
+ />
83
+ </svg>
84
+ `;
85
+ }
86
+ renderStars() {
87
+ const { rating, maxRating, showHalfStars, interactive } = this.options;
88
+ const displayRating = interactive && this.hoverRating !== null ? this.hoverRating : rating;
89
+ const starSize = sizeMap[this.options.size];
90
+ let starsHTML = '';
91
+ for (let i = 1; i <= maxRating; i++) {
92
+ const filled = i <= Math.floor(displayRating);
93
+ const half = showHalfStars &&
94
+ i === Math.ceil(displayRating) &&
95
+ displayRating % 1 >= 0.25 &&
96
+ displayRating % 1 < 0.75;
97
+ const classes = `seekora-rating-star ${filled ? 'seekora-rating-star--filled' : 'seekora-rating-star--empty'} ${interactive ? 'seekora-rating-star--interactive' : ''}`;
98
+ starsHTML += `
99
+ <span
100
+ class="${classes}"
101
+ data-rating="${i}"
102
+ style="display: inline-block; width: ${starSize}px; height: ${starSize}px; cursor: ${interactive ? 'pointer' : 'default'};"
103
+ >
104
+ ${this.createStarSVG(filled, half, starSize)}
105
+ </span>
106
+ `;
107
+ }
108
+ return `<div style="display: inline-flex; align-items: center; gap: 2px;">${starsHTML}</div>`;
109
+ }
110
+ render() {
111
+ const { variant, rating, reviewCount, showNumeric, showReviewCount, textColor, maxRating, className } = this.options;
112
+ const fontSize = fontSizeMap[this.options.size];
113
+ const clampedRating = Math.max(0, Math.min(maxRating, rating));
114
+ let html = '';
115
+ if (variant === 'stars-only') {
116
+ html = `
117
+ <div class="seekora-rating-display seekora-rating-display--stars-only ${className}"
118
+ style="display: inline-flex; align-items: center; gap: 2px;">
119
+ ${this.renderStars()}
120
+ </div>
121
+ `;
122
+ }
123
+ else if (variant === 'compact') {
124
+ html = `
125
+ <div class="seekora-rating-display seekora-rating-display--compact ${className}"
126
+ style="display: inline-flex; align-items: center; gap: 4px; font-size: ${fontSize};">
127
+ ${this.renderStars()}
128
+ ${showNumeric ? `<span class="seekora-rating-numeric" style="font-weight: 600; color: ${textColor};">${clampedRating.toFixed(1)}</span>` : ''}
129
+ ${showReviewCount && reviewCount != null && reviewCount > 0 ? `<span class="seekora-rating-review-count" style="color: ${textColor};">(${this.options.reviewCountFormat(reviewCount)})</span>` : ''}
130
+ </div>
131
+ `;
132
+ }
133
+ else if (variant === 'inline') {
134
+ html = `
135
+ <div class="seekora-rating-display seekora-rating-display--inline ${className}"
136
+ style="display: inline-flex; align-items: center; gap: 6px; font-size: ${fontSize};">
137
+ <span class="seekora-rating-numeric" style="font-weight: 600; color: var(--seekora-text-primary, #111827);">${clampedRating.toFixed(1)}</span>
138
+ ${this.renderStars()}
139
+ ${showReviewCount && reviewCount != null && reviewCount > 0 ? `<span class="seekora-rating-review-count" style="color: ${textColor};">(${this.options.reviewCountFormat(reviewCount)})</span>` : ''}
140
+ </div>
141
+ `;
142
+ }
143
+ else if (variant === 'full') {
144
+ html = `
145
+ <div class="seekora-rating-display seekora-rating-display--full ${className}"
146
+ style="display: flex; flex-direction: column; gap: 4px; font-size: ${fontSize};">
147
+ <div style="display: flex; align-items: center; gap: 6px;">
148
+ ${this.renderStars()}
149
+ <span class="seekora-rating-numeric" style="font-weight: 600; color: var(--seekora-text-primary, #111827);">${clampedRating.toFixed(1)}</span>
150
+ <span class="seekora-rating-max" style="color: ${textColor};">/ ${maxRating}</span>
151
+ </div>
152
+ ${showReviewCount && reviewCount != null && reviewCount > 0 ? `
153
+ <span class="seekora-rating-review-text" style="font-size: 0.875em; color: ${textColor};">
154
+ Based on ${this.options.reviewCountFormat(reviewCount)} ${reviewCount === 1 ? 'review' : 'reviews'}
155
+ </span>
156
+ ` : ''}
157
+ </div>
158
+ `;
159
+ }
160
+ this.container.innerHTML = html;
161
+ if (this.options.interactive) {
162
+ this.attachEventListeners();
163
+ }
164
+ }
165
+ attachEventListeners() {
166
+ const stars = this.container.querySelectorAll('.seekora-rating-star');
167
+ stars.forEach((star) => {
168
+ star.addEventListener('mouseenter', () => {
169
+ const rating = parseInt(star.dataset.rating || '0');
170
+ this.hoverRating = rating;
171
+ this.render();
172
+ });
173
+ star.addEventListener('click', () => {
174
+ const rating = parseInt(star.dataset.rating || '0');
175
+ this.hoverRating = null;
176
+ this.options.onRatingChange(rating);
177
+ this.render();
178
+ });
179
+ });
180
+ this.container.addEventListener('mouseleave', () => {
181
+ this.hoverRating = null;
182
+ this.render();
183
+ });
184
+ }
185
+ update(options) {
186
+ this.options = { ...this.options, ...options };
187
+ this.render();
188
+ }
189
+ destroy() {
190
+ this.container.innerHTML = '';
191
+ }
192
+ }
193
+ // Factory function for easier usage
194
+ function createRatingDisplay(container, options) {
195
+ return new RatingDisplay(container, options);
196
+ }
197
+
5
198
  /**
6
199
  * Default Theme
7
200
  */
@@ -4069,6 +4262,7 @@ exports.InfiniteHits = InfiniteHits;
4069
4262
  exports.Pagination = Pagination;
4070
4263
  exports.QuerySuggestions = QuerySuggestions;
4071
4264
  exports.RangeInput = RangeInput;
4265
+ exports.RatingDisplay = RatingDisplay;
4072
4266
  exports.SearchBar = SearchBar;
4073
4267
  exports.SearchLayout = SearchLayout;
4074
4268
  exports.SearchManager = SearchManager;
@@ -4076,6 +4270,7 @@ exports.SearchProvider = SearchProvider;
4076
4270
  exports.SearchResults = SearchResults;
4077
4271
  exports.SortBy = SortBy;
4078
4272
  exports.Stats = Stats;
4273
+ exports.createRatingDisplay = createRatingDisplay;
4079
4274
  exports.createTheme = createTheme;
4080
4275
  exports.darkTheme = darkTheme;
4081
4276
  exports.defaultTheme = defaultTheme;