@ably/ui 17.13.0-dev.de27db52 → 17.13.1-dev.c839343a
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/AGENTS.md +337 -0
- package/core/Accordion/types.js.map +1 -1
- package/core/Accordion.js +1 -1
- package/core/Accordion.js.map +1 -1
- package/core/CodeSnippet/languages.js +1 -1
- package/core/CodeSnippet/languages.js.map +1 -1
- package/core/CodeSnippet.js +1 -1
- package/core/CodeSnippet.js.map +1 -1
- package/core/Expander.js +1 -1
- package/core/Expander.js.map +1 -1
- package/core/Icon/components/icon-display-ephemeral-messages-dark-col.js +2 -0
- package/core/Icon/components/icon-display-ephemeral-messages-dark-col.js.map +1 -0
- package/core/Icon/components/icon-display-message-annotations-dark-col.js +2 -0
- package/core/Icon/components/icon-display-message-annotations-dark-col.js.map +1 -0
- package/core/Icon/components/icon-display-other-mono.js +2 -0
- package/core/Icon/components/icon-display-other-mono.js.map +1 -0
- package/core/Icon/components/icon-display-other.js +2 -0
- package/core/Icon/components/icon-display-other.js.map +1 -0
- package/core/Icon/components/icon-gui-checklist-checked.js +1 -1
- package/core/Icon/components/icon-gui-checklist-checked.js.map +1 -1
- package/core/Icon/components/icon-gui-code-doc.js +1 -1
- package/core/Icon/components/icon-gui-code-doc.js.map +1 -1
- package/core/Icon/components/icon-gui-cursor.js +1 -1
- package/core/Icon/components/icon-gui-cursor.js.map +1 -1
- package/core/Icon/components/icon-gui-expand.js +1 -1
- package/core/Icon/components/icon-gui-expand.js.map +1 -1
- package/core/Icon/components/icon-gui-filter-flow-step-0.js +1 -1
- package/core/Icon/components/icon-gui-filter-flow-step-0.js.map +1 -1
- package/core/Icon/components/icon-gui-flower-growth.js +1 -1
- package/core/Icon/components/icon-gui-flower-growth.js.map +1 -1
- package/core/Icon/components/icon-gui-glasses.js +1 -1
- package/core/Icon/components/icon-gui-glasses.js.map +1 -1
- package/core/Icon/components/icon-gui-mouse.js +1 -1
- package/core/Icon/components/icon-gui-mouse.js.map +1 -1
- package/core/Icon/components/icon-gui-pitfall.js +1 -1
- package/core/Icon/components/icon-gui-pitfall.js.map +1 -1
- package/core/Icon/components/icon-gui-quote-marks-fill.js +1 -1
- package/core/Icon/components/icon-gui-quote-marks-fill.js.map +1 -1
- package/core/Icon/components/icon-product-other-mono.js +2 -0
- package/core/Icon/components/icon-product-other-mono.js.map +1 -0
- package/core/Icon/components/icon-product-other.js +2 -0
- package/core/Icon/components/icon-product-other.js.map +1 -0
- package/core/Icon/components/index.js +1 -1
- package/core/Icon/components/index.js.map +1 -1
- package/core/Icon/computed-icons/display-icons.js +1 -1
- package/core/Icon/computed-icons/display-icons.js.map +1 -1
- package/core/hooks/use-content-height.js +1 -1
- package/core/hooks/use-content-height.js.map +1 -1
- package/core/hooks/use-themed-scrollpoints.js +1 -1
- package/core/hooks/use-themed-scrollpoints.js.map +1 -1
- package/core/hooks/use-themed-scrollpoints.test.js +1 -1
- package/core/hooks/use-themed-scrollpoints.test.js.map +1 -1
- package/core/icons/display/icon-display-ephemeral-messages-dark-col.svg +6 -0
- package/core/icons/display/icon-display-message-annotations-dark-col.svg +11 -0
- package/core/icons/gui/icon-gui-checklist-checked.svg +1 -1
- package/core/icons/gui/icon-gui-code-doc.svg +1 -1
- package/core/icons/gui/icon-gui-cursor.svg +1 -1
- package/core/icons/gui/icon-gui-expand.svg +1 -1
- package/core/icons/gui/icon-gui-filter-flow-step-0.svg +3 -3
- package/core/icons/gui/icon-gui-flower-growth.svg +1 -1
- package/core/icons/gui/icon-gui-glasses.svg +1 -1
- package/core/icons/gui/icon-gui-mouse.svg +1 -1
- package/core/icons/gui/icon-gui-pitfall.svg +1 -1
- package/core/icons/gui/icon-gui-quote-marks-fill.svg +2 -2
- package/core/insights/posthog.js +1 -1
- package/core/insights/posthog.js.map +1 -1
- package/core/sprites-display.svg +1 -1
- package/core/sprites-gui.svg +1 -1
- package/index.d.ts +83 -14
- package/package.json +9 -9
- package/core/CookieMessage/component.css +0 -15
- package/core/CookieMessage.js +0 -2
- package/core/CookieMessage.js.map +0 -1
package/AGENTS.md
CHANGED
|
@@ -39,6 +39,34 @@ This project is intended to primarily be consumed by the Ably website, voltaire
|
|
|
39
39
|
- **Error Handling**: Wrap external service calls in try-catch, log with logger module
|
|
40
40
|
- **Comments**: JSDoc for props, inline comments for complex logic
|
|
41
41
|
|
|
42
|
+
### Custom Hooks
|
|
43
|
+
|
|
44
|
+
- **Naming**: `use` prefix with descriptive name (e.g., `useContentHeight`, `useThemedScrollpoints`)
|
|
45
|
+
- **JSDoc**: Always include for custom hooks, especially performance-related ones
|
|
46
|
+
- **Parameters**: Document with `@param` including types and defaults
|
|
47
|
+
- **Returns**: Document with `@returns` including type and semantic meaning
|
|
48
|
+
- **Performance rationale**: Include "why" in JSDoc when optimizing (e.g., "eliminates forced reflows")
|
|
49
|
+
- **Cleanup**: Always return cleanup function to prevent memory leaks
|
|
50
|
+
- **Shared constants**: Import from `src/core/utils/heights.ts` instead of duplicating
|
|
51
|
+
|
|
52
|
+
Example:
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
/**
|
|
56
|
+
* Tracks element height using ResizeObserver to avoid forced reflows.
|
|
57
|
+
*
|
|
58
|
+
* @param ref - React ref to the element to observe
|
|
59
|
+
* @param initialHeight - Initial height value (default: 0)
|
|
60
|
+
* @returns Current height in pixels
|
|
61
|
+
*/
|
|
62
|
+
export function useContentHeight(
|
|
63
|
+
ref: RefObject<HTMLElement>,
|
|
64
|
+
initialHeight = 0,
|
|
65
|
+
): number {
|
|
66
|
+
// Implementation...
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
42
70
|
Keep emojis in the code to a minimum, only introduce them if there is precedent
|
|
43
71
|
in the file you're working on.
|
|
44
72
|
|
|
@@ -51,6 +79,9 @@ this is a given for how we work.
|
|
|
51
79
|
files in CI and don't want preventable failures. `pnpm lint:fix` should also
|
|
52
80
|
apply our formatting rules while trying to fix most things for you
|
|
53
81
|
- Run tests with `pnpm test` after making file changes
|
|
82
|
+
- When testing with Storybook, use Chrome DevTools Performance tab to verify no forced reflows
|
|
83
|
+
- For performance-related changes, compare before/after metrics and include in commit/PR
|
|
84
|
+
- Use Chrome MCP at `http://localhost:6006` (Storybook) or `http://localhost:4000` (Voltaire) for visual verification
|
|
54
85
|
|
|
55
86
|
## Styling Guide
|
|
56
87
|
|
|
@@ -188,6 +219,312 @@ Here's a complete button component demonstrating all patterns:
|
|
|
188
219
|
>
|
|
189
220
|
```
|
|
190
221
|
|
|
222
|
+
## Performance Optimization Guidelines
|
|
223
|
+
|
|
224
|
+
### When to Optimize
|
|
225
|
+
|
|
226
|
+
Optimize when Chrome DevTools Performance profiling shows:
|
|
227
|
+
|
|
228
|
+
- Forced reflows/layouts in event handlers (scroll, resize, input)
|
|
229
|
+
- Long tasks blocking the main thread (>50ms)
|
|
230
|
+
- CPU throttling causing device overheating (especially iOS)
|
|
231
|
+
|
|
232
|
+
Common anti-patterns causing forced reflows:
|
|
233
|
+
|
|
234
|
+
- `getBoundingClientRect()` in scroll/resize handlers
|
|
235
|
+
- `clientHeight/scrollHeight/offsetHeight` reads during interactions
|
|
236
|
+
- Synchronous layout queries followed by style changes
|
|
237
|
+
- DOM queries inside throttled/debounced callbacks
|
|
238
|
+
|
|
239
|
+
### Observer API Patterns
|
|
240
|
+
|
|
241
|
+
#### IntersectionObserver (Scroll Position Detection)
|
|
242
|
+
|
|
243
|
+
Use for detecting when elements enter/exit viewport or cross specific boundaries.
|
|
244
|
+
|
|
245
|
+
**Example:** Header theme changes based on which section is visible
|
|
246
|
+
|
|
247
|
+
**Key patterns:**
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
const observerRef = useRef<IntersectionObserver | null>(null);
|
|
251
|
+
const intersectingElementsRef = useRef<Map<string, IntersectionObserverEntry>>(new Map());
|
|
252
|
+
|
|
253
|
+
useEffect(() => {
|
|
254
|
+
const intersectingElements = intersectingElementsRef.current;
|
|
255
|
+
|
|
256
|
+
observerRef.current = new IntersectionObserver(
|
|
257
|
+
(entries) => {
|
|
258
|
+
requestAnimationFrame(() => {
|
|
259
|
+
// Update tracking map
|
|
260
|
+
for (const entry of entries) {
|
|
261
|
+
if (entry.isIntersecting) {
|
|
262
|
+
intersectingElements.set(entry.target.id, entry);
|
|
263
|
+
} else {
|
|
264
|
+
intersectingElements.delete(entry.target.id);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Find best match from ALL intersecting elements
|
|
269
|
+
// (observer only reports changes, not all intersecting)
|
|
270
|
+
let bestMatch = null;
|
|
271
|
+
for (const [id, entry] of intersectingElements) {
|
|
272
|
+
const rect = entry.boundingClientRect ?? entry.target.getBoundingClientRect();
|
|
273
|
+
// Calculate match quality...
|
|
274
|
+
if (isBetterMatch) bestMatch = {...};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Only update state if changed
|
|
278
|
+
if (bestMatch && bestMatch.value !== previousValueRef.current) {
|
|
279
|
+
previousValueRef.current = bestMatch.value;
|
|
280
|
+
setState(bestMatch.value);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
rootMargin: "-64px 0px 0px 0px", // Adjust for fixed header
|
|
286
|
+
threshold: 0,
|
|
287
|
+
}
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
// Observe elements
|
|
291
|
+
elements.forEach(el => observerRef.current?.observe(el));
|
|
292
|
+
|
|
293
|
+
// CRITICAL: Manual initial state check
|
|
294
|
+
// IntersectionObserver callbacks only fire on CHANGES, not initial observation
|
|
295
|
+
const timeoutId = setTimeout(() => {
|
|
296
|
+
// Check which elements currently intersect
|
|
297
|
+
// Set initial state
|
|
298
|
+
}, 0);
|
|
299
|
+
|
|
300
|
+
return () => {
|
|
301
|
+
clearTimeout(timeoutId);
|
|
302
|
+
observerRef.current?.disconnect();
|
|
303
|
+
observerRef.current = null;
|
|
304
|
+
intersectingElements.clear();
|
|
305
|
+
};
|
|
306
|
+
}, [deps]);
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
**Critical points:**
|
|
310
|
+
|
|
311
|
+
- Observer only reports state CHANGES, not all intersecting elements
|
|
312
|
+
- Use Map to track currently intersecting elements
|
|
313
|
+
- Manual initial check with `setTimeout(..., 0)` required
|
|
314
|
+
- Batch updates with `requestAnimationFrame()`
|
|
315
|
+
- Track previous value to skip redundant setState
|
|
316
|
+
- **Tiebreaker logic**: When multiple elements have equal distances, use array order (earlier in array wins)
|
|
317
|
+
- Clean up timeout, observer, and Map
|
|
318
|
+
|
|
319
|
+
**Tiebreaker pattern:**
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
// When distances are equal, use scrollpoints array order
|
|
323
|
+
if (
|
|
324
|
+
!bestMatch ||
|
|
325
|
+
distance < bestMatch.distance ||
|
|
326
|
+
(distance === bestMatch.distance && scrollpointIndex < bestMatch.index)
|
|
327
|
+
) {
|
|
328
|
+
bestMatch = { scrollpoint, distance, index: scrollpointIndex };
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
**Why this matters:** In Voltaire, both `meganav` (transparent) and `main-theme-dark` (with border) start at position 0, giving identical distances. Without a tiebreaker, the header unpredictably showed the border. Array order ensures `meganav` (listed first) always wins.
|
|
333
|
+
|
|
334
|
+
#### ResizeObserver (Height/Size Tracking)
|
|
335
|
+
|
|
336
|
+
Use for tracking element dimensions without synchronous layout reads.
|
|
337
|
+
|
|
338
|
+
**Example:** Expander content height for expand/collapse animations
|
|
339
|
+
|
|
340
|
+
**Key patterns:**
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
const rafIdRef = useRef<number | null>(null);
|
|
344
|
+
const observerRef = useRef<ResizeObserver | null>(null);
|
|
345
|
+
|
|
346
|
+
useEffect(() => {
|
|
347
|
+
let isMounted = true;
|
|
348
|
+
|
|
349
|
+
observerRef.current = new ResizeObserver((entries) => {
|
|
350
|
+
// Cancel any pending RAF to avoid stale updates
|
|
351
|
+
if (rafIdRef.current !== null) {
|
|
352
|
+
cancelAnimationFrame(rafIdRef.current);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
rafIdRef.current = requestAnimationFrame(() => {
|
|
356
|
+
rafIdRef.current = null;
|
|
357
|
+
|
|
358
|
+
// Guard against updates after unmount
|
|
359
|
+
if (!isMounted) return;
|
|
360
|
+
|
|
361
|
+
const entry = entries[0];
|
|
362
|
+
if (entry && entry.contentRect) {
|
|
363
|
+
const newHeight = Math.round(entry.contentRect.height);
|
|
364
|
+
setState(newHeight);
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
observerRef.current.observe(element);
|
|
370
|
+
|
|
371
|
+
return () => {
|
|
372
|
+
isMounted = false;
|
|
373
|
+
// Cancel pending RAF to prevent setState after unmount
|
|
374
|
+
if (rafIdRef.current !== null) {
|
|
375
|
+
cancelAnimationFrame(rafIdRef.current);
|
|
376
|
+
rafIdRef.current = null;
|
|
377
|
+
}
|
|
378
|
+
observerRef.current?.disconnect();
|
|
379
|
+
observerRef.current = null;
|
|
380
|
+
};
|
|
381
|
+
}, [ref]);
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
**Critical points:**
|
|
385
|
+
|
|
386
|
+
- Always capture RAF ID and cancel on cleanup
|
|
387
|
+
- Use `isMounted` flag to guard setState calls
|
|
388
|
+
- Cancel pending RAF before scheduling new one
|
|
389
|
+
- ResizeObserver doesn't need initial check (fires immediately on observe)
|
|
390
|
+
- Round numeric values for consistency
|
|
391
|
+
|
|
392
|
+
### Testing Async Hooks
|
|
393
|
+
|
|
394
|
+
#### Setup Pattern
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
describe("useMyHook", () => {
|
|
398
|
+
let originalIntersectionObserver: typeof IntersectionObserver;
|
|
399
|
+
let originalRequestAnimationFrame: typeof requestAnimationFrame;
|
|
400
|
+
|
|
401
|
+
beforeEach(() => {
|
|
402
|
+
vi.useFakeTimers();
|
|
403
|
+
|
|
404
|
+
// CRITICAL: Save originals BEFORE mocking
|
|
405
|
+
originalIntersectionObserver = global.IntersectionObserver;
|
|
406
|
+
originalRequestAnimationFrame = global.requestAnimationFrame;
|
|
407
|
+
|
|
408
|
+
// Mock global APIs
|
|
409
|
+
global.IntersectionObserver = vi.fn((callback) => ({
|
|
410
|
+
observe: vi.fn(),
|
|
411
|
+
disconnect: vi.fn(),
|
|
412
|
+
})) as unknown as typeof IntersectionObserver;
|
|
413
|
+
|
|
414
|
+
global.requestAnimationFrame = vi.fn((cb) => {
|
|
415
|
+
cb(0);
|
|
416
|
+
return 0;
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
afterEach(() => {
|
|
421
|
+
vi.clearAllMocks();
|
|
422
|
+
vi.useRealTimers();
|
|
423
|
+
document.body.innerHTML = "";
|
|
424
|
+
|
|
425
|
+
// CRITICAL: Restore originals to prevent test pollution
|
|
426
|
+
global.IntersectionObserver = originalIntersectionObserver;
|
|
427
|
+
global.requestAnimationFrame = originalRequestAnimationFrame;
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
#### Testing Observer Callbacks
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
it("updates state when observer fires", () => {
|
|
436
|
+
const elem = document.createElement("div");
|
|
437
|
+
elem.id = "test";
|
|
438
|
+
elem.getBoundingClientRect = vi.fn().mockReturnValue({ top: 0, bottom: 200 });
|
|
439
|
+
document.body.appendChild(elem);
|
|
440
|
+
|
|
441
|
+
const { result } = renderHook(() => useMyHook());
|
|
442
|
+
|
|
443
|
+
// Advance timers for initial check
|
|
444
|
+
act(() => {
|
|
445
|
+
vi.runAllTimers();
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
// Simulate observer callback
|
|
449
|
+
act(() => {
|
|
450
|
+
observerCallback(
|
|
451
|
+
[
|
|
452
|
+
{
|
|
453
|
+
target: elem,
|
|
454
|
+
isIntersecting: true,
|
|
455
|
+
boundingClientRect: {
|
|
456
|
+
top: 0,
|
|
457
|
+
bottom: 200,
|
|
458
|
+
left: 0,
|
|
459
|
+
right: 0,
|
|
460
|
+
x: 0,
|
|
461
|
+
y: 0,
|
|
462
|
+
width: 0,
|
|
463
|
+
height: 200,
|
|
464
|
+
},
|
|
465
|
+
} as unknown as IntersectionObserverEntry,
|
|
466
|
+
],
|
|
467
|
+
{} as IntersectionObserver,
|
|
468
|
+
);
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
expect(result.current).toBe("expected-value");
|
|
472
|
+
});
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
**Key points:**
|
|
476
|
+
|
|
477
|
+
- Mock `getBoundingClientRect` on test elements
|
|
478
|
+
- Provide `boundingClientRect` in IntersectionObserverEntry mocks
|
|
479
|
+
- Wrap timer advances and callback calls in `act()`
|
|
480
|
+
- Test both initial state and subsequent updates
|
|
481
|
+
|
|
482
|
+
### Common Pitfalls Checklist
|
|
483
|
+
|
|
484
|
+
When writing performance-optimized hooks:
|
|
485
|
+
|
|
486
|
+
- [ ] RAF cleanup: Store ID, cancel in cleanup
|
|
487
|
+
- [ ] isMounted guard: Prevent setState after unmount
|
|
488
|
+
- [ ] Initial state check: Manual check for IntersectionObserver
|
|
489
|
+
- [ ] Previous value tracking: Skip redundant setState
|
|
490
|
+
- [ ] Map/Set cleanup: Clear in cleanup function
|
|
491
|
+
- [ ] Test mock restoration: Save originals, restore in afterEach
|
|
492
|
+
- [ ] Console warnings: For missing DOM elements (not errors)
|
|
493
|
+
- [ ] Tiebreaker logic: When multiple candidates have equal scores
|
|
494
|
+
|
|
495
|
+
## Storybook Development
|
|
496
|
+
|
|
497
|
+
### Testing Interactive Components
|
|
498
|
+
|
|
499
|
+
- Use `http://localhost:6006` when developing/testing components
|
|
500
|
+
- Create stories that simulate production patterns (e.g., overlapping scrollpoints like Voltaire)
|
|
501
|
+
- Test edge cases in stories (empty arrays, missing DOM elements, rapid state changes)
|
|
502
|
+
|
|
503
|
+
### Performance Testing in Storybook
|
|
504
|
+
|
|
505
|
+
1. Open Chrome DevTools → Performance tab
|
|
506
|
+
2. Start recording while interacting with component
|
|
507
|
+
3. Search for forced reflow indicators:
|
|
508
|
+
- `getBoundingClientRect`
|
|
509
|
+
- `clientHeight`/`scrollHeight`/`offsetHeight`
|
|
510
|
+
- "Forced reflow" warnings
|
|
511
|
+
4. Measure total time in layout/reflow (should be <5ms for interactions)
|
|
512
|
+
|
|
513
|
+
### Simulating Production Patterns
|
|
514
|
+
|
|
515
|
+
When creating stories for layout-dependent components, replicate real-world scenarios:
|
|
516
|
+
|
|
517
|
+
Example - Overlapping scrollpoints (like Voltaire):
|
|
518
|
+
|
|
519
|
+
```tsx
|
|
520
|
+
<div className="relative">
|
|
521
|
+
<div id="hero" className="absolute top-0 h-32" />
|
|
522
|
+
<div id="main" className="relative pt-32 h-screen" />
|
|
523
|
+
</div>
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
This allows testing tiebreaker logic and initial state detection. Storybook stories should replicate production layout patterns to catch bugs like the tiebreaker issue. The original simple sequential zones didn't expose the Voltaire bug where elements start at the same position.
|
|
527
|
+
|
|
191
528
|
## Git workflow
|
|
192
529
|
|
|
193
530
|
- Always do work on a new branch, start the branch on the HEAD of `origin/main`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/core/Accordion/types.ts"],"sourcesContent":["import { ReactNode } from \"react\";\nimport { IconName, IconSize } from \"../Icon/types\";\nimport { ColorThemeSet } from \"../styles/colors/types\";\n\n/**\n * Represents the data structure for an Accordion component.\n */\nexport type AccordionData = {\n /**\n * The name of the accordion item.\n */\n name: string;\n\n /**\n * The optional icon name to be displayed alongside the accordion item.\n */\n icon?: IconName | AccordionIcon;\n\n /**\n * The content to be displayed when the accordion item is expanded.\n */\n content: ReactNode;\n\n /**\n * Optional click handler function that is called when the accordion item is clicked.\n * @param index - The index of the clicked accordion item.\n */\n onClick?: (index: number) => void;\n\n /**\n * Indicates whether the accordion item is interactive.\n * When false, the item cannot be expanded or collapsed by user interaction.\n * @default true\n */\n interactive?: boolean;\n};\n\nexport type AccordionIcon = {\n name: IconName;\n css?: string;\n};\n\nexport type AccordionIcons = {\n closed: AccordionIcon;\n open: AccordionIcon;\n};\n\nexport const accordionThemes = [\"default\", \"transparent\", \"static\"] as const;\n\nexport type AccordionTheme = (typeof accordionThemes)[number];\n\n/**\n * Represents the theme colors for an accordion component.\n */\nexport type AccordionThemeColors = {\n /**\n * Background color class for the accordion.\n */\n bg: ColorThemeSet;\n\n /**\n * Background color when the accordion item is hovered.\n */\n hoverBg: ColorThemeSet;\n\n /**\n * Text color class for the accordion.\n */\n text: ColorThemeSet;\n\n /**\n * Color class for the toggle icon of the accordion.\n */\n toggleIconColor: ColorThemeSet;\n\n /**\n * Optional background color class for selectable accordion items.\n */\n selectableBg?: ColorThemeSet;\n\n /**\n * Optional text color class for selectable accordion items.\n */\n selectableText?: ColorThemeSet;\n\n /**\n * Optional border color for the accordion.\n */\n border?: string;\n};\n\n/**\n * Options for configuring the Accordion component.\n */\nexport type AccordionOptions = {\n /**\n * If true, only one accordion item can be open at a time.\n * @default false\n */\n autoClose?: boolean;\n\n /**\n * If true, accordion items can be selected.\n * @default false\n */\n selectable?: boolean;\n\n /**\n * If true, the accordion header will stick to the top when scrolling.\n * @default false\n */\n sticky?: boolean;\n\n /**\n * An array of indexes indicating which accordion items should be open by default.\n * @default []\n */\n defaultOpenIndexes?: number[];\n\n /**\n * If true, all accordion items will be fully open.\n * @default false\n */\n fullyOpen?: boolean;\n\n /**\n * Custom CSS class to apply to the accordion header.\n * @default \"\"\n */\n headerCSS?: string;\n\n /**\n * If true, borders between accordion items will be hidden.\n * @default false\n */\n hideBorders?: boolean;\n\n /**\n * Size of the row icon.\n * @default \"32px\"\n */\n rowIconSize?: IconSize;\n\n /**\n * Size of the accordion icon.\n * @default \"16px\"\n */\n iconSize?: IconSize;\n\n /**\n * Custom CSS classes to apply to the selected accordion header.\n * @default \"\"\n */\n selectedHeaderCSS?: string;\n\n /**\n * Custom CSS classes to apply to the accordion content.\n * @default \"\"\n */\n contentCSS?: string;\n};\n"],"names":["accordionThemes"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../src/core/Accordion/types.ts"],"sourcesContent":["import { ReactNode } from \"react\";\nimport { IconName, IconSize } from \"../Icon/types\";\nimport { ColorThemeSet } from \"../styles/colors/types\";\n\n/**\n * Represents the data structure for an Accordion component.\n */\nexport type AccordionData = {\n /**\n * The name of the accordion item.\n */\n name: string;\n\n /**\n * Custom heading content. If provided, this will be used instead of `name`.\n * Can be a ReactNode or a function that receives the index and isOpen state\n * and returns ReactNode.\n */\n heading?: ReactNode | ((index: number, isOpen: boolean) => ReactNode);\n\n /**\n * The optional icon name to be displayed alongside the accordion item.\n */\n icon?: IconName | AccordionIcon;\n\n /**\n * The content to be displayed when the accordion item is expanded.\n */\n content: ReactNode;\n\n /**\n * Optional click handler function that is called when the accordion item is clicked.\n * @param index - The index of the clicked accordion item.\n */\n onClick?: (index: number) => void;\n\n /**\n * Indicates whether the accordion item is interactive.\n * When false, the item cannot be expanded or collapsed by user interaction.\n * @default true\n */\n interactive?: boolean;\n};\n\nexport type AccordionIcon = {\n name: IconName;\n css?: string;\n};\n\nexport type AccordionIcons = {\n closed: AccordionIcon;\n open: AccordionIcon;\n};\n\nexport const accordionThemes = [\"default\", \"transparent\", \"static\"] as const;\n\nexport type AccordionTheme = (typeof accordionThemes)[number];\n\n/**\n * Represents the theme colors for an accordion component.\n */\nexport type AccordionThemeColors = {\n /**\n * Background color class for the accordion.\n */\n bg: ColorThemeSet;\n\n /**\n * Background color when the accordion item is hovered.\n */\n hoverBg: ColorThemeSet;\n\n /**\n * Text color class for the accordion.\n */\n text: ColorThemeSet;\n\n /**\n * Color class for the toggle icon of the accordion.\n */\n toggleIconColor: ColorThemeSet;\n\n /**\n * Optional background color class for selectable accordion items.\n */\n selectableBg?: ColorThemeSet;\n\n /**\n * Optional text color class for selectable accordion items.\n */\n selectableText?: ColorThemeSet;\n\n /**\n * Optional border color for the accordion.\n */\n border?: string;\n};\n\n/**\n * Options for configuring the Accordion component.\n */\nexport type AccordionOptions = {\n /**\n * If true, only one accordion item can be open at a time.\n * @default false\n */\n autoClose?: boolean;\n\n /**\n * If true, accordion items can be selected.\n * @default false\n */\n selectable?: boolean;\n\n /**\n * If true, the accordion header will stick to the top when scrolling.\n * @default false\n */\n sticky?: boolean;\n\n /**\n * An array of indexes indicating which accordion items should be open by default.\n * @default []\n */\n defaultOpenIndexes?: number[];\n\n /**\n * If true, all accordion items will be fully open.\n * @default false\n */\n fullyOpen?: boolean;\n\n /**\n * Custom CSS class to apply to the accordion header.\n * @default \"\"\n */\n headerCSS?: string;\n\n /**\n * If true, borders between accordion items will be hidden.\n * @default false\n */\n hideBorders?: boolean;\n\n /**\n * Size of the row icon.\n * @default \"32px\"\n */\n rowIconSize?: IconSize;\n\n /**\n * Size of the accordion icon.\n * @default \"16px\"\n */\n iconSize?: IconSize;\n\n /**\n * Custom CSS classes to apply to the selected accordion header.\n * @default \"\"\n */\n selectedHeaderCSS?: string;\n\n /**\n * Custom CSS classes to apply to the accordion content.\n * @default \"\"\n */\n contentCSS?: string;\n\n /**\n * Custom CSS classes to apply to the accordion item wrapper when it is open/active.\n * @default \"\"\n */\n selectedItemCSS?: string;\n};\n"],"names":["accordionThemes"],"mappings":"AAsDA,OAAO,MAAMA,gBAAkB,CAAC,UAAW,cAAe,SAAS,AAAU"}
|
package/core/Accordion.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import React,{useMemo,useState,forwardRef,useEffect}from"react";import{AccordionContent,AccordionItem,AccordionTrigger,Accordion as RadixAccordion}from"@radix-ui/react-accordion";import Icon from"./Icon";import{themeClasses,isNonTransparentTheme,isStaticTheme}from"./Accordion/utils";import cn from"./utils/cn";const AccordionRow=({name,children,rowIcon,options,toggleIcons,theme,index,onClick,openRowValues,rowInteractive=true})=>{const{selectable,sticky}=options||{};const rowKey=`accordion-item-${index}`;const isOpen=openRowValues.includes(rowKey);const{text,bg,hoverBg,selectableBg,selectableText,border,toggleIconColor}=themeClasses[theme];const textClass=selectable&&isOpen&&selectableText||text;return React.createElement(AccordionItem,{value:rowKey,className:cn({[`${border}`]:border&&!options?.hideBorders})},React.createElement(AccordionTrigger,{onClick:onClick,className:cn({"flex w-full group/accordion-trigger py-4 ui-text-p1 font-bold text-left items-center gap-3 transition-colors focus:outline-none":true,"px-4 mb-4 rounded-lg":isNonTransparentTheme(theme),"px-0 rounded-none":!isNonTransparentTheme(theme),"pointer-events-none focus-visible:outline-none":isStaticTheme(theme),"focus-base":!isStaticTheme(theme),"sticky top-0":sticky,[`${bg} ${hoverBg} ${text}`]:!(selectable&&isOpen),[`${selectableBg} ${selectableText}`]:selectable&&isOpen,[options?.headerCSS??""]:options?.headerCSS,[options?.selectedHeaderCSS??""]:options?.selectedHeaderCSS&&isOpen})},rowIcon?React.createElement(Icon,{name:typeof rowIcon==="object"?rowIcon.name:rowIcon,color:textClass,additionalCSS:typeof rowIcon==="object"&&rowIcon.css?rowIcon.css:"",size:options?.rowIconSize??"32px"}):null,
|
|
1
|
+
import React,{useMemo,useState,forwardRef,useEffect}from"react";import{AccordionContent,AccordionItem,AccordionTrigger,Accordion as RadixAccordion}from"@radix-ui/react-accordion";import Icon from"./Icon";import{themeClasses,isNonTransparentTheme,isStaticTheme}from"./Accordion/utils";import cn from"./utils/cn";const AccordionRow=({name,heading,children,rowIcon,options,toggleIcons,theme,index,onClick,openRowValues,rowInteractive=true})=>{const{selectable,sticky}=options||{};const rowKey=`accordion-item-${index}`;const isOpen=openRowValues.includes(rowKey);const{text,bg,hoverBg,selectableBg,selectableText,border,toggleIconColor}=themeClasses[theme];const textClass=selectable&&isOpen&&selectableText||text;const renderHeading=()=>{if(heading){if(typeof heading==="function"){return heading(index,isOpen)}return heading}return React.createElement("span",null,name)};return React.createElement(AccordionItem,{value:rowKey,className:cn({[`${border}`]:border&&!options?.hideBorders,[`${options?.selectedItemCSS}`]:options?.selectedItemCSS&&isOpen})},React.createElement(AccordionTrigger,{onClick:onClick,className:cn({"flex w-full group/accordion-trigger py-4 ui-text-p1 font-bold text-left items-center gap-3 transition-colors focus:outline-none":true,"px-4 mb-4 rounded-lg":isNonTransparentTheme(theme),"px-0 rounded-none":!isNonTransparentTheme(theme),"pointer-events-none focus-visible:outline-none":isStaticTheme(theme),"focus-base":!isStaticTheme(theme),"sticky top-0":sticky,[`${bg} ${hoverBg} ${text}`]:!(selectable&&isOpen),[`${selectableBg} ${selectableText}`]:selectable&&isOpen,[options?.headerCSS??""]:options?.headerCSS,[options?.selectedHeaderCSS??""]:options?.selectedHeaderCSS&&isOpen})},rowIcon?React.createElement(Icon,{name:typeof rowIcon==="object"?rowIcon.name:rowIcon,color:textClass,additionalCSS:typeof rowIcon==="object"&&rowIcon.css?rowIcon.css:"",size:options?.rowIconSize??"32px"}):null,renderHeading(),!selectable&&!isStaticTheme(theme)&&rowInteractive?React.createElement("span",{className:"flex-1 justify-end flex items-center"},React.createElement(Icon,{name:isOpen?toggleIcons.open.name:toggleIcons.closed.name,color:toggleIconColor,additionalCSS:isOpen?typeof toggleIcons.open==="object"&&toggleIcons.open.css||"":typeof toggleIcons.closed==="object"&&toggleIcons.closed.css||"",size:options?.iconSize??"16px"})):null),rowInteractive&&React.createElement(AccordionContent,{className:cn({"ui-text-p2 overflow-hidden transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down":true,[options?.contentCSS??""]:options?.contentCSS})},React.createElement("div",{className:"pb-4"},children)))};const Accordion=forwardRef(({data,theme="transparent",icons={closed:{name:"icon-gui-plus-outline"},open:{name:"icon-gui-minus-outline"}},options,...props},ref)=>{const openIndexes=useMemo(()=>{const indexValues=data.map((_,i)=>`accordion-item-${i}`);return options?.fullyOpen?indexValues:indexValues.filter((_,index)=>options?.defaultOpenIndexes?.includes(index))},[data,options?.fullyOpen,options?.defaultOpenIndexes]);const[openRowValues,setOpenRowValues]=useState(openIndexes);useEffect(()=>{setOpenRowValues(openIndexes)},[openIndexes]);const innerAccordion=data.map((item,index)=>React.createElement(AccordionRow,{key:item.name,name:item.name,heading:item.heading,rowIcon:item.icon,toggleIcons:icons,theme:theme,options:options,index:index,onClick:()=>{item.onClick?.(index)},openRowValues:openRowValues,rowInteractive:item.interactive},item.content));return React.createElement("div",{ref:ref,...props},options?.autoClose?React.createElement(RadixAccordion,{type:"single",collapsible:true,value:openRowValues[0],onValueChange:values=>setOpenRowValues([values])},innerAccordion):React.createElement(RadixAccordion,{type:"multiple",value:openRowValues,onValueChange:values=>setOpenRowValues(values)},innerAccordion))});Accordion.displayName="Accordion";export default Accordion;
|
|
2
2
|
//# sourceMappingURL=Accordion.js.map
|
package/core/Accordion.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/Accordion.tsx"],"sourcesContent":["import React, {\n ReactNode,\n useMemo,\n useState,\n forwardRef,\n useEffect,\n} from \"react\";\nimport {\n AccordionContent,\n AccordionItem,\n AccordionTrigger,\n Accordion as RadixAccordion,\n} from \"@radix-ui/react-accordion\";\n\nimport Icon from \"./Icon\";\nimport type { IconName } from \"./Icon/types\";\nimport type {\n AccordionData,\n AccordionIcon,\n AccordionIcons,\n AccordionOptions,\n AccordionTheme,\n} from \"./Accordion/types\";\nimport {\n themeClasses,\n isNonTransparentTheme,\n isStaticTheme,\n} from \"./Accordion/utils\";\nimport cn from \"./utils/cn\";\n\ntype AccordionRowProps = {\n children: ReactNode;\n name: string;\n rowIcon?: IconName | AccordionIcon;\n theme: AccordionTheme;\n toggleIcons: AccordionIcons;\n options?: AccordionOptions;\n index: number;\n onClick: () => void;\n openRowValues: string[];\n rowInteractive?: boolean;\n};\n\nexport type AccordionProps = {\n /**\n * The data for the accordion items.\n */\n data: AccordionData[];\n\n /**\n * Icons for the accordion toggle.\n */\n icons?: AccordionIcons;\n\n /**\n * Theme for the accordion.\n */\n theme?: AccordionTheme;\n\n /**\n * Options for the accordion behavior.\n */\n options?: AccordionOptions;\n} & React.HTMLAttributes<HTMLDivElement>;\n\nconst AccordionRow = ({\n name,\n children,\n rowIcon,\n options,\n toggleIcons,\n theme,\n index,\n onClick,\n openRowValues,\n rowInteractive = true,\n}: AccordionRowProps) => {\n const { selectable, sticky } = options || {};\n const rowKey = `accordion-item-${index}`;\n const isOpen = openRowValues.includes(rowKey);\n\n const {\n text,\n bg,\n hoverBg,\n selectableBg,\n selectableText,\n border,\n toggleIconColor,\n } = themeClasses[theme];\n\n const textClass = (selectable && isOpen && selectableText) || text;\n\n return (\n <AccordionItem\n value={rowKey}\n className={cn({\n [`${border}`]: border && !options?.hideBorders,\n })}\n >\n <AccordionTrigger\n onClick={onClick}\n className={cn({\n \"flex w-full group/accordion-trigger py-4 ui-text-p1 font-bold text-left items-center gap-3 transition-colors focus:outline-none\": true,\n \"px-4 mb-4 rounded-lg\": isNonTransparentTheme(theme),\n \"px-0 rounded-none\": !isNonTransparentTheme(theme),\n \"pointer-events-none focus-visible:outline-none\":\n isStaticTheme(theme),\n \"focus-base\": !isStaticTheme(theme),\n \"sticky top-0\": sticky,\n [`${bg} ${hoverBg} ${text}`]: !(selectable && isOpen),\n [`${selectableBg} ${selectableText}`]: selectable && isOpen,\n [options?.headerCSS ?? \"\"]: options?.headerCSS,\n [options?.selectedHeaderCSS ?? \"\"]:\n options?.selectedHeaderCSS && isOpen,\n })}\n >\n {rowIcon ? (\n <Icon\n name={typeof rowIcon === \"object\" ? rowIcon.name : rowIcon}\n color={textClass}\n additionalCSS={\n typeof rowIcon === \"object\" && rowIcon.css ? rowIcon.css : \"\"\n }\n size={options?.rowIconSize ?? \"32px\"}\n />\n ) : null}\n <span>{name}</span>\n {!selectable && !isStaticTheme(theme) && rowInteractive ? (\n <span className=\"flex-1 justify-end flex items-center\">\n <Icon\n name={isOpen ? toggleIcons.open.name : toggleIcons.closed.name}\n color={toggleIconColor}\n size={options?.iconSize ?? \"16px\"}\n />\n </span>\n ) : null}\n </AccordionTrigger>\n {rowInteractive && (\n <AccordionContent\n className={cn({\n \"ui-text-p2 overflow-hidden transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down\": true,\n [options?.contentCSS ?? \"\"]: options?.contentCSS,\n })}\n >\n <div className=\"pb-4\">{children}</div>\n </AccordionContent>\n )}\n </AccordionItem>\n );\n};\n\nconst Accordion = forwardRef<HTMLDivElement, AccordionProps>(\n (\n {\n data,\n theme = \"transparent\",\n icons = {\n closed: { name: \"icon-gui-plus-outline\" },\n open: { name: \"icon-gui-minus-outline\" },\n },\n options,\n ...props\n },\n ref,\n ) => {\n const openIndexes = useMemo(() => {\n const indexValues = data.map((_, i) => `accordion-item-${i}`);\n return options?.fullyOpen\n ? indexValues\n : indexValues.filter((_, index) =>\n options?.defaultOpenIndexes?.includes(index),\n );\n }, [data, options?.fullyOpen, options?.defaultOpenIndexes]);\n\n const [openRowValues, setOpenRowValues] = useState<string[]>(openIndexes);\n\n useEffect(() => {\n setOpenRowValues(openIndexes);\n }, [openIndexes]);\n\n const innerAccordion = data.map((item, index) => (\n <AccordionRow\n key={item.name}\n name={item.name}\n rowIcon={item.icon}\n toggleIcons={icons}\n theme={theme}\n options={options}\n index={index}\n onClick={() => {\n item.onClick?.(index);\n }}\n openRowValues={openRowValues}\n rowInteractive={item.interactive}\n >\n {item.content}\n </AccordionRow>\n ));\n\n return (\n <div ref={ref} {...props}>\n {options?.autoClose ? (\n <RadixAccordion\n type=\"single\"\n collapsible\n value={openRowValues[0]}\n onValueChange={(values) => setOpenRowValues([values])}\n >\n {innerAccordion}\n </RadixAccordion>\n ) : (\n <RadixAccordion\n type=\"multiple\"\n value={openRowValues}\n onValueChange={(values) => setOpenRowValues(values)}\n >\n {innerAccordion}\n </RadixAccordion>\n )}\n </div>\n );\n },\n);\n\nAccordion.displayName = \"Accordion\";\n\nexport default Accordion;\n"],"names":["React","useMemo","useState","forwardRef","useEffect","AccordionContent","AccordionItem","AccordionTrigger","Accordion","RadixAccordion","Icon","themeClasses","isNonTransparentTheme","isStaticTheme","cn","AccordionRow","name","children","rowIcon","options","toggleIcons","theme","index","onClick","openRowValues","rowInteractive","selectable","sticky","rowKey","isOpen","includes","text","bg","hoverBg","selectableBg","selectableText","border","toggleIconColor","textClass","value","className","hideBorders","headerCSS","selectedHeaderCSS","color","additionalCSS","css","size","rowIconSize","span","open","closed","iconSize","contentCSS","div","data","icons","props","ref","openIndexes","indexValues","map","_","i","fullyOpen","filter","defaultOpenIndexes","setOpenRowValues","innerAccordion","item","key","icon","interactive","content","autoClose","type","collapsible","onValueChange","values","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/core/Accordion.tsx"],"sourcesContent":["import React, {\n ReactNode,\n useMemo,\n useState,\n forwardRef,\n useEffect,\n} from \"react\";\nimport {\n AccordionContent,\n AccordionItem,\n AccordionTrigger,\n Accordion as RadixAccordion,\n} from \"@radix-ui/react-accordion\";\n\nimport Icon from \"./Icon\";\nimport type { IconName } from \"./Icon/types\";\nimport type {\n AccordionData,\n AccordionIcon,\n AccordionIcons,\n AccordionOptions,\n AccordionTheme,\n} from \"./Accordion/types\";\nimport {\n themeClasses,\n isNonTransparentTheme,\n isStaticTheme,\n} from \"./Accordion/utils\";\nimport cn from \"./utils/cn\";\n\ntype AccordionRowProps = {\n children: ReactNode;\n name: string;\n heading?: ReactNode | ((index: number, isOpen: boolean) => ReactNode);\n rowIcon?: IconName | AccordionIcon;\n theme: AccordionTheme;\n toggleIcons: AccordionIcons;\n options?: AccordionOptions;\n index: number;\n onClick: () => void;\n openRowValues: string[];\n rowInteractive?: boolean;\n};\n\nexport type AccordionProps = {\n /**\n * The data for the accordion items.\n */\n data: AccordionData[];\n\n /**\n * Icons for the accordion toggle.\n */\n icons?: AccordionIcons;\n\n /**\n * Theme for the accordion.\n */\n theme?: AccordionTheme;\n\n /**\n * Options for the accordion behavior.\n */\n options?: AccordionOptions;\n} & React.HTMLAttributes<HTMLDivElement>;\n\nconst AccordionRow = ({\n name,\n heading,\n children,\n rowIcon,\n options,\n toggleIcons,\n theme,\n index,\n onClick,\n openRowValues,\n rowInteractive = true,\n}: AccordionRowProps) => {\n const { selectable, sticky } = options || {};\n const rowKey = `accordion-item-${index}`;\n const isOpen = openRowValues.includes(rowKey);\n\n const {\n text,\n bg,\n hoverBg,\n selectableBg,\n selectableText,\n border,\n toggleIconColor,\n } = themeClasses[theme];\n\n const textClass = (selectable && isOpen && selectableText) || text;\n\n // Render custom heading or fallback to name\n const renderHeading = () => {\n if (heading) {\n if (typeof heading === \"function\") {\n return heading(index, isOpen);\n }\n return heading;\n }\n return <span>{name}</span>;\n };\n\n return (\n <AccordionItem\n value={rowKey}\n className={cn({\n [`${border}`]: border && !options?.hideBorders,\n [`${options?.selectedItemCSS}`]: options?.selectedItemCSS && isOpen,\n })}\n >\n <AccordionTrigger\n onClick={onClick}\n className={cn({\n \"flex w-full group/accordion-trigger py-4 ui-text-p1 font-bold text-left items-center gap-3 transition-colors focus:outline-none\": true,\n \"px-4 mb-4 rounded-lg\": isNonTransparentTheme(theme),\n \"px-0 rounded-none\": !isNonTransparentTheme(theme),\n \"pointer-events-none focus-visible:outline-none\":\n isStaticTheme(theme),\n \"focus-base\": !isStaticTheme(theme),\n \"sticky top-0\": sticky,\n [`${bg} ${hoverBg} ${text}`]: !(selectable && isOpen),\n [`${selectableBg} ${selectableText}`]: selectable && isOpen,\n [options?.headerCSS ?? \"\"]: options?.headerCSS,\n [options?.selectedHeaderCSS ?? \"\"]:\n options?.selectedHeaderCSS && isOpen,\n })}\n >\n {rowIcon ? (\n <Icon\n name={typeof rowIcon === \"object\" ? rowIcon.name : rowIcon}\n color={textClass}\n additionalCSS={\n typeof rowIcon === \"object\" && rowIcon.css ? rowIcon.css : \"\"\n }\n size={options?.rowIconSize ?? \"32px\"}\n />\n ) : null}\n {renderHeading()}\n {!selectable && !isStaticTheme(theme) && rowInteractive ? (\n <span className=\"flex-1 justify-end flex items-center\">\n <Icon\n name={isOpen ? toggleIcons.open.name : toggleIcons.closed.name}\n color={toggleIconColor}\n additionalCSS={\n isOpen\n ? (typeof toggleIcons.open === \"object\" &&\n toggleIcons.open.css) ||\n \"\"\n : (typeof toggleIcons.closed === \"object\" &&\n toggleIcons.closed.css) ||\n \"\"\n }\n size={options?.iconSize ?? \"16px\"}\n />\n </span>\n ) : null}\n </AccordionTrigger>\n {rowInteractive && (\n <AccordionContent\n className={cn({\n \"ui-text-p2 overflow-hidden transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down\": true,\n [options?.contentCSS ?? \"\"]: options?.contentCSS,\n })}\n >\n <div className=\"pb-4\">{children}</div>\n </AccordionContent>\n )}\n </AccordionItem>\n );\n};\n\nconst Accordion = forwardRef<HTMLDivElement, AccordionProps>(\n (\n {\n data,\n theme = \"transparent\",\n icons = {\n closed: { name: \"icon-gui-plus-outline\" },\n open: { name: \"icon-gui-minus-outline\" },\n },\n options,\n ...props\n },\n ref,\n ) => {\n const openIndexes = useMemo(() => {\n const indexValues = data.map((_, i) => `accordion-item-${i}`);\n return options?.fullyOpen\n ? indexValues\n : indexValues.filter((_, index) =>\n options?.defaultOpenIndexes?.includes(index),\n );\n }, [data, options?.fullyOpen, options?.defaultOpenIndexes]);\n\n const [openRowValues, setOpenRowValues] = useState<string[]>(openIndexes);\n\n useEffect(() => {\n setOpenRowValues(openIndexes);\n }, [openIndexes]);\n\n const innerAccordion = data.map((item, index) => (\n <AccordionRow\n key={item.name}\n name={item.name}\n heading={item.heading}\n rowIcon={item.icon}\n toggleIcons={icons}\n theme={theme}\n options={options}\n index={index}\n onClick={() => {\n item.onClick?.(index);\n }}\n openRowValues={openRowValues}\n rowInteractive={item.interactive}\n >\n {item.content}\n </AccordionRow>\n ));\n\n return (\n <div ref={ref} {...props}>\n {options?.autoClose ? (\n <RadixAccordion\n type=\"single\"\n collapsible\n value={openRowValues[0]}\n onValueChange={(values) => setOpenRowValues([values])}\n >\n {innerAccordion}\n </RadixAccordion>\n ) : (\n <RadixAccordion\n type=\"multiple\"\n value={openRowValues}\n onValueChange={(values) => setOpenRowValues(values)}\n >\n {innerAccordion}\n </RadixAccordion>\n )}\n </div>\n );\n },\n);\n\nAccordion.displayName = \"Accordion\";\n\nexport default Accordion;\n"],"names":["React","useMemo","useState","forwardRef","useEffect","AccordionContent","AccordionItem","AccordionTrigger","Accordion","RadixAccordion","Icon","themeClasses","isNonTransparentTheme","isStaticTheme","cn","AccordionRow","name","heading","children","rowIcon","options","toggleIcons","theme","index","onClick","openRowValues","rowInteractive","selectable","sticky","rowKey","isOpen","includes","text","bg","hoverBg","selectableBg","selectableText","border","toggleIconColor","textClass","renderHeading","span","value","className","hideBorders","selectedItemCSS","headerCSS","selectedHeaderCSS","color","additionalCSS","css","size","rowIconSize","open","closed","iconSize","contentCSS","div","data","icons","props","ref","openIndexes","indexValues","map","_","i","fullyOpen","filter","defaultOpenIndexes","setOpenRowValues","innerAccordion","item","key","icon","interactive","content","autoClose","type","collapsible","onValueChange","values","displayName"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const languages={javascript:{label:"JavaScript",icon:"icon-tech-javascript",syntaxHighlighterKey:"javascript"},typescript:{label:"TypeScript",icon:"icon-tech-typescript",syntaxHighlighterKey:"typescript"},java:{label:"Java",icon:"icon-tech-java",syntaxHighlighterKey:"java"},kotlin:{label:"Kotlin",icon:"icon-tech-kotlin",syntaxHighlighterKey:"kotlin"},python:{label:"Python",icon:"icon-tech-python",syntaxHighlighterKey:"python"},csharp:{label:"C#",icon:"icon-tech-csharp",syntaxHighlighterKey:"csharp"},go:{label:"Go",icon:"icon-tech-go",syntaxHighlighterKey:"go"},ruby:{label:"Ruby",icon:"icon-tech-ruby",syntaxHighlighterKey:"ruby"},php:{label:"PHP",icon:"icon-tech-php",syntaxHighlighterKey:"php"},nodejs:{label:"Node.js",icon:"icon-tech-nodejs",syntaxHighlighterKey:"javascript"},react:{label:"React",icon:"icon-tech-react",syntaxHighlighterKey:"javascript"},html:{label:"HTML",icon:"icon-tech-web",syntaxHighlighterKey:"xml"},shell:{label:"Shell",icon:"icon-tech-web",syntaxHighlighterKey:"bash"},json:{label:"JSON",icon:"icon-tech-json",syntaxHighlighterKey:"json"},laravel:{label:"Laravel",icon:"icon-tech-laravel-broadcast",syntaxHighlighterKey:"php"},xml:{label:"XML",icon:"icon-tech-web",syntaxHighlighterKey:"xml"},sql:{label:"SQL",icon:"icon-tech-postgres",syntaxHighlighterKey:"sql"},swift:{label:"Swift",icon:"icon-tech-swift",syntaxHighlighterKey:"swift"},cpp:{label:"C++",icon:"icon-tech-web",syntaxHighlighterKey:"cpp"},dart:{label:"Dart",icon:"icon-tech-web",syntaxHighlighterKey:"dart"},objc:{label:"Objective-C",icon:"icon-tech-objectivec",syntaxHighlighterKey:"objc"},android:{label:"Android",icon:"icon-tech-android-head",syntaxHighlighterKey:"java"},flutter:{label:"Flutter",icon:"icon-tech-flutter",syntaxHighlighterKey:"dart"},jetpack:{label:"Jetpack Compose",icon:"icon-tech-jetpack",syntaxHighlighterKey:"kotlin"}};export const stripSdkType=lang=>{
|
|
1
|
+
const languages={javascript:{label:"JavaScript",icon:"icon-tech-javascript",syntaxHighlighterKey:"javascript"},typescript:{label:"TypeScript",icon:"icon-tech-typescript",syntaxHighlighterKey:"typescript"},java:{label:"Java",icon:"icon-tech-java",syntaxHighlighterKey:"java"},kotlin:{label:"Kotlin",icon:"icon-tech-kotlin",syntaxHighlighterKey:"kotlin"},python:{label:"Python",icon:"icon-tech-python",syntaxHighlighterKey:"python"},csharp:{label:"C#",icon:"icon-tech-csharp",syntaxHighlighterKey:"csharp"},go:{label:"Go",icon:"icon-tech-go",syntaxHighlighterKey:"go"},ruby:{label:"Ruby",icon:"icon-tech-ruby",syntaxHighlighterKey:"ruby"},php:{label:"PHP",icon:"icon-tech-php",syntaxHighlighterKey:"php"},nodejs:{label:"Node.js",icon:"icon-tech-nodejs",syntaxHighlighterKey:"javascript"},react:{label:"React",icon:"icon-tech-react",syntaxHighlighterKey:"javascript"},html:{label:"HTML",icon:"icon-tech-web",syntaxHighlighterKey:"xml"},shell:{label:"Shell",icon:"icon-tech-web",syntaxHighlighterKey:"bash"},json:{label:"JSON",icon:"icon-tech-json",syntaxHighlighterKey:"json"},laravel:{label:"Laravel",icon:"icon-tech-laravel-broadcast",syntaxHighlighterKey:"php"},xml:{label:"XML",icon:"icon-tech-web",syntaxHighlighterKey:"xml"},sql:{label:"SQL",icon:"icon-tech-postgres",syntaxHighlighterKey:"sql"},swift:{label:"Swift",icon:"icon-tech-swift",syntaxHighlighterKey:"swift"},cpp:{label:"C++",icon:"icon-tech-web",syntaxHighlighterKey:"cpp"},dart:{label:"Dart",icon:"icon-tech-web",syntaxHighlighterKey:"dart"},objc:{label:"Objective-C",icon:"icon-tech-objectivec",syntaxHighlighterKey:"objc"},android:{label:"Android",icon:"icon-tech-android-head",syntaxHighlighterKey:"java"},flutter:{label:"Flutter",icon:"icon-tech-flutter",syntaxHighlighterKey:"dart"},jetpack:{label:"Jetpack Compose",icon:"icon-tech-jetpack",syntaxHighlighterKey:"kotlin"}};export const stripSdkType=lang=>{const prefixes=["realtime_","rest_","fe_","be_"];for(const prefix of prefixes){if(lang.startsWith(prefix)){return lang.slice(prefix.length)}}return lang};export const getLanguageInfo=langKey=>{const key=stripSdkType(langKey).toLowerCase();if(languages[key]){return languages[key]}return{label:langKey,icon:"icon-tech-web",syntaxHighlighterKey:langKey}};export default languages;
|
|
2
2
|
//# sourceMappingURL=languages.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/core/CodeSnippet/languages.ts"],"sourcesContent":["import { IconName } from \"../Icon/types\";\n\nexport interface LanguageInfo {\n label: string;\n icon: IconName;\n syntaxHighlighterKey?: string;\n}\n\nexport type LanguageMap = Record<string, LanguageInfo>;\n\nconst languages: LanguageMap = {\n javascript: {\n label: \"JavaScript\",\n icon: \"icon-tech-javascript\",\n syntaxHighlighterKey: \"javascript\",\n },\n typescript: {\n label: \"TypeScript\",\n icon: \"icon-tech-typescript\",\n syntaxHighlighterKey: \"typescript\",\n },\n java: {\n label: \"Java\",\n icon: \"icon-tech-java\",\n syntaxHighlighterKey: \"java\",\n },\n kotlin: {\n label: \"Kotlin\",\n icon: \"icon-tech-kotlin\",\n syntaxHighlighterKey: \"kotlin\",\n },\n python: {\n label: \"Python\",\n icon: \"icon-tech-python\",\n syntaxHighlighterKey: \"python\",\n },\n csharp: {\n label: \"C#\",\n icon: \"icon-tech-csharp\",\n syntaxHighlighterKey: \"csharp\",\n },\n go: {\n label: \"Go\",\n icon: \"icon-tech-go\",\n syntaxHighlighterKey: \"go\",\n },\n ruby: {\n label: \"Ruby\",\n icon: \"icon-tech-ruby\",\n syntaxHighlighterKey: \"ruby\",\n },\n php: {\n label: \"PHP\",\n icon: \"icon-tech-php\",\n syntaxHighlighterKey: \"php\",\n },\n nodejs: {\n label: \"Node.js\",\n icon: \"icon-tech-nodejs\",\n syntaxHighlighterKey: \"javascript\",\n },\n react: {\n label: \"React\",\n icon: \"icon-tech-react\",\n syntaxHighlighterKey: \"javascript\",\n },\n html: {\n label: \"HTML\",\n icon: \"icon-tech-web\",\n syntaxHighlighterKey: \"xml\",\n },\n shell: {\n label: \"Shell\",\n icon: \"icon-tech-web\",\n syntaxHighlighterKey: \"bash\",\n },\n json: {\n label: \"JSON\",\n icon: \"icon-tech-json\",\n syntaxHighlighterKey: \"json\",\n },\n laravel: {\n label: \"Laravel\",\n icon: \"icon-tech-laravel-broadcast\",\n syntaxHighlighterKey: \"php\",\n },\n xml: {\n label: \"XML\",\n icon: \"icon-tech-web\",\n syntaxHighlighterKey: \"xml\",\n },\n sql: {\n label: \"SQL\",\n icon: \"icon-tech-postgres\",\n syntaxHighlighterKey: \"sql\",\n },\n swift: {\n label: \"Swift\",\n icon: \"icon-tech-swift\",\n syntaxHighlighterKey: \"swift\",\n },\n // New entries from languageInfo.ts\n cpp: {\n label: \"C++\",\n icon: \"icon-tech-web\",\n syntaxHighlighterKey: \"cpp\",\n },\n dart: {\n label: \"Dart\",\n icon: \"icon-tech-web\",\n syntaxHighlighterKey: \"dart\",\n },\n objc: {\n label: \"Objective-C\",\n icon: \"icon-tech-objectivec\",\n syntaxHighlighterKey: \"objc\",\n },\n android: {\n label: \"Android\",\n icon: \"icon-tech-android-head\",\n syntaxHighlighterKey: \"java\",\n },\n flutter: {\n label: \"Flutter\",\n icon: \"icon-tech-flutter\",\n syntaxHighlighterKey: \"dart\",\n },\n jetpack: {\n label: \"Jetpack Compose\",\n icon: \"icon-tech-jetpack\",\n syntaxHighlighterKey: \"kotlin\",\n },\n};\n\nexport const stripSdkType = (lang: string) => {\n
|
|
1
|
+
{"version":3,"sources":["../../../src/core/CodeSnippet/languages.ts"],"sourcesContent":["import { IconName } from \"../Icon/types\";\n\nexport interface LanguageInfo {\n label: string;\n icon: IconName;\n syntaxHighlighterKey?: string;\n}\n\nexport type LanguageMap = Record<string, LanguageInfo>;\n\nconst languages: LanguageMap = {\n javascript: {\n label: \"JavaScript\",\n icon: \"icon-tech-javascript\",\n syntaxHighlighterKey: \"javascript\",\n },\n typescript: {\n label: \"TypeScript\",\n icon: \"icon-tech-typescript\",\n syntaxHighlighterKey: \"typescript\",\n },\n java: {\n label: \"Java\",\n icon: \"icon-tech-java\",\n syntaxHighlighterKey: \"java\",\n },\n kotlin: {\n label: \"Kotlin\",\n icon: \"icon-tech-kotlin\",\n syntaxHighlighterKey: \"kotlin\",\n },\n python: {\n label: \"Python\",\n icon: \"icon-tech-python\",\n syntaxHighlighterKey: \"python\",\n },\n csharp: {\n label: \"C#\",\n icon: \"icon-tech-csharp\",\n syntaxHighlighterKey: \"csharp\",\n },\n go: {\n label: \"Go\",\n icon: \"icon-tech-go\",\n syntaxHighlighterKey: \"go\",\n },\n ruby: {\n label: \"Ruby\",\n icon: \"icon-tech-ruby\",\n syntaxHighlighterKey: \"ruby\",\n },\n php: {\n label: \"PHP\",\n icon: \"icon-tech-php\",\n syntaxHighlighterKey: \"php\",\n },\n nodejs: {\n label: \"Node.js\",\n icon: \"icon-tech-nodejs\",\n syntaxHighlighterKey: \"javascript\",\n },\n react: {\n label: \"React\",\n icon: \"icon-tech-react\",\n syntaxHighlighterKey: \"javascript\",\n },\n html: {\n label: \"HTML\",\n icon: \"icon-tech-web\",\n syntaxHighlighterKey: \"xml\",\n },\n shell: {\n label: \"Shell\",\n icon: \"icon-tech-web\",\n syntaxHighlighterKey: \"bash\",\n },\n json: {\n label: \"JSON\",\n icon: \"icon-tech-json\",\n syntaxHighlighterKey: \"json\",\n },\n laravel: {\n label: \"Laravel\",\n icon: \"icon-tech-laravel-broadcast\",\n syntaxHighlighterKey: \"php\",\n },\n xml: {\n label: \"XML\",\n icon: \"icon-tech-web\",\n syntaxHighlighterKey: \"xml\",\n },\n sql: {\n label: \"SQL\",\n icon: \"icon-tech-postgres\",\n syntaxHighlighterKey: \"sql\",\n },\n swift: {\n label: \"Swift\",\n icon: \"icon-tech-swift\",\n syntaxHighlighterKey: \"swift\",\n },\n // New entries from languageInfo.ts\n cpp: {\n label: \"C++\",\n icon: \"icon-tech-web\",\n syntaxHighlighterKey: \"cpp\",\n },\n dart: {\n label: \"Dart\",\n icon: \"icon-tech-web\",\n syntaxHighlighterKey: \"dart\",\n },\n objc: {\n label: \"Objective-C\",\n icon: \"icon-tech-objectivec\",\n syntaxHighlighterKey: \"objc\",\n },\n android: {\n label: \"Android\",\n icon: \"icon-tech-android-head\",\n syntaxHighlighterKey: \"java\",\n },\n flutter: {\n label: \"Flutter\",\n icon: \"icon-tech-flutter\",\n syntaxHighlighterKey: \"dart\",\n },\n jetpack: {\n label: \"Jetpack Compose\",\n icon: \"icon-tech-jetpack\",\n syntaxHighlighterKey: \"kotlin\",\n },\n};\n\nexport const stripSdkType = (lang: string) => {\n const prefixes = [\"realtime_\", \"rest_\", \"fe_\", \"be_\"];\n for (const prefix of prefixes) {\n if (lang.startsWith(prefix)) {\n return lang.slice(prefix.length);\n }\n }\n return lang;\n};\n\n// Fallback function to handle languages not in the map\nexport const getLanguageInfo = (langKey: string): LanguageInfo => {\n const key = stripSdkType(langKey).toLowerCase();\n\n if (languages[key]) {\n return languages[key];\n }\n\n // Fallback for unknown languages\n return {\n label: langKey,\n icon: \"icon-tech-web\",\n syntaxHighlighterKey: langKey,\n };\n};\n\nexport default languages;\n"],"names":["languages","javascript","label","icon","syntaxHighlighterKey","typescript","java","kotlin","python","csharp","go","ruby","php","nodejs","react","html","shell","json","laravel","xml","sql","swift","cpp","dart","objc","android","flutter","jetpack","stripSdkType","lang","prefixes","prefix","startsWith","slice","length","getLanguageInfo","langKey","key","toLowerCase"],"mappings":"AAUA,MAAMA,UAAyB,CAC7BC,WAAY,CACVC,MAAO,aACPC,KAAM,uBACNC,qBAAsB,YACxB,EACAC,WAAY,CACVH,MAAO,aACPC,KAAM,uBACNC,qBAAsB,YACxB,EACAE,KAAM,CACJJ,MAAO,OACPC,KAAM,iBACNC,qBAAsB,MACxB,EACAG,OAAQ,CACNL,MAAO,SACPC,KAAM,mBACNC,qBAAsB,QACxB,EACAI,OAAQ,CACNN,MAAO,SACPC,KAAM,mBACNC,qBAAsB,QACxB,EACAK,OAAQ,CACNP,MAAO,KACPC,KAAM,mBACNC,qBAAsB,QACxB,EACAM,GAAI,CACFR,MAAO,KACPC,KAAM,eACNC,qBAAsB,IACxB,EACAO,KAAM,CACJT,MAAO,OACPC,KAAM,iBACNC,qBAAsB,MACxB,EACAQ,IAAK,CACHV,MAAO,MACPC,KAAM,gBACNC,qBAAsB,KACxB,EACAS,OAAQ,CACNX,MAAO,UACPC,KAAM,mBACNC,qBAAsB,YACxB,EACAU,MAAO,CACLZ,MAAO,QACPC,KAAM,kBACNC,qBAAsB,YACxB,EACAW,KAAM,CACJb,MAAO,OACPC,KAAM,gBACNC,qBAAsB,KACxB,EACAY,MAAO,CACLd,MAAO,QACPC,KAAM,gBACNC,qBAAsB,MACxB,EACAa,KAAM,CACJf,MAAO,OACPC,KAAM,iBACNC,qBAAsB,MACxB,EACAc,QAAS,CACPhB,MAAO,UACPC,KAAM,8BACNC,qBAAsB,KACxB,EACAe,IAAK,CACHjB,MAAO,MACPC,KAAM,gBACNC,qBAAsB,KACxB,EACAgB,IAAK,CACHlB,MAAO,MACPC,KAAM,qBACNC,qBAAsB,KACxB,EACAiB,MAAO,CACLnB,MAAO,QACPC,KAAM,kBACNC,qBAAsB,OACxB,EAEAkB,IAAK,CACHpB,MAAO,MACPC,KAAM,gBACNC,qBAAsB,KACxB,EACAmB,KAAM,CACJrB,MAAO,OACPC,KAAM,gBACNC,qBAAsB,MACxB,EACAoB,KAAM,CACJtB,MAAO,cACPC,KAAM,uBACNC,qBAAsB,MACxB,EACAqB,QAAS,CACPvB,MAAO,UACPC,KAAM,yBACNC,qBAAsB,MACxB,EACAsB,QAAS,CACPxB,MAAO,UACPC,KAAM,oBACNC,qBAAsB,MACxB,EACAuB,QAAS,CACPzB,MAAO,kBACPC,KAAM,oBACNC,qBAAsB,QACxB,CACF,CAEA,QAAO,MAAMwB,aAAe,AAACC,OAC3B,MAAMC,SAAW,CAAC,YAAa,QAAS,MAAO,MAAM,CACrD,IAAK,MAAMC,UAAUD,SAAU,CAC7B,GAAID,KAAKG,UAAU,CAACD,QAAS,CAC3B,OAAOF,KAAKI,KAAK,CAACF,OAAOG,MAAM,CACjC,CACF,CACA,OAAOL,IACT,CAAE,AAGF,QAAO,MAAMM,gBAAkB,AAACC,UAC9B,MAAMC,IAAMT,aAAaQ,SAASE,WAAW,GAE7C,GAAItC,SAAS,CAACqC,IAAI,CAAE,CAClB,OAAOrC,SAAS,CAACqC,IAAI,AACvB,CAGA,MAAO,CACLnC,MAAOkC,QACPjC,KAAM,gBACNC,qBAAsBgC,OACxB,CACF,CAAE,AAEF,gBAAepC,SAAU"}
|
package/core/CodeSnippet.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import React,{useState,useEffect,Children,isValidElement,useRef,useCallback,useMemo}from"react";import Code from"./Code";import cn from"./utils/cn";import Icon from"./Icon";import{getLanguageInfo,stripSdkType}from"./CodeSnippet/languages";import LanguageSelector from"./CodeSnippet/LanguageSelector";import ApiKeySelector from"./CodeSnippet/ApiKeySelector";import PlainCodeView from"./CodeSnippet/PlainCodeView";import CopyButton from"./CodeSnippet/CopyButton";import SegmentedControl from"./SegmentedControl";const substituteApiKey=(content,apiKey,mask=true)=>{return content.replace(/\{\{API_KEY\}\}/g,mask?`${apiKey.split(":")[0]}:*****`:apiKey)};const CodeSnippet=({fixed=false,headerRow=false,title="Code",children,className,lang,onChange,apiKeys,sdk,showCodeLines=true,languageOrdering,wrapCode=false})=>{const codeRef=useRef(null);const[selectedApiKey,setSelectedApiKey]=useState(()=>apiKeys?.[0]?.keys?.[0]?.key??"");useEffect(()=>{if(!selectedApiKey&&apiKeys&&apiKeys.length>0){setSelectedApiKey(apiKeys[0].keys?.[0]?.key)}},[apiKeys]);useEffect(()=>{const element=codeRef.current;if(!element)return;const unmaskRenderedApiKey=(content,apiKey)=>{return content.replace(/(['"]?)([^:'"]+):\*{5}\1/g,`$1${apiKey}$1`)};const handleCopy=event=>{const selection=window.getSelection();if(!selection||selection.rangeCount===0)return;const selectedText=selection.toString();if(!selectedText)return;const range=selection.getRangeAt(0);if(!element.contains(range.commonAncestorContainer))return;const modifiedText=unmaskRenderedApiKey(selectedText,selectedApiKey);event.clipboardData?.setData("text/plain",modifiedText);event.preventDefault()};document.addEventListener("copy",handleCopy);return()=>{document.removeEventListener("copy",handleCopy)}},[selectedApiKey]);const extractLanguageFromCode=useCallback(codeElement=>{if(!codeElement||!codeElement.props.className)return null;const classNames=codeElement.props.className.split(" ");const langClass=classNames.find(cls=>cls.startsWith("language-"));if(!langClass)return null;return langClass.substring(9)},[]);const{codeData,languages,sdkTypes,isSinglePlainCommand}=useMemo(()=>{const childrenArray=Children.toArray(children);const languages=[];const sdkTypes=new Set;const codeData=[];const isSinglePlainCommand=childrenArray.length===1&&["language-shell","language-text"].some(lang=>isValidElement(childrenArray[0])&&isValidElement(childrenArray[0].props.children)&&childrenArray[0].props.children.props.className?.includes(lang));childrenArray.forEach(child=>{if(!isValidElement(child))return;const preElement=child;const codeElement=isValidElement(preElement.props.children)?preElement.props.children:null;if(!codeElement)return;const codeLanguage=extractLanguageFromCode(codeElement);if(!codeLanguage)return;if(codeLanguage.startsWith("realtime_")){sdkTypes.add("realtime")}else if(codeLanguage.startsWith("rest_")){sdkTypes.add("rest")}if(!languages.includes(codeLanguage)){languages.push(codeLanguage)}const codeContent=codeElement.props.children;codeData.push({language:codeLanguage,content:codeContent})});return{codeData,languages,sdkTypes,isSinglePlainCommand}},[children,extractLanguageFromCode]);const resolvedSdk=useMemo(()=>{if(sdkTypes.size===1&&sdk&&!sdkTypes.has(sdk)){return Array.from(sdkTypes)[0]}return sdk??null},[sdk,sdkTypes]);const showSDKSelector=sdkTypes.size>0;const filteredLanguages=useMemo(()=>{const filtered=!resolvedSdk||!showSDKSelector?[...languages]:languages.filter(lang=>lang.startsWith(`${resolvedSdk}_`));if(languageOrdering&&languageOrdering.length>0){filtered.sort((a,b)=>{const aBase=stripSdkType(a);const bBase=stripSdkType(b);const aIndex=languageOrdering.indexOf(aBase);const bIndex=languageOrdering.indexOf(bBase);if(aIndex!==-1&&bIndex!==-1)return aIndex-bIndex;if(aIndex!==-1)return-1;if(bIndex!==-1)return 1;return 0})}return filtered},[resolvedSdk,showSDKSelector,languages,languageOrdering]);const activeLanguage=useMemo(()=>{if(resolvedSdk&&sdkTypes.has(resolvedSdk)){return`${resolvedSdk}_${lang}`}if(lang)return lang;if(filteredLanguages.length>0)return filteredLanguages[0];return languages[0]},[resolvedSdk,sdkTypes,lang,filteredLanguages,languages]);const requiresApiKeySubstitution=useMemo(()=>{const containsPlaceholder=codeData.some(code=>code?.content.includes("{{API_KEY}}"));return containsPlaceholder&&!!apiKeys&&apiKeys.length>0&&!!selectedApiKey},[codeData,apiKeys,selectedApiKey]);const[isHovering,setIsHovering]=useState(false);const hasOnlyJsonSnippet=useMemo(()=>languages.length===1&&languages[0]==="json",[languages]);const processedChildren=useMemo(()=>{if(!activeLanguage)return[];const targetLanguage=hasOnlyJsonSnippet?"json":activeLanguage;return codeData.filter(code=>{return code?.language===targetLanguage}).map(code=>{if(!code)return null;const cleanLang=hasOnlyJsonSnippet?"json":code.language;const langInfo=getLanguageInfo(cleanLang??"");if(typeof code.content==="string"||typeof code.content==="number"||typeof code.content==="boolean"){let processedContent=String(code.content);if(requiresApiKeySubstitution){processedContent=substituteApiKey(processedContent,selectedApiKey)}if(!langInfo.syntaxHighlighterKey||!cleanLang)return null;return React.createElement(Code,{key:code.language,language:langInfo.syntaxHighlighterKey||cleanLang,snippet:processedContent,additionalCSS:"!bg-neutral-000 text-neutral-1300 dark:!bg-neutral-1300 dark:text-neutral-200 px-6 py-4",showLines:showCodeLines,wrap:wrapCode})}return null})},[activeLanguage,hasOnlyJsonSnippet,codeData,requiresApiKeySubstitution,showCodeLines,wrapCode,selectedApiKey]);const hasSnippetForActiveLanguage=useMemo(()=>{if(!activeLanguage)return false;if(hasOnlyJsonSnippet)return true;return codeData.some(code=>{return code?.language===activeLanguage})},[activeLanguage,hasOnlyJsonSnippet,codeData]);const handleSDKTypeChange=useCallback(type=>{const nextLang=stripSdkType(languages.find(l=>l===`${type}_${stripSdkType(activeLanguage)}`)??languages.find(l=>l.startsWith(`${type}_`))??activeLanguage);if(onChange&&nextLang){onChange(stripSdkType(activeLanguage),type)}},[activeLanguage,languages,onChange]);const handleLanguageChange=useCallback(language=>{if(onChange){onChange(stripSdkType(language),resolvedSdk)}},[onChange,resolvedSdk]);const noSnippetMessage=useMemo(()=>{if(!activeLanguage)return null;const activeLanguageInfo=getLanguageInfo(activeLanguage);return React.createElement("div",{className:"px-16 py-6 ui-text-body2 text-neutral-800 dark:text-neutral-400 text-center flex flex-col gap-3 items-center"},React.createElement(Icon,{name:"icon-gui-exclamation-triangle-outline",color:"text-yellow-600 dark:text-yellow-400",size:"24px"}),React.createElement("p",{className:"ui-text-p3 text-neutral-700 dark:text-neutral-600"},"You're currently viewing the ",activeLanguageInfo.label," docs. There either isn't a ",activeLanguageInfo.label," code sample for this example, or this feature isn't supported in"," ",activeLanguageInfo.label,". Switch language to view this example in a different language, or check which SDKs support this feature."))},[activeLanguage]);const showLanguageSelector=!fixed&&filteredLanguages.length>0;const showFullSelector=filteredLanguages.length>1;const renderContent=useMemo(()=>{if(!activeLanguage)return null;if(hasSnippetForActiveLanguage){return processedChildren}return noSnippetMessage},[activeLanguage,hasSnippetForActiveLanguage,processedChildren,noSnippetMessage]);if(isSinglePlainCommand){const plainChild=codeData[0];if(plainChild){const codeContent=plainChild.content;const language=plainChild.language;if(!language||!codeContent)return null;let processedContent=String(codeContent);if(requiresApiKeySubstitution){processedContent=substituteApiKey(processedContent,selectedApiKey)}return React.createElement(PlainCodeView,{content:processedContent,className:className,language:language,icon:language==="shell"?"icon-gui-command-line-outline":null})}}return React.createElement("div",{className:cn("rounded-lg overflow-hidden bg-neutral-100 dark:bg-neutral-1200 border border-neutral-300 dark:border-neutral-1000 min-h-[3.375rem]",className)},headerRow&&React.createElement("div",{className:"h-[2.375rem] bg-neutral-200 dark:bg-neutral-1100 border-b border-neutral-300 dark:border-neutral-1000 flex items-center py-1 px-3 rounded-t-lg"},React.createElement("div",{className:"flex space-x-1.5"},React.createElement("div",{className:"w-3 h-3 rounded-full bg-orange-500"}),React.createElement("div",{className:"w-3 h-3 rounded-full bg-yellow-500"}),React.createElement("div",{className:"w-3 h-3 rounded-full bg-green-500"})),React.createElement("div",{className:"flex-1 text-center ui-text-p3 font-bold text-neutral-1300 dark:text-neutral-000"},title),React.createElement("div",{className:"w-12"})),showSDKSelector&&React.createElement("div",{className:cn("p-2 border-b border-neutral-300 dark:border-neutral-1000",sdkTypes.size===1&&"p-1",headerRow?"":"rounded-t-lg")},React.createElement("div",{className:"flex gap-1 justify-start"},["realtime","rest"].map(type=>sdkTypes.has(type)&&React.createElement(SegmentedControl,{key:type,onClick:()=>handleSDKTypeChange(type),size:"xs",active:resolvedSdk===type,className:cn("text-[11px] font-semibold px-2 py-1 h-auto",sdkTypes.size===1&&"pointer-events-none bg-neutral-100 dark:bg-neutral-1200 !text-neutral-800 !dark:text-neutral-500",sdkTypes.size>1&&resolvedSdk!==type&&"bg-neutral-100 dark:bg-neutral-1200 hover:bg-neutral-200 dark:hover:bg-neutral-1100 active:bg-neutral-400 dark:active:bg-neutral-900",sdkTypes.size>1&&resolvedSdk===type&&"bg-neutral-000 dark:bg-neutral-1100")},type==="realtime"?"Realtime":"REST")))),showLanguageSelector&&(showFullSelector?React.createElement(LanguageSelector,{languages:filteredLanguages,activeLanguage:activeLanguage,onLanguageChange:handleLanguageChange}):React.createElement("div",{className:cn("border-b border-neutral-300 dark:border-neutral-1000 h-[2.125rem] inline-flex items-center px-3 w-full",{"rounded-t-lg":!headerRow})},filteredLanguages.length>0&&React.createElement("div",{className:cn("inline-flex items-center",{"cursor-pointer":filteredLanguages.length>0}),...filteredLanguages.length>0&&{onClick:()=>handleLanguageChange(filteredLanguages[0])}},React.createElement(Icon,{name:getLanguageInfo(filteredLanguages[0]).icon,size:"16px",additionalCSS:"mr-2"}),React.createElement("span",{className:"ui-text-label4 font-semibold text-neutral-800 dark:text-neutral-500 select-none"},getLanguageInfo(filteredLanguages[0]).label)))),React.createElement("div",{ref:codeRef,className:"relative",onMouseEnter:()=>setIsHovering(true),onMouseLeave:()=>setIsHovering(false),onFocus:()=>setIsHovering(true),onBlur:()=>setIsHovering(false)},renderContent,isHovering&&activeLanguage&&hasSnippetForActiveLanguage&&React.createElement(CopyButton,{onCopy:()=>{const text=codeData.find(code=>code.language===activeLanguage)?.content;if(text)navigator.clipboard.writeText(substituteApiKey(text,selectedApiKey,false))}})),requiresApiKeySubstitution&&React.createElement(ApiKeySelector,{apiKeys:apiKeys,selectedApiKey:selectedApiKey,onApiKeyChange:setSelectedApiKey}))};export default CodeSnippet;
|
|
1
|
+
import React,{useState,useEffect,Children,isValidElement,useRef,useCallback,useMemo}from"react";import Code from"./Code";import cn from"./utils/cn";import Icon from"./Icon";import{getLanguageInfo,stripSdkType}from"./CodeSnippet/languages";import LanguageSelector from"./CodeSnippet/LanguageSelector";import ApiKeySelector from"./CodeSnippet/ApiKeySelector";import PlainCodeView from"./CodeSnippet/PlainCodeView";import CopyButton from"./CodeSnippet/CopyButton";import SegmentedControl from"./SegmentedControl";const substituteApiKey=(content,apiKey,mask=true)=>{return content.replace(/\{\{API_KEY\}\}/g,mask?`${apiKey.split(":")[0]}:*****`:apiKey)};const CodeSnippet=({fixed=false,headerRow=false,title="Code",children,className,lang,onChange,apiKeys,sdk,showCodeLines=true,languageOrdering,wrapCode=false})=>{const codeRef=useRef(null);const[selectedApiKey,setSelectedApiKey]=useState(()=>apiKeys?.[0]?.keys?.[0]?.key??"");useEffect(()=>{if(!selectedApiKey&&apiKeys&&apiKeys.length>0){setSelectedApiKey(apiKeys[0].keys?.[0]?.key)}},[apiKeys]);useEffect(()=>{const element=codeRef.current;if(!element)return;const unmaskRenderedApiKey=(content,apiKey)=>{return content.replace(/(['"]?)([^:'"]+):\*{5}\1/g,`$1${apiKey}$1`)};const handleCopy=event=>{const selection=window.getSelection();if(!selection||selection.rangeCount===0)return;const selectedText=selection.toString();if(!selectedText)return;const range=selection.getRangeAt(0);if(!element.contains(range.commonAncestorContainer))return;const modifiedText=unmaskRenderedApiKey(selectedText,selectedApiKey);event.clipboardData?.setData("text/plain",modifiedText);event.preventDefault()};document.addEventListener("copy",handleCopy);return()=>{document.removeEventListener("copy",handleCopy)}},[selectedApiKey]);const extractLanguageFromCode=useCallback(codeElement=>{if(!codeElement||!codeElement.props.className)return null;const classNames=codeElement.props.className.split(" ");const langClass=classNames.find(cls=>cls.startsWith("language-"));if(!langClass)return null;return langClass.substring(9)},[]);const findCodeElement=useCallback(preChildren=>{if(isValidElement(preChildren)){return preChildren}if(Array.isArray(preChildren)){const codeEl=preChildren.find(c=>isValidElement(c));return codeEl&&isValidElement(codeEl)?codeEl:null}return null},[]);const{codeData,languages,sdkTypes,isSinglePlainCommand}=useMemo(()=>{const childrenArray=Children.toArray(children);const languages=[];const sdkTypes=new Set;const codeData=[];const isSinglePlainCommand=childrenArray.length===1&&["language-shell","language-text"].some(lang=>{if(!isValidElement(childrenArray[0]))return false;const codeEl=findCodeElement(childrenArray[0].props.children);return codeEl?.props.className?.includes(lang)});childrenArray.forEach(child=>{if(!isValidElement(child))return;const preElement=child;const codeElement=findCodeElement(preElement.props.children);if(!codeElement)return;const codeLanguage=extractLanguageFromCode(codeElement);if(!codeLanguage)return;if(codeLanguage.startsWith("realtime_")){sdkTypes.add("realtime")}else if(codeLanguage.startsWith("rest_")){sdkTypes.add("rest")}else if(codeLanguage.startsWith("fe_")){sdkTypes.add("fe")}else if(codeLanguage.startsWith("be_")){sdkTypes.add("be")}if(!languages.includes(codeLanguage)){languages.push(codeLanguage)}const codeContent=codeElement.props.children;codeData.push({language:codeLanguage,content:codeContent})});return{codeData,languages,sdkTypes,isSinglePlainCommand}},[children,extractLanguageFromCode,findCodeElement]);const resolvedSdk=useMemo(()=>{if(sdkTypes.size===1&&sdk&&!sdkTypes.has(sdk)){return Array.from(sdkTypes)[0]}return sdk??null},[sdk,sdkTypes]);const showSDKSelector=sdkTypes.has("realtime")||sdkTypes.has("rest");const filteredLanguages=useMemo(()=>{const filtered=!resolvedSdk||!showSDKSelector?[...languages]:languages.filter(lang=>lang.startsWith(`${resolvedSdk}_`));if(languageOrdering&&languageOrdering.length>0){filtered.sort((a,b)=>{const aBase=stripSdkType(a);const bBase=stripSdkType(b);const aIndex=languageOrdering.indexOf(aBase);const bIndex=languageOrdering.indexOf(bBase);if(aIndex!==-1&&bIndex!==-1)return aIndex-bIndex;if(aIndex!==-1)return-1;if(bIndex!==-1)return 1;return 0})}return filtered},[resolvedSdk,showSDKSelector,languages,languageOrdering]);const activeLanguage=useMemo(()=>{if(resolvedSdk==="fe"||resolvedSdk==="be"){const fullLang=`${resolvedSdk}_${lang}`;if(languages.includes(fullLang)){return fullLang}const prefixMatch=languages.find(l=>l.startsWith(`${resolvedSdk}_`));if(prefixMatch)return prefixMatch}if(resolvedSdk&&sdkTypes.has(resolvedSdk)){return`${resolvedSdk}_${lang}`}if(lang)return lang;if(filteredLanguages.length>0)return filteredLanguages[0];return languages[0]},[resolvedSdk,sdkTypes,lang,filteredLanguages,languages]);const requiresApiKeySubstitution=useMemo(()=>{const containsPlaceholder=codeData.some(code=>code?.content.includes("{{API_KEY}}"));return containsPlaceholder&&!!apiKeys&&apiKeys.length>0&&!!selectedApiKey},[codeData,apiKeys,selectedApiKey]);const[isHovering,setIsHovering]=useState(false);const hasOnlyJsonSnippet=useMemo(()=>languages.length===1&&languages[0]==="json",[languages]);const processedChildren=useMemo(()=>{if(!activeLanguage)return[];const targetLanguage=hasOnlyJsonSnippet?"json":activeLanguage;return codeData.filter(code=>{return code?.language===targetLanguage}).map(code=>{if(!code)return null;const cleanLang=hasOnlyJsonSnippet?"json":code.language;const langInfo=getLanguageInfo(cleanLang??"");if(typeof code.content==="string"||typeof code.content==="number"||typeof code.content==="boolean"){let processedContent=String(code.content);if(requiresApiKeySubstitution){processedContent=substituteApiKey(processedContent,selectedApiKey)}if(!langInfo.syntaxHighlighterKey||!cleanLang)return null;return React.createElement(Code,{key:code.language,language:langInfo.syntaxHighlighterKey||cleanLang,snippet:processedContent,additionalCSS:"!bg-neutral-000 text-neutral-1300 dark:!bg-neutral-1300 dark:text-neutral-200 px-6 py-4",showLines:showCodeLines,wrap:wrapCode})}return null})},[activeLanguage,hasOnlyJsonSnippet,codeData,requiresApiKeySubstitution,showCodeLines,wrapCode,selectedApiKey]);const hasSnippetForActiveLanguage=useMemo(()=>{if(!activeLanguage)return false;if(hasOnlyJsonSnippet)return true;return codeData.some(code=>{return code?.language===activeLanguage})},[activeLanguage,hasOnlyJsonSnippet,codeData]);const handleSDKTypeChange=useCallback(type=>{const nextLang=stripSdkType(languages.find(l=>l===`${type}_${stripSdkType(activeLanguage)}`)??languages.find(l=>l.startsWith(`${type}_`))??activeLanguage);if(onChange&&nextLang){onChange(stripSdkType(activeLanguage),type)}},[activeLanguage,languages,onChange]);const handleLanguageChange=useCallback(language=>{if(onChange){onChange(stripSdkType(language),resolvedSdk)}},[onChange,resolvedSdk]);const noSnippetMessage=useMemo(()=>{if(!activeLanguage)return null;const activeLanguageInfo=getLanguageInfo(activeLanguage);return React.createElement("div",{className:"px-16 py-6 ui-text-body2 text-neutral-800 dark:text-neutral-400 text-center flex flex-col gap-3 items-center"},React.createElement(Icon,{name:"icon-gui-exclamation-triangle-outline",color:"text-yellow-600 dark:text-yellow-400",size:"24px"}),React.createElement("p",{className:"ui-text-p3 text-neutral-700 dark:text-neutral-600"},"You're currently viewing the ",activeLanguageInfo.label," docs. There either isn't a ",activeLanguageInfo.label," code sample for this example, or this feature isn't supported in"," ",activeLanguageInfo.label,". Switch language to view this example in a different language, or check which SDKs support this feature."))},[activeLanguage]);const showLanguageSelector=!fixed&&filteredLanguages.length>0;const showFullSelector=filteredLanguages.length>1;const showFixedLanguageLabel=fixed&&activeLanguage;const renderContent=useMemo(()=>{if(!activeLanguage)return null;if(hasSnippetForActiveLanguage){return processedChildren}return noSnippetMessage},[activeLanguage,hasSnippetForActiveLanguage,processedChildren,noSnippetMessage]);if(isSinglePlainCommand){const plainChild=codeData[0];if(plainChild){const codeContent=plainChild.content;const language=plainChild.language;if(!language||!codeContent)return null;let processedContent=String(codeContent);if(requiresApiKeySubstitution){processedContent=substituteApiKey(processedContent,selectedApiKey)}return React.createElement(PlainCodeView,{content:processedContent,className:className,language:language,icon:language==="shell"?"icon-gui-command-line-outline":null})}}return React.createElement("div",{className:cn("rounded-lg overflow-hidden bg-neutral-100 dark:bg-neutral-1200 border border-neutral-300 dark:border-neutral-1000 min-h-[3.375rem]",className)},headerRow&&React.createElement("div",{className:"h-[2.375rem] bg-neutral-200 dark:bg-neutral-1100 border-b border-neutral-300 dark:border-neutral-1000 flex items-center py-1 px-3 rounded-t-lg"},React.createElement("div",{className:"flex space-x-1.5"},React.createElement("div",{className:"w-3 h-3 rounded-full bg-orange-500"}),React.createElement("div",{className:"w-3 h-3 rounded-full bg-yellow-500"}),React.createElement("div",{className:"w-3 h-3 rounded-full bg-green-500"})),React.createElement("div",{className:"flex-1 text-center ui-text-p3 font-bold text-neutral-1300 dark:text-neutral-000"},title),React.createElement("div",{className:"w-12"})),showSDKSelector&&React.createElement("div",{className:cn("p-2 border-b border-neutral-300 dark:border-neutral-1000",sdkTypes.size===1&&"p-1",headerRow?"":"rounded-t-lg")},React.createElement("div",{className:"flex gap-1 justify-start"},["realtime","rest"].map(type=>sdkTypes.has(type)&&React.createElement(SegmentedControl,{key:type,onClick:()=>handleSDKTypeChange(type),size:"xs",active:resolvedSdk===type,className:cn("text-[11px] font-semibold px-2 py-1 h-auto",sdkTypes.size===1&&"pointer-events-none bg-neutral-100 dark:bg-neutral-1200 !text-neutral-800 !dark:text-neutral-500",sdkTypes.size>1&&resolvedSdk!==type&&"bg-neutral-100 dark:bg-neutral-1200 hover:bg-neutral-200 dark:hover:bg-neutral-1100 active:bg-neutral-400 dark:active:bg-neutral-900",sdkTypes.size>1&&resolvedSdk===type&&"bg-neutral-000 dark:bg-neutral-1100")},type==="realtime"?"Realtime":"REST")))),showFixedLanguageLabel&&React.createElement("div",{className:cn("border-b border-neutral-300 dark:border-neutral-1000 h-[2.125rem] inline-flex items-center px-3 w-full",{"rounded-t-lg":!headerRow})},React.createElement("div",{className:"inline-flex items-center"},React.createElement(Icon,{name:getLanguageInfo(activeLanguage).icon,size:"16px",additionalCSS:"mr-2"}),React.createElement("span",{className:"ui-text-label4 font-semibold text-neutral-800 dark:text-neutral-500 select-none"},getLanguageInfo(activeLanguage).label))),showLanguageSelector&&(showFullSelector?React.createElement(LanguageSelector,{languages:filteredLanguages,activeLanguage:activeLanguage,onLanguageChange:handleLanguageChange}):React.createElement("div",{className:cn("border-b border-neutral-300 dark:border-neutral-1000 h-[2.125rem] inline-flex items-center px-3 w-full",{"rounded-t-lg":!headerRow})},filteredLanguages.length>0&&React.createElement("div",{className:cn("inline-flex items-center",{"cursor-pointer":filteredLanguages.length>0}),...filteredLanguages.length>0&&{onClick:()=>handleLanguageChange(filteredLanguages[0])}},React.createElement(Icon,{name:getLanguageInfo(filteredLanguages[0]).icon,size:"16px",additionalCSS:"mr-2"}),React.createElement("span",{className:"ui-text-label4 font-semibold text-neutral-800 dark:text-neutral-500 select-none"},getLanguageInfo(filteredLanguages[0]).label)))),React.createElement("div",{ref:codeRef,className:"relative",onMouseEnter:()=>setIsHovering(true),onMouseLeave:()=>setIsHovering(false),onFocus:()=>setIsHovering(true),onBlur:()=>setIsHovering(false)},renderContent,isHovering&&activeLanguage&&hasSnippetForActiveLanguage&&React.createElement(CopyButton,{onCopy:()=>{const text=codeData.find(code=>code.language===activeLanguage)?.content;if(text)navigator.clipboard.writeText(substituteApiKey(text,selectedApiKey,false))}})),requiresApiKeySubstitution&&React.createElement(ApiKeySelector,{apiKeys:apiKeys,selectedApiKey:selectedApiKey,onApiKeyChange:setSelectedApiKey}))};export default CodeSnippet;
|
|
2
2
|
//# sourceMappingURL=CodeSnippet.js.map
|