kyd-shared-badge 0.3.106 → 0.3.108
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/package.json +1 -1
- package/src/components/SkillsBubble.tsx +129 -122
package/package.json
CHANGED
|
@@ -251,136 +251,143 @@ export default function SkillsBubble({ skillsCategoryRadar, skillsByCategory, sk
|
|
|
251
251
|
|
|
252
252
|
const columnComponent = (entry: { label: string; years?: number; presence?: string; presenceTypes?: Array<'certified' | 'observed' | 'self-reported'>; sources?: string[] } | '', idx: number, isLeft: boolean) => {
|
|
253
253
|
return (
|
|
254
|
-
<div key={idx} className="flex
|
|
255
|
-
<div className="flex flex-
|
|
256
|
-
<
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
254
|
+
<div key={idx} className="flex justify-between gap-3 min-w-0">
|
|
255
|
+
<div className="flex flex-row w-1/2 items-start gap-8">
|
|
256
|
+
{entry ? <span className="shrink-0 opacity-70 text-base items-center flex h-full">{idx + (isLeft ? 1 : 6)}.</span> : <span className="opacity-0 whitespace-nowrap">\u00A0</span>}
|
|
257
|
+
|
|
258
|
+
<div className="flex flex-col min-w-0 justify-center leading-tight">
|
|
259
|
+
<div className="flex items-center gap-2 min-w-0 text-lg text-[var(--text-main)]">
|
|
260
|
+
{entry && typeof entry !== 'string' ? (
|
|
261
|
+
<span className="truncate font-semibold" title={entry.label}>
|
|
262
|
+
{entry.label}
|
|
263
|
+
{/* Evidence count bullet */}
|
|
264
|
+
{(() => {
|
|
265
|
+
console.log('entry', entry);
|
|
266
|
+
const meta = skillsMeta?.[entry.label];
|
|
267
|
+
const count = Number((meta as any)?.evidenceCount || 0);
|
|
268
|
+
return count > 0 ? (
|
|
269
|
+
<>
|
|
270
|
+
{' '}<span className="opacity-60">•</span>{' '}
|
|
271
|
+
<span
|
|
272
|
+
className="cursor-help opacity-80"
|
|
273
|
+
onMouseEnter={(e) =>
|
|
274
|
+
showLegendTooltipAt(
|
|
275
|
+
e.currentTarget,
|
|
276
|
+
'Evidence count',
|
|
277
|
+
'Total number of sources across observed, certified, and self-reported evidence for this skill.'
|
|
278
|
+
)
|
|
279
|
+
}
|
|
280
|
+
onMouseLeave={hideLegendTooltip}
|
|
281
|
+
>
|
|
282
|
+
{count}
|
|
283
|
+
</span>
|
|
284
|
+
</>
|
|
285
|
+
) : null;
|
|
286
|
+
})()}
|
|
287
|
+
</span>
|
|
288
|
+
) : (
|
|
289
|
+
<span className="truncate">{typeof entry === 'string' ? entry : '\u00A0'}</span>
|
|
290
|
+
)}
|
|
291
|
+
</div>
|
|
292
|
+
<span className="text-xs text-[var(--text-secondary)] flex flex-wrap items-center gap-1">
|
|
293
|
+
{entry && typeof entry !== 'string' ? (
|
|
294
|
+
<>
|
|
295
|
+
<span
|
|
296
|
+
className="cursor-help"
|
|
297
|
+
onMouseEnter={(e) => showLegendTooltipAt(e.currentTarget, 'Sources', 'The source where we observed this skill.')}
|
|
298
|
+
onMouseLeave={hideLegendTooltip}
|
|
299
|
+
>
|
|
300
|
+
Sources:
|
|
301
|
+
</span>
|
|
302
|
+
{Array.isArray((entry as any).sources) && (entry as any).sources.length > 0 ? (
|
|
303
|
+
(() => {
|
|
304
|
+
const sourceProviders: string[] = ((entry as any).sources as string[]).map((src: string) => {
|
|
305
|
+
const str = String(src);
|
|
306
|
+
let provider = str.split(':')[0] || '';
|
|
307
|
+
if (!provider || provider === str) {
|
|
308
|
+
// If split(':')[0] didn't find a delimiter or provider (i.e., no ':'), try split('.')
|
|
309
|
+
provider = str.split('.')[0] || '';
|
|
276
310
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
311
|
+
return provider.toLowerCase();
|
|
312
|
+
});
|
|
313
|
+
const uniqueProviders = Array.from(new Set<string>(sourceProviders));
|
|
314
|
+
const filteredProviders = uniqueProviders.filter((provider) =>
|
|
315
|
+
providers.includes(provider.toLowerCase())
|
|
316
|
+
);
|
|
317
|
+
return filteredProviders.map((provider) => (
|
|
318
|
+
<span
|
|
319
|
+
key={provider}
|
|
320
|
+
onMouseEnter={(e) =>
|
|
321
|
+
showLegendTooltipAt(
|
|
322
|
+
e.currentTarget,
|
|
323
|
+
undefined,
|
|
324
|
+
getProviderDisplayName(provider)
|
|
325
|
+
)
|
|
326
|
+
}
|
|
327
|
+
onMouseLeave={hideLegendTooltip}
|
|
328
|
+
className="inline-flex items-center"
|
|
329
|
+
>
|
|
330
|
+
<ProviderIcon name={provider} />
|
|
331
|
+
</span>
|
|
332
|
+
));
|
|
333
|
+
})()
|
|
334
|
+
) : null}
|
|
335
|
+
</>
|
|
336
|
+
) : (
|
|
337
|
+
<span className="opacity-0 whitespace-nowrap">'\u00A0'</span>
|
|
338
|
+
)}
|
|
339
|
+
</span>
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
<div
|
|
343
|
+
className="flex flex-col items-end leading-tight h-full justify-start text-base"
|
|
344
|
+
>
|
|
345
|
+
<div className="pb-1">
|
|
346
|
+
{entry && typeof entry !== 'string' && entry.years ? (
|
|
347
|
+
<span
|
|
348
|
+
className="whitespace-nowrap text-[var(--text-secondary)] cursor-help"
|
|
349
|
+
onMouseEnter={(e) => {
|
|
350
|
+
const copy = experienceLegendTooltip();
|
|
351
|
+
showLegendTooltipAt(e.currentTarget, copy.title, copy.body);
|
|
352
|
+
}}
|
|
353
|
+
onMouseLeave={hideLegendTooltip}
|
|
354
|
+
>
|
|
355
|
+
{`${entry.years} Years`}
|
|
284
356
|
</span>
|
|
285
357
|
) : (
|
|
286
|
-
<span className="
|
|
358
|
+
<span className="opacity-0 whitespace-nowrap text-[var(--text-secondary)]">0 Years</span>
|
|
287
359
|
)}
|
|
288
360
|
</div>
|
|
289
|
-
<
|
|
290
|
-
{
|
|
291
|
-
|
|
292
|
-
<span
|
|
293
|
-
className="cursor-help"
|
|
294
|
-
onMouseEnter={(e) => showLegendTooltipAt(e.currentTarget, 'Sources', 'The source where we observed this skill.')}
|
|
295
|
-
onMouseLeave={hideLegendTooltip}
|
|
296
|
-
>
|
|
297
|
-
Sources:
|
|
298
|
-
</span>
|
|
299
|
-
{Array.isArray((entry as any).sources) && (entry as any).sources.length > 0 ? (
|
|
300
|
-
(() => {
|
|
301
|
-
const sourceProviders: string[] = ((entry as any).sources as string[]).map((src: string) => {
|
|
302
|
-
const str = String(src);
|
|
303
|
-
let provider = str.split(':')[0] || '';
|
|
304
|
-
if (!provider || provider === str) {
|
|
305
|
-
// If split(':')[0] didn't find a delimiter or provider (i.e., no ':'), try split('.')
|
|
306
|
-
provider = str.split('.')[0] || '';
|
|
307
|
-
}
|
|
308
|
-
return provider.toLowerCase();
|
|
309
|
-
});
|
|
310
|
-
const uniqueProviders = Array.from(new Set<string>(sourceProviders));
|
|
311
|
-
const filteredProviders = uniqueProviders.filter((provider) =>
|
|
312
|
-
providers.includes(provider.toLowerCase())
|
|
313
|
-
);
|
|
314
|
-
return filteredProviders.map((provider) => (
|
|
315
|
-
<span
|
|
316
|
-
key={provider}
|
|
317
|
-
onMouseEnter={(e) =>
|
|
318
|
-
showLegendTooltipAt(
|
|
319
|
-
e.currentTarget,
|
|
320
|
-
undefined,
|
|
321
|
-
getProviderDisplayName(provider)
|
|
322
|
-
)
|
|
323
|
-
}
|
|
324
|
-
onMouseLeave={hideLegendTooltip}
|
|
325
|
-
className="inline-flex items-center"
|
|
326
|
-
>
|
|
327
|
-
<ProviderIcon name={provider} />
|
|
328
|
-
</span>
|
|
329
|
-
));
|
|
330
|
-
})()
|
|
331
|
-
) : null}
|
|
332
|
-
</>
|
|
333
|
-
) : (
|
|
334
|
-
<span className="opacity-0 whitespace-nowrap">'\u00A0'</span>
|
|
335
|
-
)}
|
|
336
|
-
</span>
|
|
337
|
-
</div>
|
|
338
|
-
{entry && typeof entry !== 'string' ? (
|
|
339
|
-
<div
|
|
340
|
-
className="flex flex-col items-end leading-tight h-full justify-start text-base"
|
|
341
|
-
>
|
|
342
|
-
<div className="pb-1">
|
|
343
|
-
{entry.years ? (
|
|
344
|
-
<span
|
|
345
|
-
className="whitespace-nowrap text-[var(--text-secondary)] cursor-help"
|
|
346
|
-
onMouseEnter={(e) => {
|
|
347
|
-
const copy = experienceLegendTooltip();
|
|
348
|
-
showLegendTooltipAt(e.currentTarget, copy.title, copy.body);
|
|
349
|
-
}}
|
|
350
|
-
onMouseLeave={hideLegendTooltip}
|
|
351
|
-
>
|
|
352
|
-
{`${entry.years} Years`}
|
|
353
|
-
</span>
|
|
354
|
-
) : (
|
|
355
|
-
<span className="opacity-0 whitespace-nowrap text-[var(--text-secondary)]">0 Years</span>
|
|
356
|
-
)}
|
|
357
|
-
</div>
|
|
358
|
-
<div
|
|
359
|
-
onMouseEnter={(e) => {
|
|
361
|
+
<div
|
|
362
|
+
onMouseEnter={(e) => {
|
|
363
|
+
if (entry && typeof entry !== 'string') {
|
|
360
364
|
const copy = presenceLegendTooltip();
|
|
361
365
|
showLegendTooltipAt(e.currentTarget, undefined, copy.body);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
366
|
+
}
|
|
367
|
+
}}
|
|
368
|
+
onMouseLeave={(e) => {
|
|
369
|
+
if (entry && typeof entry !== 'string') hideLegendTooltip();
|
|
370
|
+
}}
|
|
371
|
+
className="pt-1"
|
|
372
|
+
>
|
|
373
|
+
{(() => {
|
|
374
|
+
if (entry && typeof entry !== 'string') {
|
|
375
|
+
const types = Array.isArray(entry.presenceTypes) ? entry.presenceTypes : (entry.presence ? [String(entry.presence) as any] : []);
|
|
376
|
+
const hasAny = types.length > 0;
|
|
377
|
+
return hasAny ? (
|
|
378
|
+
<div
|
|
379
|
+
className="flex items-center gap-1"
|
|
380
|
+
>
|
|
381
|
+
{types.map((t) => (
|
|
382
|
+
<span key={t} className="inline-block h-2 w-2 rounded-full" style={{ background: presenceColor(t) }} />
|
|
383
|
+
))}
|
|
384
|
+
</div>
|
|
385
|
+
) : <span className="opacity-0 whitespace-nowrap">.</span>;
|
|
386
|
+
}
|
|
387
|
+
return <span className="opacity-0 whitespace-nowrap">.</span>;
|
|
379
388
|
})()}
|
|
380
|
-
|
|
381
|
-
</div>
|
|
382
389
|
</div>
|
|
383
|
-
|
|
390
|
+
</div>
|
|
384
391
|
</div>
|
|
385
392
|
)
|
|
386
393
|
}
|
|
@@ -474,7 +481,7 @@ export default function SkillsBubble({ skillsCategoryRadar, skillsByCategory, sk
|
|
|
474
481
|
</span>
|
|
475
482
|
) : null}
|
|
476
483
|
</div>
|
|
477
|
-
<div className="grid grid-cols-2 gap-x-
|
|
484
|
+
<div className="grid grid-cols-2 gap-x-20 text-xs" style={{ color: 'var(--text-secondary)', minHeight: 250 }}>
|
|
478
485
|
<div className="grid gap-3">
|
|
479
486
|
{leftColumnGrid.map((entry, idx) => (
|
|
480
487
|
columnComponent(entry, idx, true)
|