@donotdev/components 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/atomic/Button/index.js +3 -3
- package/dist/atomic/CallToAction/index.d.ts +2 -2
- package/dist/atomic/CallToAction/index.d.ts.map +1 -1
- package/dist/atomic/CallToAction/index.js +1 -1
- package/dist/atomic/Card/index.d.ts.map +1 -1
- package/dist/atomic/Card/index.js +1 -1
- package/dist/atomic/List/index.d.ts +1 -1
- package/dist/atomic/List/index.js +1 -1
- package/dist/atomic/Tooltip/index.d.ts +14 -22
- package/dist/atomic/Tooltip/index.d.ts.map +1 -1
- package/dist/atomic/Tooltip/index.js +50 -55
- package/dist/atomic/VideoPlayer/index.d.ts +0 -67
- package/dist/atomic/VideoPlayer/index.d.ts.map +1 -1
- package/dist/atomic/VideoPlayer/index.js +26 -14
- package/dist/hooks/useIntersectionObserver.d.ts +63 -45
- package/dist/hooks/useIntersectionObserver.d.ts.map +1 -1
- package/dist/hooks/useIntersectionObserver.js +192 -49
- package/dist/index.js +4 -4
- package/dist/styles/index.css +360 -30
- package/package.json +1 -1
|
@@ -106,13 +106,13 @@ const Button = ({ className, variant, render, display = DISPLAY.AUTO, icon, icon
|
|
|
106
106
|
// Render prop pattern - no cloneElement needed (React 19 compatible)
|
|
107
107
|
const buttonElement = render ? (render(elementProps)) : (_jsx("button", { type: props.type || 'button', role: "button", tabIndex: 0, ...elementProps, children: buttonContent }));
|
|
108
108
|
// Tooltip: COMPACT/AUTO (may be icon-only when collapsed), others only if explicit
|
|
109
|
-
//
|
|
109
|
+
// Don't specify side - let CSS --tooltip-side take priority (RTL-aware in sidebars)
|
|
110
110
|
if (effectiveDisplay === DISPLAY.COMPACT ||
|
|
111
111
|
effectiveDisplay === DISPLAY.AUTO) {
|
|
112
|
-
return (_jsx(Tooltip, { content: tooltip || getAriaLabel() || 'Button',
|
|
112
|
+
return (_jsx(Tooltip, { content: tooltip || getAriaLabel() || 'Button', children: buttonElement }));
|
|
113
113
|
}
|
|
114
114
|
if (tooltip) {
|
|
115
|
-
return (_jsx(Tooltip, { content: tooltip,
|
|
115
|
+
return (_jsx(Tooltip, { content: tooltip, children: buttonElement }));
|
|
116
116
|
}
|
|
117
117
|
return buttonElement;
|
|
118
118
|
};
|
|
@@ -61,9 +61,9 @@ interface CallToActionOwnProps {
|
|
|
61
61
|
align?: 'start' | 'center' | 'end';
|
|
62
62
|
/**
|
|
63
63
|
* Tone system for background colors (matches Section component)
|
|
64
|
-
* @default '
|
|
64
|
+
* @default 'ghost'
|
|
65
65
|
*/
|
|
66
|
-
tone?: 'base' | 'muted' | 'elevated' | 'contrast' | 'accent';
|
|
66
|
+
tone?: 'ghost' | 'base' | 'muted' | 'elevated' | 'contrast' | 'accent';
|
|
67
67
|
/** Additional children (for custom content) */
|
|
68
68
|
children?: ReactNode;
|
|
69
69
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/atomic/CallToAction/index.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAC1B,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAKf,OAAO,oBAAoB,CAAC;AAE5B;;GAEG;AACH,UAAU,oBAAoB;IAC5B;;;;;;OAMG;IACH,EAAE,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,KAAK,CAAC;IAEjC,iBAAiB;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,8BAA8B;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,qEAAqE;IACrE,aAAa,CAAC,EAAE,SAAS,CAAC;IAE1B,uEAAuE;IACvE,eAAe,CAAC,EAAE,SAAS,CAAC;IAE5B;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;IAEnC;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/atomic/CallToAction/index.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAC1B,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAKf,OAAO,oBAAoB,CAAC;AAE5B;;GAEG;AACH,UAAU,oBAAoB;IAC5B;;;;;;OAMG;IACH,EAAE,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,KAAK,CAAC;IAEjC,iBAAiB;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,8BAA8B;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,qEAAqE;IACrE,aAAa,CAAC,EAAE,SAAS,CAAC;IAE1B,uEAAuE;IACvE,eAAe,CAAC,EAAE,SAAS,CAAC;IAE5B;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;IAEnC;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,QAAQ,CAAC;IAEvE,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;GAGG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,WAAW,GAAG,SAAS,IAC7D,oBAAoB,GAClB,IAAI,CAAC,qBAAqB,CAAC,CAAC,CAAC,EAAE,MAAM,oBAAoB,CAAC,CAAC;AAE/D,QAAA,MAAM,YAAY,mIA+CjB,CAAC;AAIF,eAAe,YAAY,CAAC"}
|
|
@@ -38,7 +38,7 @@ import { createElement, forwardRef, } from 'react';
|
|
|
38
38
|
import { cn } from '../../utils/helpers';
|
|
39
39
|
import Text, { TEXT_VARIANT } from '../Text';
|
|
40
40
|
import './CallToAction.css';
|
|
41
|
-
const CallToAction = forwardRef(function CallToAction({ as = 'section', title, subtitle, primaryAction, secondaryAction, align = 'center', tone = '
|
|
41
|
+
const CallToAction = forwardRef(function CallToAction({ as = 'section', title, subtitle, primaryAction, secondaryAction, align = 'center', tone = 'ghost', children, className, ...props }, ref) {
|
|
42
42
|
const Component = as;
|
|
43
43
|
return createElement(Component, {
|
|
44
44
|
ref,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/atomic/Card/index.tsx"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAI7D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAOvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAE5D;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC;AAExD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,SAAS,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/atomic/Card/index.tsx"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAI7D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAOvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAE5D;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC;AAExD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,SAAS,GAAG,SAAS,CA0B7E;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,CAAC,EAAE,QAAQ,EACf,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,EAC1B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,GAC5B,SAAS,CA0BX;AAED;;;GAGG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;CAAkB,CAAC;AAE5C;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,YAAY,CAAC,OAAO,eAAe,CAAC,CAAC,SAAS,CAAC,CAAC;AAE1E,MAAM,WAAW,SACf,SACE,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC,EACzD,YAAY,CAAC,OAAO,eAAe,CAAC;IACtC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iDAAiD;IACjD,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,qBAAqB;IACrB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,mBAAmB;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oBAAoB;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sCAAsC;IACtC,GAAG,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,QAAA,MAAM,IAAI,GAAI,gJAiBX,SAAS,4CA0CX,CAAC;AAEF,eAAe,IAAI,CAAC"}
|
|
@@ -43,7 +43,7 @@ export function renderCardContent(content) {
|
|
|
43
43
|
if (content.length === 1 && typeof content[0] === 'string') {
|
|
44
44
|
return (_jsx(Text, { as: "p", level: "small", children: content[0] }));
|
|
45
45
|
}
|
|
46
|
-
return (_jsx(List, { items: content, style: { listStyle: 'none', paddingInlineStart: 0 } }));
|
|
46
|
+
return (_jsx(List, { items: content, gap: "tight", style: { listStyle: 'none', paddingInlineStart: 0 } }));
|
|
47
47
|
}
|
|
48
48
|
return content;
|
|
49
49
|
}
|
|
@@ -33,7 +33,7 @@ import './List.css';
|
|
|
33
33
|
* @param {ListProps} props - The props for the list
|
|
34
34
|
* @returns {JSX.Element} The rendered list component
|
|
35
35
|
*/
|
|
36
|
-
const List = ({ items = [], ordered = false, icon, gap = GAP_VARIANT.
|
|
36
|
+
const List = ({ items = [], ordered = false, icon, gap = GAP_VARIANT.TIGHT, density = 'default', className, ...props }) => {
|
|
37
37
|
const Component = ordered ? 'ol' : 'ul';
|
|
38
38
|
return (_jsx(Component, { className: cn('dndev-list', className), "data-ordered": ordered ? 'true' : undefined, "data-gap": gap, "data-density": density, ...props, children: items.map((item, index) => {
|
|
39
39
|
// Handle string/number - apply container icon if provided
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Tooltip component
|
|
3
|
-
* @description Accessible tooltip
|
|
3
|
+
* @description Accessible tooltip built on Radix UI primitives.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Positioning: prop > CSS --tooltip-side > 'bottom'
|
|
6
|
+
* Smart hiding: Tooltip auto-hides when trigger has visible label
|
|
7
|
+
*
|
|
8
|
+
* @version 0.0.6
|
|
6
9
|
* @since 0.0.1
|
|
7
10
|
* @author AMBROISE PARK Consulting
|
|
8
11
|
*/
|
|
@@ -13,36 +16,25 @@ type TooltipSide = 'top' | 'right' | 'bottom' | 'left';
|
|
|
13
16
|
export interface TooltipProps {
|
|
14
17
|
content: string | ReactNode;
|
|
15
18
|
children: ReactNode;
|
|
16
|
-
/**
|
|
17
|
-
* Tooltip position. If not set, reads from CSS `--tooltip-side` property,
|
|
18
|
-
* then falls back to Radix auto-positioning.
|
|
19
|
-
*/
|
|
19
|
+
/** Tooltip position. Priority: prop > CSS --tooltip-side > 'bottom' */
|
|
20
20
|
side?: TooltipSide;
|
|
21
21
|
align?: 'start' | 'center' | 'end';
|
|
22
22
|
delayDuration?: number;
|
|
23
23
|
variant?: FloatingVariant;
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
|
-
* Accessible tooltip component
|
|
27
|
-
*
|
|
28
|
-
* Positioning priority:
|
|
29
|
-
* 1. Explicit `side` prop
|
|
30
|
-
* 2. CSS `--tooltip-side` property (set by container like Sidebar)
|
|
31
|
-
* 3. Radix auto-positioning (default)
|
|
26
|
+
* Accessible tooltip component.
|
|
32
27
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* be available. This component gracefully degrades by rendering just children
|
|
36
|
-
* during SSR, then hydrating with full tooltip functionality on client.
|
|
28
|
+
* Auto-hides when trigger has visible label (e.g., button not in compact mode).
|
|
29
|
+
* Only shows tooltip when label is hidden, avoiding redundant information.
|
|
37
30
|
*
|
|
38
31
|
* @example
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* [dir='rtl'] .sidebar { --tooltip-side: left; }
|
|
42
|
-
* ```
|
|
32
|
+
* // Explicit side
|
|
33
|
+
* <Tooltip content="Help" side="right">...</Tooltip>
|
|
43
34
|
*
|
|
44
|
-
* @
|
|
45
|
-
*
|
|
35
|
+
* @example
|
|
36
|
+
* // Container-aware (sidebar sets --tooltip-side: right)
|
|
37
|
+
* <Tooltip content="Help">...</Tooltip>
|
|
46
38
|
*/
|
|
47
39
|
declare const Tooltip: ({ content, children, side, align, delayDuration, variant, }: TooltipProps) => import("react/jsx-runtime").JSX.Element;
|
|
48
40
|
export default Tooltip;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/atomic/Tooltip/index.tsx"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/atomic/Tooltip/index.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AAEH,OAAyB,EAGvB,eAAe,EAChB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAoB,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAI/E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,KAAK,WAAW,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEvD,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,QAAQ,EAAE,SAAS,CAAC;IACpB,uEAAuE;IACvE,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,eAAe,CAAC;CAC3B;AAYD;;;;;;;;;;;;;GAaG;AACH,QAAA,MAAM,OAAO,GAAI,6DAOd,YAAY,4CA0Dd,CAAC;AAEF,eAAe,OAAO,CAAC;AACvB,OAAO,EAAE,eAAe,EAAE,CAAC"}
|
|
@@ -1,83 +1,78 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// packages/components/src/atomic/Tooltip/index.tsx
|
|
3
3
|
/**
|
|
4
4
|
* @fileoverview Tooltip component
|
|
5
|
-
* @description Accessible tooltip
|
|
5
|
+
* @description Accessible tooltip built on Radix UI primitives.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* Positioning: prop > CSS --tooltip-side > 'bottom'
|
|
8
|
+
* Smart hiding: Tooltip auto-hides when trigger has visible label
|
|
9
|
+
*
|
|
10
|
+
* @version 0.0.6
|
|
8
11
|
* @since 0.0.1
|
|
9
12
|
* @author AMBROISE PARK Consulting
|
|
10
13
|
*/
|
|
11
14
|
import TooltipPrimitive, { TooltipTrigger, TooltipContent, TooltipProvider, } from './TooltipPrimitive';
|
|
12
15
|
import { FLOATING_VARIANT } from '../../utils/constants';
|
|
13
16
|
import { getVariantDataAttrs } from '../../utils/helpers';
|
|
14
|
-
import { useRef, useState
|
|
17
|
+
import { useCallback, useRef, useState } from 'react';
|
|
15
18
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
* Check if trigger element has a visible label.
|
|
20
|
+
* Returns true if label exists and has width (not hidden).
|
|
21
|
+
*/
|
|
22
|
+
function hasVisibleLabel(element) {
|
|
23
|
+
if (!element)
|
|
24
|
+
return false;
|
|
25
|
+
const label = element.querySelector('.dndev-interactive-label');
|
|
26
|
+
return label instanceof HTMLElement && label.offsetWidth > 0;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Accessible tooltip component.
|
|
22
30
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* be available. This component gracefully degrades by rendering just children
|
|
26
|
-
* during SSR, then hydrating with full tooltip functionality on client.
|
|
31
|
+
* Auto-hides when trigger has visible label (e.g., button not in compact mode).
|
|
32
|
+
* Only shows tooltip when label is hidden, avoiding redundant information.
|
|
27
33
|
*
|
|
28
34
|
* @example
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* [dir='rtl'] .sidebar { --tooltip-side: left; }
|
|
32
|
-
* ```
|
|
35
|
+
* // Explicit side
|
|
36
|
+
* <Tooltip content="Help" side="right">...</Tooltip>
|
|
33
37
|
*
|
|
34
|
-
* @
|
|
35
|
-
*
|
|
38
|
+
* @example
|
|
39
|
+
* // Container-aware (sidebar sets --tooltip-side: right)
|
|
40
|
+
* <Tooltip content="Help">...</Tooltip>
|
|
36
41
|
*/
|
|
37
42
|
const Tooltip = ({ content, children, side, align = 'center', delayDuration = 300, variant, }) => {
|
|
38
|
-
const
|
|
39
|
-
const [
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
useEffect(() => {
|
|
49
|
-
// SSR guard + read CSS property first (highest priority)
|
|
50
|
-
if (typeof window !== 'undefined' && triggerRef.current) {
|
|
51
|
-
const cssValue = getComputedStyle(triggerRef.current)
|
|
43
|
+
const [mounted, setMounted] = useState(false);
|
|
44
|
+
const [cssSide, setCssSide] = useState(null);
|
|
45
|
+
const [open, setOpen] = useState(false);
|
|
46
|
+
const triggerElement = useRef(null);
|
|
47
|
+
// Callback ref - fires when element mounts, reads CSS var immediately
|
|
48
|
+
const triggerRef = useCallback((node) => {
|
|
49
|
+
triggerElement.current = node;
|
|
50
|
+
if (node) {
|
|
51
|
+
setMounted(true);
|
|
52
|
+
const val = getComputedStyle(node)
|
|
52
53
|
.getPropertyValue('--tooltip-side')
|
|
53
54
|
.trim();
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
55
|
+
if (val)
|
|
56
|
+
setCssSide(val);
|
|
58
57
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
}, []);
|
|
59
|
+
// Handle open change - skip if label is visible
|
|
60
|
+
const handleOpenChange = useCallback((nextOpen) => {
|
|
61
|
+
if (nextOpen && hasVisibleLabel(triggerElement.current)) {
|
|
62
|
+
return; // Label visible, don't show tooltip
|
|
63
63
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
// After hydration, full tooltip functionality is available
|
|
70
|
-
if (!isMounted) {
|
|
71
|
-
return _jsx(_Fragment, { children: children });
|
|
64
|
+
setOpen(nextOpen);
|
|
65
|
+
}, []);
|
|
66
|
+
// SSR: render children only
|
|
67
|
+
if (!mounted) {
|
|
68
|
+
return (_jsx(TooltipPrimitive, { delayDuration: delayDuration, children: _jsx(TooltipTrigger, { asChild: true, ref: triggerRef, children: children }) }));
|
|
72
69
|
}
|
|
70
|
+
// Priority: prop > CSS > 'bottom'
|
|
71
|
+
const finalSide = side ?? cssSide ?? 'bottom';
|
|
73
72
|
const variantAttrs = getVariantDataAttrs({
|
|
74
73
|
variant: variant !== FLOATING_VARIANT.DEFAULT ? variant : undefined,
|
|
75
74
|
});
|
|
76
|
-
|
|
77
|
-
const setTriggerRef = (node) => {
|
|
78
|
-
triggerRef.current = node;
|
|
79
|
-
};
|
|
80
|
-
return (_jsxs(TooltipPrimitive, { delayDuration: delayDuration, children: [_jsx(TooltipTrigger, { asChild: true, ref: setTriggerRef, children: children }), _jsx(TooltipContent, { side: computedSide, align: align, ...variantAttrs, children: content })] }));
|
|
75
|
+
return (_jsxs(TooltipPrimitive, { delayDuration: delayDuration, open: open, onOpenChange: handleOpenChange, children: [_jsx(TooltipTrigger, { asChild: true, ref: triggerRef, children: children }), _jsx(TooltipContent, { side: finalSide, align: align, ...variantAttrs, children: content })] }));
|
|
81
76
|
};
|
|
82
77
|
export default Tooltip;
|
|
83
78
|
export { TooltipProvider };
|
|
@@ -98,73 +98,6 @@ export interface VideoPlayerProps {
|
|
|
98
98
|
*/
|
|
99
99
|
allowFullscreen?: boolean;
|
|
100
100
|
}
|
|
101
|
-
/**
|
|
102
|
-
* Video player component with lazy-loading facade pattern for optimal Lighthouse scores.
|
|
103
|
-
*
|
|
104
|
-
* **Lazy-Loading Behavior:**
|
|
105
|
-
* - Shows thumbnail facade initially (zero iframe resources)
|
|
106
|
-
* - Lazy-loads iframe only on user click/interaction
|
|
107
|
-
* - Works for both inline (`modal={false}`) and modal modes
|
|
108
|
-
*
|
|
109
|
-
* **Thumbnail Priority:**
|
|
110
|
-
* 1. Custom `thumbnail` prop (if provided)
|
|
111
|
-
* 2. Auto-generated YouTube thumbnail (if `url` is VideoConfig with `platform: 'youtube'`)
|
|
112
|
-
* 3. Button fallback (if no thumbnail available)
|
|
113
|
-
*
|
|
114
|
-
* **URL Formats:**
|
|
115
|
-
* - **String URL**: Full embed URL as-is. Requires manual `thumbnail` prop.
|
|
116
|
-
* - **VideoConfig Object**: Framework constructs privacy-first URL. Auto-generates YouTube thumbnails.
|
|
117
|
-
*
|
|
118
|
-
* @component
|
|
119
|
-
* @example
|
|
120
|
-
* ```tsx
|
|
121
|
-
* // Auto-thumbnail (YouTube VideoConfig) - recommended
|
|
122
|
-
* <VideoPlayer
|
|
123
|
-
* url={{ platform: 'youtube', id: 'dQw4w9WgXcQ' }}
|
|
124
|
-
* title="Tutorial Video"
|
|
125
|
-
* modal={false}
|
|
126
|
-
* />
|
|
127
|
-
* // Thumbnail auto-generated from YouTube
|
|
128
|
-
*
|
|
129
|
-
* // Custom thumbnail override
|
|
130
|
-
* <VideoPlayer
|
|
131
|
-
* url={{ platform: 'youtube', id: 'dQw4w9WgXcQ' }}
|
|
132
|
-
* thumbnail="/custom-screenshot.jpg"
|
|
133
|
-
* title="Tutorial Video"
|
|
134
|
-
* modal={false}
|
|
135
|
-
* />
|
|
136
|
-
* // Uses custom thumbnail instead of auto-generated
|
|
137
|
-
*
|
|
138
|
-
* // String URL - manual thumbnail required
|
|
139
|
-
* <VideoPlayer
|
|
140
|
-
* url="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ"
|
|
141
|
-
* thumbnail="/custom-thumb.jpg" // Required
|
|
142
|
-
* title="Product Demo"
|
|
143
|
-
* modal={false}
|
|
144
|
-
* />
|
|
145
|
-
*
|
|
146
|
-
* // Modal mode (lazy-loads iframe when modal opens)
|
|
147
|
-
* <VideoPlayer
|
|
148
|
-
* url={{ platform: 'youtube', id: 'dQw4w9WgXcQ' }}
|
|
149
|
-
* title="Tutorial Video"
|
|
150
|
-
* modal={true} // default
|
|
151
|
-
* />
|
|
152
|
-
*
|
|
153
|
-
* // Above-the-fold (eager load thumbnail for LCP)
|
|
154
|
-
* <VideoPlayer
|
|
155
|
-
* url={{ platform: 'youtube', id: 'dQw4w9WgXcQ' }}
|
|
156
|
-
* eager={true}
|
|
157
|
-
* title="Hero Video"
|
|
158
|
-
* modal={false}
|
|
159
|
-
* />
|
|
160
|
-
* ```
|
|
161
|
-
* @param {VideoPlayerProps} props - The props for the video player
|
|
162
|
-
* @returns {JSX.Element} The rendered video player
|
|
163
|
-
*
|
|
164
|
-
* @version 0.0.1
|
|
165
|
-
* @since 0.0.1
|
|
166
|
-
* @author AMBROISE PARK Consulting
|
|
167
|
-
*/
|
|
168
101
|
declare const VideoPlayer: ({ url, trigger, thumbnail, eager, title, modal, aspectRatio, className, autoplay, allowFullscreen, }: VideoPlayerProps) => import("react/jsx-runtime").JSX.Element;
|
|
169
102
|
export default VideoPlayer;
|
|
170
103
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/atomic/VideoPlayer/index.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/atomic/VideoPlayer/index.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,qBAAqB;IACrB,QAAQ,EAAE,SAAS,GAAG,OAAO,CAAC;IAC9B,eAAe;IACf,EAAE,EAAE,MAAM,CAAC;IACX;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,GAAG,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,qFAAqF;IACrF,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB;;;;;;;;;;;;;OAaG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,oCAAoC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wBAAwB;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AA+JD,QAAA,MAAM,WAAW,GAAI,sGAWlB,gBAAgB,4CAwJlB,CAAC;AAEF,eAAe,WAAW,CAAC"}
|
|
@@ -9,8 +9,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
9
9
|
* @author AMBROISE PARK Consulting
|
|
10
10
|
*/
|
|
11
11
|
import { Play } from 'lucide-react';
|
|
12
|
-
import { useState, useEffect } from 'react';
|
|
12
|
+
import { useState, useEffect, useRef } from 'react';
|
|
13
13
|
import { useTranslation } from '@donotdev/core';
|
|
14
|
+
import { useIntersectionObserver } from '@donotdev/components';
|
|
14
15
|
import { cn } from '../../utils/helpers';
|
|
15
16
|
import Button, { BUTTON_VARIANT } from '../Button';
|
|
16
17
|
import Dialog from '../Dialog';
|
|
@@ -62,7 +63,7 @@ const getThumbnailUrl = (urlOrConfig) => {
|
|
|
62
63
|
return null;
|
|
63
64
|
}
|
|
64
65
|
if (urlOrConfig.platform === 'youtube') {
|
|
65
|
-
return `https://img.youtube.com/vi/${urlOrConfig.id}/
|
|
66
|
+
return `https://img.youtube.com/vi/${urlOrConfig.id}/hqdefault.jpg`;
|
|
66
67
|
}
|
|
67
68
|
return null;
|
|
68
69
|
};
|
|
@@ -133,13 +134,28 @@ const getThumbnailUrl = (urlOrConfig) => {
|
|
|
133
134
|
* @since 0.0.1
|
|
134
135
|
* @author AMBROISE PARK Consulting
|
|
135
136
|
*/
|
|
137
|
+
/**
|
|
138
|
+
* SVG placeholder for video player - zero bytes, instant render
|
|
139
|
+
* Lazy-loads actual thumbnail on intersection
|
|
140
|
+
*/
|
|
141
|
+
const VideoPlaceholder = ({ aspectRatio, className, }) => (_jsxs("svg", { className: cn('dndev-video-placeholder', className), viewBox: "0 0 16 9", style: { aspectRatio, width: '100%', display: 'block' }, preserveAspectRatio: "xMidYMid meet", "aria-hidden": "true", children: [_jsx("rect", { width: "16", height: "9", fill: "var(--muted)" }), _jsx("circle", { cx: "8", cy: "4.5", r: "1.5", fill: "var(--foreground)", opacity: "0.8" }), _jsx("path", { d: "M7 3.5L7 5.5L9 4.5Z", fill: "var(--background)" })] }));
|
|
136
142
|
const VideoPlayer = ({ url = { platform: 'youtube', id: 'dQw4w9WgXcQ' }, trigger, thumbnail, eager = false, title = 'Video', modal = true, aspectRatio = '16/9', className, autoplay = false, allowFullscreen = true, }) => {
|
|
137
143
|
const { t } = useTranslation(['dndev']);
|
|
138
144
|
const [isOpen, setIsOpen] = useState(false);
|
|
139
145
|
const [isLoaded, setIsLoaded] = useState(false);
|
|
140
146
|
const [iframeReady, setIframeReady] = useState(false);
|
|
147
|
+
const [thumbnailLoaded, setThumbnailLoaded] = useState(eager);
|
|
141
148
|
const embedUrl = getEmbedUrl(url, autoplay);
|
|
142
149
|
const thumbnailUrl = thumbnail || getThumbnailUrl(url);
|
|
150
|
+
// Lazy-load thumbnail on intersection (unless eager)
|
|
151
|
+
const { ref: thumbnailRef, isIntersecting } = useIntersectionObserver({
|
|
152
|
+
threshold: 0.1,
|
|
153
|
+
});
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
if (isIntersecting && thumbnailUrl && !eager && !thumbnailLoaded) {
|
|
156
|
+
setThumbnailLoaded(true);
|
|
157
|
+
}
|
|
158
|
+
}, [isIntersecting, thumbnailUrl, eager, thumbnailLoaded]);
|
|
143
159
|
// Lazy-load iframe when modal opens
|
|
144
160
|
useEffect(() => {
|
|
145
161
|
if (isOpen && !isLoaded) {
|
|
@@ -153,18 +169,14 @@ const VideoPlayer = ({ url = { platform: 'youtube', id: 'dQw4w9WgXcQ' }, trigger
|
|
|
153
169
|
const videoFrame = (_jsx("iframe", { src: isLoaded ? embedUrl : '', title: title, allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture", allowFullScreen: allowFullscreen, className: cn('dndev-video-frame', className), style: { aspectRatio }, onLoad: () => setIframeReady(true) }));
|
|
154
170
|
// Inline video (no modal) - lazy-load on click
|
|
155
171
|
if (!modal) {
|
|
156
|
-
// Not clicked yet - show thumbnail
|
|
172
|
+
// Not clicked yet - show thumbnail or placeholder
|
|
157
173
|
if (!isLoaded) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}, className: cn('dndev-video-thumbnail', className), style: { aspectRatio }, "aria-label": t('video.clickToWatch', 'Click to watch video'), children: [_jsx("img", { src: thumbnailUrl, alt: title, loading: eager ? 'eager' : 'lazy', fetchPriority: eager ? 'high' : 'low', decoding: "async" }), _jsx("div", { className: "dndev-video-play-overlay", children: _jsx(Play, { className: "dndev-video-play-icon" }) })] }));
|
|
165
|
-
}
|
|
166
|
-
// No thumbnail - show button
|
|
167
|
-
return (_jsx(Button, { icon: Play, onClick: () => setIsLoaded(true), className: className, children: t('video.watchVideo', 'Watch Video') }));
|
|
174
|
+
return (_jsxs("button", { ref: thumbnailRef, type: "button", onClick: () => setIsLoaded(true), onKeyDown: (e) => {
|
|
175
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
176
|
+
e.preventDefault();
|
|
177
|
+
setIsLoaded(true);
|
|
178
|
+
}
|
|
179
|
+
}, className: cn('dndev-video-thumbnail', className), style: { aspectRatio }, "aria-label": t('video.clickToWatch', 'Click to watch video'), children: [thumbnailLoaded && thumbnailUrl ? (_jsx("img", { src: thumbnailUrl, alt: title, loading: "eager", fetchPriority: "high", decoding: "async" })) : (_jsx(VideoPlaceholder, { aspectRatio: aspectRatio })), _jsx("div", { className: "dndev-video-play-overlay", children: _jsx(Play, { className: "dndev-video-play-icon" }) })] }));
|
|
168
180
|
}
|
|
169
181
|
// Clicked - show iframe with thumbnail overlay until iframe loads
|
|
170
182
|
return (_jsxs("div", { className: cn('dndev-video-container', className), style: { aspectRatio, position: 'relative' }, children: [videoFrame, !iframeReady && thumbnailUrl && (_jsxs("div", { className: "dndev-video-thumbnail dndev-video-loading-overlay", style: {
|
|
@@ -174,7 +186,7 @@ const VideoPlayer = ({ url = { platform: 'youtube', id: 'dQw4w9WgXcQ' }, trigger
|
|
|
174
186
|
}, children: [_jsx("img", { src: thumbnailUrl, alt: title, loading: "eager", decoding: "async" }), _jsx("div", { className: "dndev-video-play-overlay dndev-video-loading", children: _jsx(Spinner, { "aria-label": t('video.loading', 'Loading video') }) })] }))] }));
|
|
175
187
|
}
|
|
176
188
|
// Modal mode - lazy-load iframe when modal opens
|
|
177
|
-
const defaultTrigger =
|
|
189
|
+
const defaultTrigger = (_jsxs("button", { ref: thumbnailRef, type: "button", className: "dndev-video-thumbnail", style: { aspectRatio }, "aria-label": t('video.clickToWatch', 'Click to watch video'), children: [thumbnailLoaded && thumbnailUrl ? (_jsx("img", { src: thumbnailUrl, alt: title, loading: "eager", fetchPriority: "high", decoding: "async" })) : (_jsx(VideoPlaceholder, { aspectRatio: aspectRatio })), _jsx("div", { className: "dndev-video-play-overlay", children: _jsx(Play, { className: "dndev-video-play-icon" }) })] }));
|
|
178
190
|
return (_jsx(Dialog, { trigger: trigger || defaultTrigger, title: title, open: isOpen, onOpenChange: setIsOpen, showClose: true, className: "dndev-video-dialog", children: videoFrame }));
|
|
179
191
|
};
|
|
180
192
|
export default VideoPlayer;
|
|
@@ -6,21 +6,11 @@
|
|
|
6
6
|
* @author AMBROISE PARK Consulting
|
|
7
7
|
*/
|
|
8
8
|
export interface UseIntersectionObserverOptions {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* @default 0.1
|
|
12
|
-
*/
|
|
13
|
-
threshold?: number;
|
|
14
|
-
/**
|
|
15
|
-
* Root margin for intersection observer
|
|
16
|
-
* @default '0px'
|
|
17
|
-
*/
|
|
9
|
+
threshold?: number | number[];
|
|
10
|
+
root?: Element | null;
|
|
18
11
|
rootMargin?: string;
|
|
19
|
-
/**
|
|
20
|
-
* Whether to only trigger once
|
|
21
|
-
* @default false
|
|
22
|
-
*/
|
|
23
12
|
once?: boolean;
|
|
13
|
+
fallbackIntersecting?: boolean;
|
|
24
14
|
}
|
|
25
15
|
/**
|
|
26
16
|
* Return type for useIntersectionObserver hook
|
|
@@ -29,54 +19,82 @@ export interface UseIntersectionObserverOptions {
|
|
|
29
19
|
* @since 0.0.1
|
|
30
20
|
* @author AMBROISE PARK Consulting
|
|
31
21
|
*/
|
|
32
|
-
export interface UseIntersectionObserverReturn {
|
|
33
|
-
|
|
34
|
-
* Ref to attach to the element
|
|
35
|
-
*/
|
|
36
|
-
ref: React.RefObject<HTMLElement | null>;
|
|
37
|
-
/**
|
|
38
|
-
* Whether the element is intersecting
|
|
39
|
-
*/
|
|
22
|
+
export interface UseIntersectionObserverReturn<T extends Element = Element> {
|
|
23
|
+
ref: React.RefObject<T | null>;
|
|
40
24
|
isIntersecting: boolean;
|
|
41
|
-
/**
|
|
42
|
-
* Whether the element has been triggered at least once
|
|
43
|
-
*/
|
|
44
25
|
hasTriggered: boolean;
|
|
45
|
-
/**
|
|
46
|
-
* The intersection observer entry
|
|
47
|
-
*/
|
|
48
26
|
entry: IntersectionObserverEntry | null;
|
|
49
27
|
}
|
|
50
28
|
/**
|
|
51
|
-
* Hook
|
|
29
|
+
* useIntersectionObserver - Basic Intersection Observation Hook (React 19 Optimized)
|
|
52
30
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
31
|
+
* Lightweight hook for basic intersection observation with optimal performance.
|
|
32
|
+
* Uses React 19's useSyncExternalStore pattern for automatic cleanup, proper
|
|
33
|
+
* SSR handling, and tearing prevention in concurrent rendering.
|
|
55
34
|
*
|
|
56
|
-
* @
|
|
35
|
+
* @param options - Configuration options for intersection observation
|
|
36
|
+
* @returns Object containing ref, isIntersecting state, and entry
|
|
37
|
+
*
|
|
38
|
+
* @example Basic intersection detection
|
|
39
|
+
* ```tsx
|
|
40
|
+
* function IntersectionComponent() {
|
|
41
|
+
* const { ref, isIntersecting } = useIntersectionObserver();
|
|
42
|
+
*
|
|
43
|
+
* return (
|
|
44
|
+
* <div ref={ref} className={isIntersecting ? 'visible' : 'hidden'}>
|
|
45
|
+
* Content that appears when intersecting
|
|
46
|
+
* </div>
|
|
47
|
+
* );
|
|
48
|
+
* }
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* @example With custom threshold
|
|
57
52
|
* ```tsx
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
53
|
+
* function ThresholdComponent() {
|
|
54
|
+
* const { ref, isIntersecting } = useIntersectionObserver({
|
|
55
|
+
* threshold: 0.5,
|
|
56
|
+
* rootMargin: '50px'
|
|
57
|
+
* });
|
|
61
58
|
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
59
|
+
* return (
|
|
60
|
+
* <div ref={ref}>
|
|
61
|
+
* {isIntersecting ? '50% visible' : 'Not visible enough'}
|
|
62
|
+
* </div>
|
|
63
|
+
* );
|
|
64
|
+
* }
|
|
67
65
|
* ```
|
|
68
66
|
*
|
|
69
|
-
* @example
|
|
67
|
+
* @example Once-only triggering (React 19 optimized)
|
|
70
68
|
* ```tsx
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
69
|
+
* function OnceComponent() {
|
|
70
|
+
* const { ref, isIntersecting } = useIntersectionObserver({ once: true });
|
|
71
|
+
*
|
|
72
|
+
* return (
|
|
73
|
+
* <div ref={ref}>
|
|
74
|
+
* {isIntersecting && <ExpensiveComponent />}
|
|
75
|
+
* </div>
|
|
76
|
+
* );
|
|
77
|
+
* }
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* @example SSR-safe implementation
|
|
81
|
+
* ```tsx
|
|
82
|
+
* function SSRComponent() {
|
|
83
|
+
* const { ref, isIntersecting } = useIntersectionObserver({
|
|
84
|
+
* fallbackIntersecting: true // Shows content during SSR
|
|
85
|
+
* });
|
|
86
|
+
*
|
|
87
|
+
* return (
|
|
88
|
+
* <div ref={ref}>
|
|
89
|
+
* {isIntersecting ? 'Client-side' : 'SSR fallback'}
|
|
90
|
+
* </div>
|
|
91
|
+
* );
|
|
92
|
+
* }
|
|
75
93
|
* ```
|
|
76
94
|
*
|
|
77
95
|
* @version 0.0.1
|
|
78
96
|
* @since 0.0.1
|
|
79
97
|
* @author AMBROISE PARK Consulting
|
|
80
98
|
*/
|
|
81
|
-
export declare function useIntersectionObserver(options?: UseIntersectionObserverOptions): UseIntersectionObserverReturn
|
|
99
|
+
export declare function useIntersectionObserver<T extends Element = Element>(options?: UseIntersectionObserverOptions): UseIntersectionObserverReturn<T>;
|
|
82
100
|
//# sourceMappingURL=useIntersectionObserver.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useIntersectionObserver.d.ts","sourceRoot":"","sources":["../../src/hooks/useIntersectionObserver.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useIntersectionObserver.d.ts","sourceRoot":"","sources":["../../src/hooks/useIntersectionObserver.ts"],"names":[],"mappings":"AA2BA;;;;;;GAMG;AACH,MAAM,WAAW,8BAA8B;IAC7C,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC9B,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,6BAA6B,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO;IACxE,GAAG,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC/B,cAAc,EAAE,OAAO,CAAC;IACxB,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,yBAAyB,GAAG,IAAI,CAAC;CACzC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsEG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EACjE,OAAO,GAAE,8BAAmC,GAC3C,6BAA6B,CAAC,CAAC,CAAC,CA4ClC"}
|