@ably/ui 17.12.0-dev.7bcf1327 → 17.13.0-dev.2764bbe8

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.
@@ -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 * Can be a string or React element for custom content.\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":"AAuDA,OAAO,MAAMA,gBAAkB,CAAC,UAAW,cAAe,SAAS,AAAU"}
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,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;
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
@@ -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 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
+ {"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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"}
package/core/Expander.js CHANGED
@@ -1,2 +1,2 @@
1
- import React,{useEffect,useRef,useState}from"react";import*as RadixCollapsible from"@radix-ui/react-collapsible";import{throttle}from"es-toolkit/compat";import cn from"./utils/cn";const Expander=({heightThreshold=200,className,fadeClassName,controlsClassName,controlsOpenedLabel,controlsClosedLabel,children})=>{const innerRef=useRef(null);const[showControls,setShowControls]=useState(false);const[contentHeight,setContentHeight]=useState(heightThreshold);const[expanded,setExpanded]=useState(false);useEffect(()=>{if(innerRef.current){setContentHeight(innerRef.current.clientHeight)}setShowControls(contentHeight>=heightThreshold)},[contentHeight,heightThreshold]);useEffect(()=>{const onResize=throttle(()=>{if(innerRef.current){setContentHeight(innerRef.current.clientHeight)}},250);window.addEventListener("resize",onResize);return()=>{window.removeEventListener("resize",onResize)}},[]);const height=contentHeight<heightThreshold?"auto":expanded?contentHeight:heightThreshold;return React.createElement(RadixCollapsible.Root,{open:expanded,onOpenChange:setExpanded},React.createElement("div",{style:{height},"data-testid":"expander-container",className:cn("overflow-hidden transition-all relative",className)},showControls&&!expanded&&React.createElement("div",{className:cn("h-16 w-full bg-gradient-to-t from-white to-transparent absolute bottom-0 left-0 right-0",fadeClassName)}),React.createElement("div",{ref:innerRef},children)),showControls&&React.createElement(RadixCollapsible.Trigger,{asChild:true},React.createElement("button",{"data-testid":"expander-controls",className:cn(heightThreshold===0&&!expanded?"":"mt-4","cursor-pointer font-bold text-gui-blue-default-light hover:text-gui-blue-hover-light",controlsClassName)},expanded?controlsOpenedLabel??"View less -":controlsClosedLabel??"View all +")))};export default Expander;
1
+ import React,{useMemo,useRef,useState}from"react";import*as RadixCollapsible from"@radix-ui/react-collapsible";import cn from"./utils/cn";import{useContentHeight}from"./hooks/use-content-height";const Expander=({heightThreshold=200,className,fadeClassName,controlsClassName,controlsOpenedLabel,controlsClosedLabel,children})=>{const innerRef=useRef(null);const[expanded,setExpanded]=useState(false);const contentHeight=useContentHeight(innerRef,heightThreshold);const showControls=useMemo(()=>contentHeight>=heightThreshold,[contentHeight,heightThreshold]);const height=useMemo(()=>contentHeight<heightThreshold?"auto":expanded?contentHeight:heightThreshold,[contentHeight,heightThreshold,expanded]);return React.createElement(RadixCollapsible.Root,{open:expanded,onOpenChange:setExpanded},React.createElement("div",{style:{height},"data-testid":"expander-container",className:cn("overflow-hidden transition-all relative",className)},showControls&&!expanded&&React.createElement("div",{className:cn("h-16 w-full bg-gradient-to-t from-white to-transparent absolute bottom-0 left-0 right-0",fadeClassName)}),React.createElement("div",{ref:innerRef},children)),showControls&&React.createElement(RadixCollapsible.Trigger,{asChild:true},React.createElement("button",{"data-testid":"expander-controls",className:cn(heightThreshold===0&&!expanded?"":"mt-4","cursor-pointer font-bold text-gui-blue-default-light hover:text-gui-blue-hover-light",controlsClassName)},expanded?controlsOpenedLabel??"View less -":controlsClosedLabel??"View all +")))};export default Expander;
2
2
  //# sourceMappingURL=Expander.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/Expander.tsx"],"sourcesContent":["import React, {\n PropsWithChildren,\n ReactNode,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport * as RadixCollapsible from \"@radix-ui/react-collapsible\";\nimport { throttle } from \"es-toolkit/compat\";\nimport cn from \"./utils/cn\";\n\ntype ExpanderProps = {\n heightThreshold?: number;\n className?: string;\n fadeClassName?: string;\n controlsClassName?: string;\n controlsOpenedLabel?: string | ReactNode;\n controlsClosedLabel?: string | ReactNode;\n};\n\nconst Expander = ({\n heightThreshold = 200,\n className,\n fadeClassName,\n controlsClassName,\n controlsOpenedLabel,\n controlsClosedLabel,\n children,\n}: PropsWithChildren<ExpanderProps>) => {\n const innerRef = useRef<HTMLDivElement>(null);\n const [showControls, setShowControls] = useState(false);\n const [contentHeight, setContentHeight] = useState<number>(heightThreshold);\n const [expanded, setExpanded] = useState(false);\n\n useEffect(() => {\n if (innerRef.current) {\n setContentHeight(innerRef.current.clientHeight);\n }\n\n setShowControls(contentHeight >= heightThreshold);\n }, [contentHeight, heightThreshold]);\n\n useEffect(() => {\n const onResize = throttle(() => {\n if (innerRef.current) {\n setContentHeight(innerRef.current.clientHeight);\n }\n }, 250);\n\n window.addEventListener(\"resize\", onResize);\n return () => {\n window.removeEventListener(\"resize\", onResize);\n };\n }, []);\n\n const height =\n contentHeight < heightThreshold\n ? \"auto\"\n : expanded\n ? contentHeight\n : heightThreshold;\n\n return (\n <RadixCollapsible.Root open={expanded} onOpenChange={setExpanded}>\n <div\n style={{ height }}\n data-testid=\"expander-container\"\n className={cn(\"overflow-hidden transition-all relative\", className)}\n >\n {showControls && !expanded && (\n <div\n className={cn(\n \"h-16 w-full bg-gradient-to-t from-white to-transparent absolute bottom-0 left-0 right-0\",\n fadeClassName,\n )}\n ></div>\n )}\n <div ref={innerRef}>{children}</div>\n </div>\n {showControls && (\n <RadixCollapsible.Trigger asChild>\n <button\n data-testid=\"expander-controls\"\n className={cn(\n heightThreshold === 0 && !expanded ? \"\" : \"mt-4\",\n \"cursor-pointer font-bold text-gui-blue-default-light hover:text-gui-blue-hover-light\",\n controlsClassName,\n )}\n >\n {expanded\n ? (controlsOpenedLabel ?? \"View less -\")\n : (controlsClosedLabel ?? \"View all +\")}\n </button>\n </RadixCollapsible.Trigger>\n )}\n </RadixCollapsible.Root>\n );\n};\n\nexport default Expander;\n"],"names":["React","useEffect","useRef","useState","RadixCollapsible","throttle","cn","Expander","heightThreshold","className","fadeClassName","controlsClassName","controlsOpenedLabel","controlsClosedLabel","children","innerRef","showControls","setShowControls","contentHeight","setContentHeight","expanded","setExpanded","current","clientHeight","onResize","window","addEventListener","removeEventListener","height","Root","open","onOpenChange","div","style","data-testid","ref","Trigger","asChild","button"],"mappings":"AAAA,OAAOA,OAGLC,SAAS,CACTC,MAAM,CACNC,QAAQ,KACH,OAAQ,AACf,WAAYC,qBAAsB,6BAA8B,AAChE,QAASC,QAAQ,KAAQ,mBAAoB,AAC7C,QAAOC,OAAQ,YAAa,CAW5B,MAAMC,SAAW,CAAC,CAChBC,gBAAkB,GAAG,CACrBC,SAAS,CACTC,aAAa,CACbC,iBAAiB,CACjBC,mBAAmB,CACnBC,mBAAmB,CACnBC,QAAQ,CACyB,IACjC,MAAMC,SAAWb,OAAuB,MACxC,KAAM,CAACc,aAAcC,gBAAgB,CAAGd,SAAS,OACjD,KAAM,CAACe,cAAeC,iBAAiB,CAAGhB,SAAiBK,iBAC3D,KAAM,CAACY,SAAUC,YAAY,CAAGlB,SAAS,OAEzCF,UAAU,KACR,GAAIc,SAASO,OAAO,CAAE,CACpBH,iBAAiBJ,SAASO,OAAO,CAACC,YAAY,CAChD,CAEAN,gBAAgBC,eAAiBV,gBACnC,EAAG,CAACU,cAAeV,gBAAgB,EAEnCP,UAAU,KACR,MAAMuB,SAAWnB,SAAS,KACxB,GAAIU,SAASO,OAAO,CAAE,CACpBH,iBAAiBJ,SAASO,OAAO,CAACC,YAAY,CAChD,CACF,EAAG,KAEHE,OAAOC,gBAAgB,CAAC,SAAUF,UAClC,MAAO,KACLC,OAAOE,mBAAmB,CAAC,SAAUH,SACvC,CACF,EAAG,EAAE,EAEL,MAAMI,OACJV,cAAgBV,gBACZ,OACAY,SACEF,cACAV,gBAER,OACE,oBAACJ,iBAAiByB,IAAI,EAACC,KAAMV,SAAUW,aAAcV,aACnD,oBAACW,OACCC,MAAO,CAAEL,MAAO,EAChBM,cAAY,qBACZzB,UAAWH,GAAG,0CAA2CG,YAExDO,cAAgB,CAACI,UAChB,oBAACY,OACCvB,UAAWH,GACT,0FACAI,iBAIN,oBAACsB,OAAIG,IAAKpB,UAAWD,WAEtBE,cACC,oBAACZ,iBAAiBgC,OAAO,EAACC,QAAAA,MACxB,oBAACC,UACCJ,cAAY,oBACZzB,UAAWH,GACTE,kBAAoB,GAAK,CAACY,SAAW,GAAK,OAC1C,uFACAT,oBAGDS,SACIR,qBAAuB,cACvBC,qBAAuB,eAMxC,CAEA,gBAAeN,QAAS"}
1
+ {"version":3,"sources":["../../src/core/Expander.tsx"],"sourcesContent":["import React, {\n PropsWithChildren,\n ReactNode,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport * as RadixCollapsible from \"@radix-ui/react-collapsible\";\nimport cn from \"./utils/cn\";\nimport { useContentHeight } from \"./hooks/use-content-height\";\n\ntype ExpanderProps = {\n heightThreshold?: number;\n className?: string;\n fadeClassName?: string;\n controlsClassName?: string;\n controlsOpenedLabel?: string | ReactNode;\n controlsClosedLabel?: string | ReactNode;\n};\n\nconst Expander = ({\n heightThreshold = 200,\n className,\n fadeClassName,\n controlsClassName,\n controlsOpenedLabel,\n controlsClosedLabel,\n children,\n}: PropsWithChildren<ExpanderProps>) => {\n const innerRef = useRef<HTMLDivElement>(null);\n const [expanded, setExpanded] = useState(false);\n\n const contentHeight = useContentHeight(innerRef, heightThreshold);\n\n const showControls = useMemo(\n () => contentHeight >= heightThreshold,\n [contentHeight, heightThreshold],\n );\n\n const height = useMemo(\n () =>\n contentHeight < heightThreshold\n ? \"auto\"\n : expanded\n ? contentHeight\n : heightThreshold,\n [contentHeight, heightThreshold, expanded],\n );\n\n return (\n <RadixCollapsible.Root open={expanded} onOpenChange={setExpanded}>\n <div\n style={{ height }}\n data-testid=\"expander-container\"\n className={cn(\"overflow-hidden transition-all relative\", className)}\n >\n {showControls && !expanded && (\n <div\n className={cn(\n \"h-16 w-full bg-gradient-to-t from-white to-transparent absolute bottom-0 left-0 right-0\",\n fadeClassName,\n )}\n ></div>\n )}\n <div ref={innerRef}>{children}</div>\n </div>\n {showControls && (\n <RadixCollapsible.Trigger asChild>\n <button\n data-testid=\"expander-controls\"\n className={cn(\n heightThreshold === 0 && !expanded ? \"\" : \"mt-4\",\n \"cursor-pointer font-bold text-gui-blue-default-light hover:text-gui-blue-hover-light\",\n controlsClassName,\n )}\n >\n {expanded\n ? (controlsOpenedLabel ?? \"View less -\")\n : (controlsClosedLabel ?? \"View all +\")}\n </button>\n </RadixCollapsible.Trigger>\n )}\n </RadixCollapsible.Root>\n );\n};\n\nexport default Expander;\n"],"names":["React","useMemo","useRef","useState","RadixCollapsible","cn","useContentHeight","Expander","heightThreshold","className","fadeClassName","controlsClassName","controlsOpenedLabel","controlsClosedLabel","children","innerRef","expanded","setExpanded","contentHeight","showControls","height","Root","open","onOpenChange","div","style","data-testid","ref","Trigger","asChild","button"],"mappings":"AAAA,OAAOA,OAGLC,OAAO,CACPC,MAAM,CACNC,QAAQ,KACH,OAAQ,AACf,WAAYC,qBAAsB,6BAA8B,AAChE,QAAOC,OAAQ,YAAa,AAC5B,QAASC,gBAAgB,KAAQ,4BAA6B,CAW9D,MAAMC,SAAW,CAAC,CAChBC,gBAAkB,GAAG,CACrBC,SAAS,CACTC,aAAa,CACbC,iBAAiB,CACjBC,mBAAmB,CACnBC,mBAAmB,CACnBC,QAAQ,CACyB,IACjC,MAAMC,SAAWb,OAAuB,MACxC,KAAM,CAACc,SAAUC,YAAY,CAAGd,SAAS,OAEzC,MAAMe,cAAgBZ,iBAAiBS,SAAUP,iBAEjD,MAAMW,aAAelB,QACnB,IAAMiB,eAAiBV,gBACvB,CAACU,cAAeV,gBAAgB,EAGlC,MAAMY,OAASnB,QACb,IACEiB,cAAgBV,gBACZ,OACAQ,SACEE,cACAV,gBACR,CAACU,cAAeV,gBAAiBQ,SAAS,EAG5C,OACE,oBAACZ,iBAAiBiB,IAAI,EAACC,KAAMN,SAAUO,aAAcN,aACnD,oBAACO,OACCC,MAAO,CAAEL,MAAO,EAChBM,cAAY,qBACZjB,UAAWJ,GAAG,0CAA2CI,YAExDU,cAAgB,CAACH,UAChB,oBAACQ,OACCf,UAAWJ,GACT,0FACAK,iBAIN,oBAACc,OAAIG,IAAKZ,UAAWD,WAEtBK,cACC,oBAACf,iBAAiBwB,OAAO,EAACC,QAAAA,MACxB,oBAACC,UACCJ,cAAY,oBACZjB,UAAWJ,GACTG,kBAAoB,GAAK,CAACQ,SAAW,GAAK,OAC1C,uFACAL,oBAGDK,SACIJ,qBAAuB,cACvBC,qBAAuB,eAMxC,CAEA,gBAAeN,QAAS"}
@@ -0,0 +1,2 @@
1
+ export{};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/core/Header/types.ts"],"sourcesContent":["export type ThemedScrollpoint = {\n id: string;\n className: string;\n};\n"],"names":[],"mappings":"AAAA,QAGE"}
package/core/Header.js CHANGED
@@ -1,2 +1,2 @@
1
- import React,{useState,useEffect,useRef,useMemo,useCallback}from"react";import Icon from"./Icon";import cn from"./utils/cn";import Logo from"./Logo";import{componentMaxHeight,HEADER_BOTTOM_MARGIN,HEADER_HEIGHT}from"./utils/heights";import{HeaderLinks}from"./Header/HeaderLinks";import{throttle}from"es-toolkit/compat";import{COLLAPSE_TRIGGER_DISTANCE}from"./Notice/component";const FLEXIBLE_DESKTOP_CLASSES="hidden md:flex flex-1 items-center h-full";const MAX_MOBILE_MENU_WIDTH="560px";const Header=({className,isNoticeBannerEnabled=false,noticeHeight=0,searchBar,searchButton,logoHref,headerLinks,headerLinksClassName,headerCenterClassName,nav,mobileNav,sessionState,themedScrollpoints=[],searchButtonVisibility="all",location,logoBadge})=>{const[showMenu,setShowMenu]=useState(false);const[fadingOut,setFadingOut]=useState(false);const[noticeBannerVisible,setNoticeBannerVisible]=useState(isNoticeBannerEnabled);const menuRef=useRef(null);const[scrollpointClasses,setScrollpointClasses]=useState(themedScrollpoints.length>0?themedScrollpoints[0].className:"");const headerStyle={height:HEADER_HEIGHT,top:noticeBannerVisible?`${noticeHeight}px`:"0"};const headerClassName=cn("fixed left-0 top-0 w-full z-50 bg-neutral-000 dark:bg-neutral-1300 border-b border-neutral-300 dark:border-neutral-1000 transition-all duration-300 ease-in-out px-6 lg:px-16",scrollpointClasses,{"md:top-auto":noticeBannerVisible});const closeMenu=()=>{setFadingOut(true);setTimeout(()=>{setShowMenu(false);setFadingOut(false)},150)};const handleNoticeClose=useCallback(()=>{setNoticeBannerVisible(false)},[]);useEffect(()=>{document.addEventListener("notice-closed",handleNoticeClose);return()=>document.removeEventListener("notice-closed",handleNoticeClose)},[handleNoticeClose]);useEffect(()=>{const handleScroll=()=>{const noticeElement=document.querySelector('[data-id="ui-notice"]');const isNoticeClosedToBeHidden=noticeElement?.classList.contains("ui-announcement-hidden");setNoticeBannerVisible(window.scrollY<=COLLAPSE_TRIGGER_DISTANCE&&isNoticeBannerEnabled&&!isNoticeClosedToBeHidden);for(const scrollpoint of themedScrollpoints){const element=document.getElementById(scrollpoint.id);if(element){const rect=element.getBoundingClientRect();if(rect.top<=HEADER_HEIGHT&&rect.bottom>=HEADER_HEIGHT){setScrollpointClasses(scrollpoint.className);return}}}};const throttledHandleScroll=throttle(handleScroll,100);handleScroll();window.addEventListener("scroll",throttledHandleScroll);return()=>window.removeEventListener("scroll",throttledHandleScroll)},[themedScrollpoints,isNoticeBannerEnabled]);useEffect(()=>{const handleResize=()=>{if(window.innerWidth>=1040){setShowMenu(false)}};window.addEventListener("resize",handleResize);return()=>window.removeEventListener("resize",handleResize)},[]);useEffect(()=>{if(showMenu){document.body.classList.add("overflow-hidden")}else{document.body.classList.remove("overflow-hidden")}return()=>{document.body.classList.remove("overflow-hidden")}},[showMenu]);useEffect(()=>{if(location&&showMenu){closeMenu()}},[location]);const wrappedSearchButton=useMemo(()=>searchButton?React.createElement("div",{className:"text-neutral-1300 dark:text-neutral-000 flex items-center"},searchButton):null,[searchButton]);return React.createElement(React.Fragment,null,React.createElement("header",{role:"banner",style:headerStyle,className:headerClassName},React.createElement("div",{className:cn("flex items-center h-full",className)},React.createElement("nav",{className:"flex flex-1 h-full items-center"},["light","dark"].map(theme=>React.createElement(Logo,{key:theme,href:logoHref,theme:theme,badge:logoBadge,additionalLinkAttrs:{className:cn("h-full focus-base rounded mr-4 lg:mr-8",{"flex dark:hidden":theme==="light","hidden dark:flex":theme==="dark"})}})),React.createElement("div",{className:FLEXIBLE_DESKTOP_CLASSES},nav)),React.createElement("div",{className:"flex md:hidden flex-1 items-center justify-end gap-6 h-full"},searchButtonVisibility!=="desktop"?wrappedSearchButton:null,React.createElement("button",{className:"cursor-pointer focus-base rounded flex items-center p-0",onClick:()=>setShowMenu(!showMenu),"aria-expanded":showMenu,"aria-controls":"mobile-menu","aria-label":"Toggle menu"},React.createElement(Icon,{name:showMenu?"icon-gui-x-mark-outline":"icon-gui-bars-3-outline",additionalCSS:"text-neutral-1300 dark:text-neutral-000",size:"1.5rem"}))),searchBar?React.createElement("div",{className:cn(FLEXIBLE_DESKTOP_CLASSES,"justify-center",headerCenterClassName)},searchBar):null,React.createElement(HeaderLinks,{className:cn(FLEXIBLE_DESKTOP_CLASSES,headerLinksClassName),headerLinks:headerLinks,sessionState:sessionState,searchButton:wrappedSearchButton,searchButtonVisibility:searchButtonVisibility}))),showMenu?React.createElement(React.Fragment,null,React.createElement("div",{className:cn("fixed inset-0 bg-neutral-1300 dark:bg-neutral-1300 z-40",{"animate-[fade-in-ten-percent_150ms_ease-in-out_forwards]":!fadingOut,"animate-[fade-out-ten-percent_150ms_ease-in-out_forwards]":fadingOut}),onClick:closeMenu,onKeyDown:e=>e.key==="Escape"&&closeMenu(),role:"presentation"}),React.createElement("div",{id:"mobile-menu",className:"md:hidden fixed flex flex-col top-[4.75rem] overflow-y-hidden mx-3 right-0 w-[calc(100%-24px)] bg-neutral-000 dark:bg-neutral-1300 rounded-2xl ui-shadow-lg-medium z-50",style:{maxWidth:MAX_MOBILE_MENU_WIDTH,maxHeight:componentMaxHeight(HEADER_HEIGHT,HEADER_BOTTOM_MARGIN)},ref:menuRef,role:"navigation"},mobileNav,React.createElement(HeaderLinks,{headerLinks:headerLinks,sessionState:sessionState}))):null)};export default Header;
1
+ import React,{useState,useEffect,useRef,useMemo,useCallback}from"react";import Icon from"./Icon";import cn from"./utils/cn";import Logo from"./Logo";import{componentMaxHeight,HEADER_BOTTOM_MARGIN,HEADER_HEIGHT}from"./utils/heights";import{HeaderLinks}from"./Header/HeaderLinks";import{throttle}from"es-toolkit/compat";import{COLLAPSE_TRIGGER_DISTANCE}from"./Notice/component";import{useThemedScrollpoints}from"./hooks/use-themed-scrollpoints";const FLEXIBLE_DESKTOP_CLASSES="hidden md:flex flex-1 items-center h-full";const MAX_MOBILE_MENU_WIDTH="560px";const Header=({className,isNoticeBannerEnabled=false,noticeHeight=0,searchBar,searchButton,logoHref,headerLinks,headerLinksClassName,headerCenterClassName,nav,mobileNav,sessionState,themedScrollpoints=[],searchButtonVisibility="all",location,logoBadge})=>{const[showMenu,setShowMenu]=useState(false);const[fadingOut,setFadingOut]=useState(false);const[noticeBannerVisible,setNoticeBannerVisible]=useState(isNoticeBannerEnabled);const menuRef=useRef(null);const scrollpointClasses=useThemedScrollpoints(themedScrollpoints);const headerStyle={height:HEADER_HEIGHT,top:noticeBannerVisible?`${noticeHeight}px`:"0"};const headerClassName=cn("fixed left-0 top-0 w-full z-50 bg-neutral-000 dark:bg-neutral-1300 border-b border-neutral-300 dark:border-neutral-1000 transition-all duration-300 ease-in-out px-6 lg:px-16",scrollpointClasses,{"md:top-auto":noticeBannerVisible});const closeMenu=()=>{setFadingOut(true);setTimeout(()=>{setShowMenu(false);setFadingOut(false)},150)};const handleNoticeClose=useCallback(()=>{setNoticeBannerVisible(false)},[]);useEffect(()=>{document.addEventListener("notice-closed",handleNoticeClose);return()=>document.removeEventListener("notice-closed",handleNoticeClose)},[handleNoticeClose]);useEffect(()=>{if(!isNoticeBannerEnabled){return}const noticeElement=document.querySelector('[data-id="ui-notice"]');if(!noticeElement){console.warn("Header: Notice element not found");return}let previousVisibility=noticeBannerVisible;const handleScroll=()=>{const scrollY=window.scrollY;const isNoticeHidden=noticeElement.classList.contains("ui-announcement-hidden");const shouldBeVisible=scrollY<=COLLAPSE_TRIGGER_DISTANCE&&!isNoticeHidden;if(shouldBeVisible!==previousVisibility){previousVisibility=shouldBeVisible;setNoticeBannerVisible(shouldBeVisible)}};const throttledHandleScroll=throttle(handleScroll,100);handleScroll();window.addEventListener("scroll",throttledHandleScroll,{passive:true});return()=>{window.removeEventListener("scroll",throttledHandleScroll)}},[isNoticeBannerEnabled,noticeBannerVisible]);useEffect(()=>{const handleResize=()=>{if(window.innerWidth>=1040){setShowMenu(false)}};window.addEventListener("resize",handleResize);return()=>window.removeEventListener("resize",handleResize)},[]);useEffect(()=>{if(showMenu){document.body.classList.add("overflow-hidden")}else{document.body.classList.remove("overflow-hidden")}return()=>{document.body.classList.remove("overflow-hidden")}},[showMenu]);useEffect(()=>{if(location&&showMenu){closeMenu()}},[location]);const wrappedSearchButton=useMemo(()=>searchButton?React.createElement("div",{className:"text-neutral-1300 dark:text-neutral-000 flex items-center"},searchButton):null,[searchButton]);return React.createElement(React.Fragment,null,React.createElement("header",{role:"banner",style:headerStyle,className:headerClassName},React.createElement("div",{className:cn("flex items-center h-full",className)},React.createElement("nav",{className:"flex flex-1 h-full items-center"},["light","dark"].map(theme=>React.createElement(Logo,{key:theme,href:logoHref,theme:theme,badge:logoBadge,additionalLinkAttrs:{className:cn("h-full focus-base rounded mr-4 lg:mr-8",{"flex dark:hidden":theme==="light","hidden dark:flex":theme==="dark"})}})),React.createElement("div",{className:FLEXIBLE_DESKTOP_CLASSES},nav)),React.createElement("div",{className:"flex md:hidden flex-1 items-center justify-end gap-6 h-full"},searchButtonVisibility!=="desktop"?wrappedSearchButton:null,React.createElement("button",{className:"cursor-pointer focus-base rounded flex items-center p-0",onClick:()=>setShowMenu(!showMenu),"aria-expanded":showMenu,"aria-controls":"mobile-menu","aria-label":"Toggle menu"},React.createElement(Icon,{name:showMenu?"icon-gui-x-mark-outline":"icon-gui-bars-3-outline",additionalCSS:"text-neutral-1300 dark:text-neutral-000",size:"1.5rem"}))),searchBar?React.createElement("div",{className:cn(FLEXIBLE_DESKTOP_CLASSES,"justify-center",headerCenterClassName)},searchBar):null,React.createElement(HeaderLinks,{className:cn(FLEXIBLE_DESKTOP_CLASSES,headerLinksClassName),headerLinks:headerLinks,sessionState:sessionState,searchButton:wrappedSearchButton,searchButtonVisibility:searchButtonVisibility}))),showMenu?React.createElement(React.Fragment,null,React.createElement("div",{className:cn("fixed inset-0 bg-neutral-1300 dark:bg-neutral-1300 z-40",{"animate-[fade-in-ten-percent_150ms_ease-in-out_forwards]":!fadingOut,"animate-[fade-out-ten-percent_150ms_ease-in-out_forwards]":fadingOut}),onClick:closeMenu,onKeyDown:e=>e.key==="Escape"&&closeMenu(),role:"presentation"}),React.createElement("div",{id:"mobile-menu",className:"md:hidden fixed flex flex-col top-[4.75rem] overflow-y-hidden mx-3 right-0 w-[calc(100%-24px)] bg-neutral-000 dark:bg-neutral-1300 rounded-2xl ui-shadow-lg-medium z-50",style:{maxWidth:MAX_MOBILE_MENU_WIDTH,maxHeight:componentMaxHeight(HEADER_HEIGHT,HEADER_BOTTOM_MARGIN)},ref:menuRef,role:"navigation"},mobileNav,React.createElement(HeaderLinks,{headerLinks:headerLinks,sessionState:sessionState}))):null)};export default Header;
2
2
  //# sourceMappingURL=Header.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/Header.tsx"],"sourcesContent":["import React, {\n useState,\n useEffect,\n useRef,\n ReactNode,\n useMemo,\n useCallback,\n} from \"react\";\nimport Icon from \"./Icon\";\nimport cn from \"./utils/cn\";\nimport Logo from \"./Logo\";\nimport {\n componentMaxHeight,\n HEADER_BOTTOM_MARGIN,\n HEADER_HEIGHT,\n} from \"./utils/heights\";\nimport { HeaderLinks } from \"./Header/HeaderLinks\";\nimport { throttle } from \"es-toolkit/compat\";\nimport { Theme } from \"./styles/colors/types\";\nimport { COLLAPSE_TRIGGER_DISTANCE } from \"./Notice/component\";\n\nexport type ThemedScrollpoint = {\n id: string;\n className: string;\n};\n\n/**\n * Represents the state of the user session in the header.\n */\nexport type HeaderSessionState = {\n /**\n * Indicates if the user is signed in.\n */\n signedIn: boolean;\n\n /**\n * Information required to log out the user.\n */\n logOut: {\n /**\n * Token used for logging out.\n */\n token: string;\n\n /**\n * URL to log out the user.\n */\n href: string;\n };\n\n /**\n * Name of the user's account.\n */\n accountName: string;\n};\n\n/**\n * Props for the Header component.\n */\nexport type HeaderProps = {\n /**\n * Optional classnames to add to the header\n */\n className?: string;\n /**\n * Indicates if the notice banner is enabled.\n */\n isNoticeBannerEnabled?: boolean;\n /**\n * Height of the notice banner in pixels.\n */\n noticeHeight?: number;\n /**\n * Optional search bar element.\n */\n searchBar?: ReactNode;\n\n /**\n * Optional search button element.\n */\n searchButton?: ReactNode;\n\n /**\n * URL for the logo link.\n */\n logoHref?: string;\n\n /**\n * Array of header links.\n */\n headerLinks?: {\n /**\n * URL for the link.\n */\n href: string;\n\n /**\n * Label for the link.\n */\n label: string;\n\n /**\n * Indicates if the link should open in a new tab.\n */\n external?: boolean;\n }[];\n\n /**\n * Optional classname for styling the header links container.\n */\n headerLinksClassName?: string;\n\n /**\n * Optional classname for styling the header center container.\n */\n headerCenterClassName?: string;\n\n /**\n * Optional desktop navigation element.\n */\n nav?: ReactNode;\n\n /**\n * Optional mobile navigation element.\n */\n mobileNav?: ReactNode;\n\n /**\n * State of the user session.\n */\n sessionState?: HeaderSessionState;\n\n /**\n * Array of themed scrollpoints. The header will change its appearance based on the scrollpoint in view.\n */\n themedScrollpoints?: ThemedScrollpoint[];\n\n /**\n * Visibility setting for the search button.\n * - \"all\": Visible on all devices.\n * - \"desktop\": Visible only on desktop devices.\n * - \"mobile\": Visible only on mobile devices.\n */\n searchButtonVisibility?: \"all\" | \"desktop\" | \"mobile\";\n\n /**\n * Optional location object to detect location changes.\n */\n location?: Location;\n\n /**\n * Optional badge text to display on the logo.\n */\n logoBadge?: string;\n};\n\nconst FLEXIBLE_DESKTOP_CLASSES = \"hidden md:flex flex-1 items-center h-full\";\n\n/**\n * Maximum width before the menu expanded into full width\n */\nconst MAX_MOBILE_MENU_WIDTH = \"560px\";\n\nconst Header: React.FC<HeaderProps> = ({\n className,\n isNoticeBannerEnabled = false,\n noticeHeight = 0,\n searchBar,\n searchButton,\n logoHref,\n headerLinks,\n headerLinksClassName,\n headerCenterClassName,\n nav,\n mobileNav,\n sessionState,\n themedScrollpoints = [],\n searchButtonVisibility = \"all\",\n location,\n logoBadge,\n}) => {\n const [showMenu, setShowMenu] = useState(false);\n const [fadingOut, setFadingOut] = useState(false);\n const [noticeBannerVisible, setNoticeBannerVisible] = useState(\n isNoticeBannerEnabled,\n );\n const menuRef = useRef<HTMLDivElement>(null);\n const [scrollpointClasses, setScrollpointClasses] = useState<string>(\n themedScrollpoints.length > 0 ? themedScrollpoints[0].className : \"\",\n );\n\n const headerStyle = {\n height: HEADER_HEIGHT,\n top: noticeBannerVisible ? `${noticeHeight}px` : \"0\",\n };\n\n const headerClassName = cn(\n \"fixed left-0 top-0 w-full z-50 bg-neutral-000 dark:bg-neutral-1300 border-b border-neutral-300 dark:border-neutral-1000 transition-all duration-300 ease-in-out px-6 lg:px-16\",\n scrollpointClasses,\n {\n \"md:top-auto\": noticeBannerVisible,\n },\n );\n\n const closeMenu = () => {\n setFadingOut(true);\n\n setTimeout(() => {\n setShowMenu(false);\n setFadingOut(false);\n }, 150);\n };\n\n const handleNoticeClose = useCallback(() => {\n setNoticeBannerVisible(false);\n }, []);\n\n useEffect(() => {\n document.addEventListener(\"notice-closed\", handleNoticeClose);\n return () =>\n document.removeEventListener(\"notice-closed\", handleNoticeClose);\n }, [handleNoticeClose]);\n\n useEffect(() => {\n const handleScroll = () => {\n const noticeElement = document.querySelector('[data-id=\"ui-notice\"]');\n const isNoticeClosedToBeHidden = noticeElement?.classList.contains(\n \"ui-announcement-hidden\",\n );\n setNoticeBannerVisible(\n window.scrollY <= COLLAPSE_TRIGGER_DISTANCE &&\n isNoticeBannerEnabled &&\n !isNoticeClosedToBeHidden,\n );\n for (const scrollpoint of themedScrollpoints) {\n const element = document.getElementById(scrollpoint.id);\n if (element) {\n const rect = element.getBoundingClientRect();\n if (rect.top <= HEADER_HEIGHT && rect.bottom >= HEADER_HEIGHT) {\n setScrollpointClasses(scrollpoint.className);\n return;\n }\n }\n }\n };\n\n const throttledHandleScroll = throttle(handleScroll, 100);\n\n handleScroll();\n\n window.addEventListener(\"scroll\", throttledHandleScroll);\n return () => window.removeEventListener(\"scroll\", throttledHandleScroll);\n }, [themedScrollpoints, isNoticeBannerEnabled]);\n\n useEffect(() => {\n const handleResize = () => {\n if (window.innerWidth >= 1040) {\n setShowMenu(false);\n }\n };\n window.addEventListener(\"resize\", handleResize);\n return () => window.removeEventListener(\"resize\", handleResize);\n }, []);\n\n useEffect(() => {\n if (showMenu) {\n document.body.classList.add(\"overflow-hidden\");\n } else {\n document.body.classList.remove(\"overflow-hidden\");\n }\n\n // Cleanup on unmount\n return () => {\n document.body.classList.remove(\"overflow-hidden\");\n };\n }, [showMenu]);\n\n // Close menu when location changes\n useEffect(() => {\n if (location && showMenu) {\n closeMenu();\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [location]);\n\n const wrappedSearchButton = useMemo(\n () =>\n searchButton ? (\n <div className=\"text-neutral-1300 dark:text-neutral-000 flex items-center\">\n {searchButton}\n </div>\n ) : null,\n [searchButton],\n );\n\n return (\n <>\n <header role=\"banner\" style={headerStyle} className={headerClassName}>\n <div className={cn(\"flex items-center h-full\", className)}>\n <nav className=\"flex flex-1 h-full items-center\">\n {([\"light\", \"dark\"] as Theme[]).map((theme) => (\n <Logo\n key={theme}\n href={logoHref}\n theme={theme}\n badge={logoBadge}\n additionalLinkAttrs={{\n className: cn(\"h-full focus-base rounded mr-4 lg:mr-8\", {\n \"flex dark:hidden\": theme === \"light\",\n \"hidden dark:flex\": theme === \"dark\",\n }),\n }}\n />\n ))}\n <div className={FLEXIBLE_DESKTOP_CLASSES}>{nav}</div>\n </nav>\n <div className=\"flex md:hidden flex-1 items-center justify-end gap-6 h-full\">\n {searchButtonVisibility !== \"desktop\" ? wrappedSearchButton : null}\n <button\n className=\"cursor-pointer focus-base rounded flex items-center p-0\"\n onClick={() => setShowMenu(!showMenu)}\n aria-expanded={showMenu}\n aria-controls=\"mobile-menu\"\n aria-label=\"Toggle menu\"\n >\n <Icon\n name={\n showMenu\n ? \"icon-gui-x-mark-outline\"\n : \"icon-gui-bars-3-outline\"\n }\n additionalCSS=\"text-neutral-1300 dark:text-neutral-000\"\n size=\"1.5rem\"\n />\n </button>\n </div>\n {searchBar ? (\n <div\n className={cn(\n FLEXIBLE_DESKTOP_CLASSES,\n \"justify-center\",\n headerCenterClassName,\n )}\n >\n {searchBar}\n </div>\n ) : null}\n <HeaderLinks\n className={cn(FLEXIBLE_DESKTOP_CLASSES, headerLinksClassName)}\n headerLinks={headerLinks}\n sessionState={sessionState}\n searchButton={wrappedSearchButton}\n searchButtonVisibility={searchButtonVisibility}\n />\n </div>\n </header>\n {showMenu ? (\n <>\n <div\n className={cn(\n \"fixed inset-0 bg-neutral-1300 dark:bg-neutral-1300 z-40\",\n {\n \"animate-[fade-in-ten-percent_150ms_ease-in-out_forwards]\":\n !fadingOut,\n \"animate-[fade-out-ten-percent_150ms_ease-in-out_forwards]\":\n fadingOut,\n },\n )}\n onClick={closeMenu}\n onKeyDown={(e) => e.key === \"Escape\" && closeMenu()}\n role=\"presentation\"\n />\n <div\n id=\"mobile-menu\"\n className=\"md:hidden fixed flex flex-col top-[4.75rem] overflow-y-hidden mx-3 right-0 w-[calc(100%-24px)] bg-neutral-000 dark:bg-neutral-1300 rounded-2xl ui-shadow-lg-medium z-50\"\n style={{\n maxWidth: MAX_MOBILE_MENU_WIDTH,\n maxHeight: componentMaxHeight(\n HEADER_HEIGHT,\n HEADER_BOTTOM_MARGIN,\n ),\n }}\n ref={menuRef}\n role=\"navigation\"\n >\n {mobileNav}\n <HeaderLinks\n headerLinks={headerLinks}\n sessionState={sessionState}\n />\n </div>\n </>\n ) : null}\n </>\n );\n};\n\nexport default Header;\n"],"names":["React","useState","useEffect","useRef","useMemo","useCallback","Icon","cn","Logo","componentMaxHeight","HEADER_BOTTOM_MARGIN","HEADER_HEIGHT","HeaderLinks","throttle","COLLAPSE_TRIGGER_DISTANCE","FLEXIBLE_DESKTOP_CLASSES","MAX_MOBILE_MENU_WIDTH","Header","className","isNoticeBannerEnabled","noticeHeight","searchBar","searchButton","logoHref","headerLinks","headerLinksClassName","headerCenterClassName","nav","mobileNav","sessionState","themedScrollpoints","searchButtonVisibility","location","logoBadge","showMenu","setShowMenu","fadingOut","setFadingOut","noticeBannerVisible","setNoticeBannerVisible","menuRef","scrollpointClasses","setScrollpointClasses","length","headerStyle","height","top","headerClassName","closeMenu","setTimeout","handleNoticeClose","document","addEventListener","removeEventListener","handleScroll","noticeElement","querySelector","isNoticeClosedToBeHidden","classList","contains","window","scrollY","scrollpoint","element","getElementById","id","rect","getBoundingClientRect","bottom","throttledHandleScroll","handleResize","innerWidth","body","add","remove","wrappedSearchButton","div","header","role","style","map","theme","key","href","badge","additionalLinkAttrs","button","onClick","aria-expanded","aria-controls","aria-label","name","additionalCSS","size","onKeyDown","e","maxWidth","maxHeight","ref"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/core/Header.tsx"],"sourcesContent":["import React, {\n useState,\n useEffect,\n useRef,\n ReactNode,\n useMemo,\n useCallback,\n} from \"react\";\nimport Icon from \"./Icon\";\nimport cn from \"./utils/cn\";\nimport Logo from \"./Logo\";\nimport {\n componentMaxHeight,\n HEADER_BOTTOM_MARGIN,\n HEADER_HEIGHT,\n} from \"./utils/heights\";\nimport { HeaderLinks } from \"./Header/HeaderLinks\";\nimport { throttle } from \"es-toolkit/compat\";\nimport { Theme } from \"./styles/colors/types\";\nimport { COLLAPSE_TRIGGER_DISTANCE } from \"./Notice/component\";\nimport { useThemedScrollpoints } from \"./hooks/use-themed-scrollpoints\";\nimport { ThemedScrollpoint } from \"./Header/types\";\n\nexport type { ThemedScrollpoint };\n\n/**\n * Represents the state of the user session in the header.\n */\nexport type HeaderSessionState = {\n /**\n * Indicates if the user is signed in.\n */\n signedIn: boolean;\n\n /**\n * Information required to log out the user.\n */\n logOut: {\n /**\n * Token used for logging out.\n */\n token: string;\n\n /**\n * URL to log out the user.\n */\n href: string;\n };\n\n /**\n * Name of the user's account.\n */\n accountName: string;\n};\n\n/**\n * Props for the Header component.\n */\nexport type HeaderProps = {\n /**\n * Optional classnames to add to the header\n */\n className?: string;\n /**\n * Indicates if the notice banner is enabled.\n */\n isNoticeBannerEnabled?: boolean;\n /**\n * Height of the notice banner in pixels.\n */\n noticeHeight?: number;\n /**\n * Optional search bar element.\n */\n searchBar?: ReactNode;\n\n /**\n * Optional search button element.\n */\n searchButton?: ReactNode;\n\n /**\n * URL for the logo link.\n */\n logoHref?: string;\n\n /**\n * Array of header links.\n */\n headerLinks?: {\n /**\n * URL for the link.\n */\n href: string;\n\n /**\n * Label for the link.\n */\n label: string;\n\n /**\n * Indicates if the link should open in a new tab.\n */\n external?: boolean;\n }[];\n\n /**\n * Optional classname for styling the header links container.\n */\n headerLinksClassName?: string;\n\n /**\n * Optional classname for styling the header center container.\n */\n headerCenterClassName?: string;\n\n /**\n * Optional desktop navigation element.\n */\n nav?: ReactNode;\n\n /**\n * Optional mobile navigation element.\n */\n mobileNav?: ReactNode;\n\n /**\n * State of the user session.\n */\n sessionState?: HeaderSessionState;\n\n /**\n * Array of themed scrollpoints. The header will change its appearance based on the scrollpoint in view.\n */\n themedScrollpoints?: ThemedScrollpoint[];\n\n /**\n * Visibility setting for the search button.\n * - \"all\": Visible on all devices.\n * - \"desktop\": Visible only on desktop devices.\n * - \"mobile\": Visible only on mobile devices.\n */\n searchButtonVisibility?: \"all\" | \"desktop\" | \"mobile\";\n\n /**\n * Optional location object to detect location changes.\n */\n location?: Location;\n\n /**\n * Optional badge text to display on the logo.\n */\n logoBadge?: string;\n};\n\nconst FLEXIBLE_DESKTOP_CLASSES = \"hidden md:flex flex-1 items-center h-full\";\n\n/**\n * Maximum width before the menu expanded into full width\n */\nconst MAX_MOBILE_MENU_WIDTH = \"560px\";\n\nconst Header: React.FC<HeaderProps> = ({\n className,\n isNoticeBannerEnabled = false,\n noticeHeight = 0,\n searchBar,\n searchButton,\n logoHref,\n headerLinks,\n headerLinksClassName,\n headerCenterClassName,\n nav,\n mobileNav,\n sessionState,\n themedScrollpoints = [],\n searchButtonVisibility = \"all\",\n location,\n logoBadge,\n}) => {\n const [showMenu, setShowMenu] = useState(false);\n const [fadingOut, setFadingOut] = useState(false);\n const [noticeBannerVisible, setNoticeBannerVisible] = useState(\n isNoticeBannerEnabled,\n );\n const menuRef = useRef<HTMLDivElement>(null);\n const scrollpointClasses = useThemedScrollpoints(themedScrollpoints);\n\n const headerStyle = {\n height: HEADER_HEIGHT,\n top: noticeBannerVisible ? `${noticeHeight}px` : \"0\",\n };\n\n const headerClassName = cn(\n \"fixed left-0 top-0 w-full z-50 bg-neutral-000 dark:bg-neutral-1300 border-b border-neutral-300 dark:border-neutral-1000 transition-all duration-300 ease-in-out px-6 lg:px-16\",\n scrollpointClasses,\n {\n \"md:top-auto\": noticeBannerVisible,\n },\n );\n\n const closeMenu = () => {\n setFadingOut(true);\n\n setTimeout(() => {\n setShowMenu(false);\n setFadingOut(false);\n }, 150);\n };\n\n const handleNoticeClose = useCallback(() => {\n setNoticeBannerVisible(false);\n }, []);\n\n useEffect(() => {\n document.addEventListener(\"notice-closed\", handleNoticeClose);\n return () =>\n document.removeEventListener(\"notice-closed\", handleNoticeClose);\n }, [handleNoticeClose]);\n\n useEffect(() => {\n if (!isNoticeBannerEnabled) {\n return;\n }\n\n const noticeElement = document.querySelector('[data-id=\"ui-notice\"]');\n\n if (!noticeElement) {\n console.warn(\"Header: Notice element not found\");\n return;\n }\n\n let previousVisibility = noticeBannerVisible;\n\n const handleScroll = () => {\n const scrollY = window.scrollY;\n const isNoticeHidden = noticeElement.classList.contains(\n \"ui-announcement-hidden\",\n );\n\n const shouldBeVisible =\n scrollY <= COLLAPSE_TRIGGER_DISTANCE && !isNoticeHidden;\n\n if (shouldBeVisible !== previousVisibility) {\n previousVisibility = shouldBeVisible;\n setNoticeBannerVisible(shouldBeVisible);\n }\n };\n\n const throttledHandleScroll = throttle(handleScroll, 100);\n\n handleScroll();\n\n window.addEventListener(\"scroll\", throttledHandleScroll, { passive: true });\n\n return () => {\n window.removeEventListener(\"scroll\", throttledHandleScroll);\n };\n }, [isNoticeBannerEnabled, noticeBannerVisible]);\n\n useEffect(() => {\n const handleResize = () => {\n if (window.innerWidth >= 1040) {\n setShowMenu(false);\n }\n };\n window.addEventListener(\"resize\", handleResize);\n return () => window.removeEventListener(\"resize\", handleResize);\n }, []);\n\n useEffect(() => {\n if (showMenu) {\n document.body.classList.add(\"overflow-hidden\");\n } else {\n document.body.classList.remove(\"overflow-hidden\");\n }\n\n // Cleanup on unmount\n return () => {\n document.body.classList.remove(\"overflow-hidden\");\n };\n }, [showMenu]);\n\n // Close menu when location changes\n useEffect(() => {\n if (location && showMenu) {\n closeMenu();\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [location]);\n\n const wrappedSearchButton = useMemo(\n () =>\n searchButton ? (\n <div className=\"text-neutral-1300 dark:text-neutral-000 flex items-center\">\n {searchButton}\n </div>\n ) : null,\n [searchButton],\n );\n\n return (\n <>\n <header role=\"banner\" style={headerStyle} className={headerClassName}>\n <div className={cn(\"flex items-center h-full\", className)}>\n <nav className=\"flex flex-1 h-full items-center\">\n {([\"light\", \"dark\"] as Theme[]).map((theme) => (\n <Logo\n key={theme}\n href={logoHref}\n theme={theme}\n badge={logoBadge}\n additionalLinkAttrs={{\n className: cn(\"h-full focus-base rounded mr-4 lg:mr-8\", {\n \"flex dark:hidden\": theme === \"light\",\n \"hidden dark:flex\": theme === \"dark\",\n }),\n }}\n />\n ))}\n <div className={FLEXIBLE_DESKTOP_CLASSES}>{nav}</div>\n </nav>\n <div className=\"flex md:hidden flex-1 items-center justify-end gap-6 h-full\">\n {searchButtonVisibility !== \"desktop\" ? wrappedSearchButton : null}\n <button\n className=\"cursor-pointer focus-base rounded flex items-center p-0\"\n onClick={() => setShowMenu(!showMenu)}\n aria-expanded={showMenu}\n aria-controls=\"mobile-menu\"\n aria-label=\"Toggle menu\"\n >\n <Icon\n name={\n showMenu\n ? \"icon-gui-x-mark-outline\"\n : \"icon-gui-bars-3-outline\"\n }\n additionalCSS=\"text-neutral-1300 dark:text-neutral-000\"\n size=\"1.5rem\"\n />\n </button>\n </div>\n {searchBar ? (\n <div\n className={cn(\n FLEXIBLE_DESKTOP_CLASSES,\n \"justify-center\",\n headerCenterClassName,\n )}\n >\n {searchBar}\n </div>\n ) : null}\n <HeaderLinks\n className={cn(FLEXIBLE_DESKTOP_CLASSES, headerLinksClassName)}\n headerLinks={headerLinks}\n sessionState={sessionState}\n searchButton={wrappedSearchButton}\n searchButtonVisibility={searchButtonVisibility}\n />\n </div>\n </header>\n {showMenu ? (\n <>\n <div\n className={cn(\n \"fixed inset-0 bg-neutral-1300 dark:bg-neutral-1300 z-40\",\n {\n \"animate-[fade-in-ten-percent_150ms_ease-in-out_forwards]\":\n !fadingOut,\n \"animate-[fade-out-ten-percent_150ms_ease-in-out_forwards]\":\n fadingOut,\n },\n )}\n onClick={closeMenu}\n onKeyDown={(e) => e.key === \"Escape\" && closeMenu()}\n role=\"presentation\"\n />\n <div\n id=\"mobile-menu\"\n className=\"md:hidden fixed flex flex-col top-[4.75rem] overflow-y-hidden mx-3 right-0 w-[calc(100%-24px)] bg-neutral-000 dark:bg-neutral-1300 rounded-2xl ui-shadow-lg-medium z-50\"\n style={{\n maxWidth: MAX_MOBILE_MENU_WIDTH,\n maxHeight: componentMaxHeight(\n HEADER_HEIGHT,\n HEADER_BOTTOM_MARGIN,\n ),\n }}\n ref={menuRef}\n role=\"navigation\"\n >\n {mobileNav}\n <HeaderLinks\n headerLinks={headerLinks}\n sessionState={sessionState}\n />\n </div>\n </>\n ) : null}\n </>\n );\n};\n\nexport default Header;\n"],"names":["React","useState","useEffect","useRef","useMemo","useCallback","Icon","cn","Logo","componentMaxHeight","HEADER_BOTTOM_MARGIN","HEADER_HEIGHT","HeaderLinks","throttle","COLLAPSE_TRIGGER_DISTANCE","useThemedScrollpoints","FLEXIBLE_DESKTOP_CLASSES","MAX_MOBILE_MENU_WIDTH","Header","className","isNoticeBannerEnabled","noticeHeight","searchBar","searchButton","logoHref","headerLinks","headerLinksClassName","headerCenterClassName","nav","mobileNav","sessionState","themedScrollpoints","searchButtonVisibility","location","logoBadge","showMenu","setShowMenu","fadingOut","setFadingOut","noticeBannerVisible","setNoticeBannerVisible","menuRef","scrollpointClasses","headerStyle","height","top","headerClassName","closeMenu","setTimeout","handleNoticeClose","document","addEventListener","removeEventListener","noticeElement","querySelector","console","warn","previousVisibility","handleScroll","scrollY","window","isNoticeHidden","classList","contains","shouldBeVisible","throttledHandleScroll","passive","handleResize","innerWidth","body","add","remove","wrappedSearchButton","div","header","role","style","map","theme","key","href","badge","additionalLinkAttrs","button","onClick","aria-expanded","aria-controls","aria-label","name","additionalCSS","size","onKeyDown","e","id","maxWidth","maxHeight","ref"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{useState,useEffect,useRef}from"react";export function useContentHeight(ref,initialHeight=0){const[contentHeight,setContentHeight]=useState(initialHeight);const observerRef=useRef(null);useEffect(()=>{const element=ref.current;if(!element){return}observerRef.current=new ResizeObserver(entries=>{requestAnimationFrame(()=>{const entry=entries[0];if(entry&&entry.contentRect){const newHeight=Math.round(entry.contentRect.height);setContentHeight(newHeight)}})});observerRef.current.observe(element);return()=>{observerRef.current?.disconnect();observerRef.current=null}},[ref]);return contentHeight}
2
+ //# sourceMappingURL=use-content-height.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/core/hooks/use-content-height.ts"],"sourcesContent":["import { useState, useEffect, useRef, RefObject } from \"react\";\n\n/**\n * Custom hook that tracks the content height of an element using ResizeObserver.\n * This eliminates forced reflows by using the browser's native resize observation API\n * instead of synchronous clientHeight/getBoundingClientRect queries.\n *\n * @param ref - React ref to the element to observe\n * @param initialHeight - Initial height value (default: 0)\n * @returns Current content height in pixels\n */\nexport function useContentHeight(\n ref: RefObject<HTMLElement>,\n initialHeight = 0,\n): number {\n const [contentHeight, setContentHeight] = useState<number>(initialHeight);\n const observerRef = useRef<ResizeObserver | null>(null);\n\n useEffect(() => {\n const element = ref.current;\n\n if (!element) {\n return;\n }\n\n observerRef.current = new ResizeObserver((entries) => {\n requestAnimationFrame(() => {\n const entry = entries[0];\n if (entry && entry.contentRect) {\n const newHeight = Math.round(entry.contentRect.height);\n setContentHeight(newHeight);\n }\n });\n });\n\n observerRef.current.observe(element);\n\n return () => {\n observerRef.current?.disconnect();\n observerRef.current = null;\n };\n }, [ref]);\n\n return contentHeight;\n}\n"],"names":["useState","useEffect","useRef","useContentHeight","ref","initialHeight","contentHeight","setContentHeight","observerRef","element","current","ResizeObserver","entries","requestAnimationFrame","entry","contentRect","newHeight","Math","round","height","observe","disconnect"],"mappings":"AAAA,OAASA,QAAQ,CAAEC,SAAS,CAAEC,MAAM,KAAmB,OAAQ,AAW/D,QAAO,SAASC,iBACdC,GAA2B,CAC3BC,cAAgB,CAAC,EAEjB,KAAM,CAACC,cAAeC,iBAAiB,CAAGP,SAAiBK,eAC3D,MAAMG,YAAcN,OAA8B,MAElDD,UAAU,KACR,MAAMQ,QAAUL,IAAIM,OAAO,CAE3B,GAAI,CAACD,QAAS,CACZ,MACF,CAEAD,YAAYE,OAAO,CAAG,IAAIC,eAAe,AAACC,UACxCC,sBAAsB,KACpB,MAAMC,MAAQF,OAAO,CAAC,EAAE,CACxB,GAAIE,OAASA,MAAMC,WAAW,CAAE,CAC9B,MAAMC,UAAYC,KAAKC,KAAK,CAACJ,MAAMC,WAAW,CAACI,MAAM,EACrDZ,iBAAiBS,UACnB,CACF,EACF,GAEAR,YAAYE,OAAO,CAACU,OAAO,CAACX,SAE5B,MAAO,KACLD,YAAYE,OAAO,EAAEW,YACrBb,CAAAA,YAAYE,OAAO,CAAG,IACxB,CACF,EAAG,CAACN,IAAI,EAER,OAAOE,aACT"}
@@ -0,0 +1,2 @@
1
+ import{useState,useEffect,useRef}from"react";const HEADER_HEIGHT=64;export function useThemedScrollpoints(scrollpoints){const[activeClassName,setActiveClassName]=useState("");const previousClassNameRef=useRef("");const observerRef=useRef(null);const initialCheckDoneRef=useRef(false);const intersectingElementsRef=useRef(new Map);useEffect(()=>{if(scrollpoints.length===0){setActiveClassName("");previousClassNameRef.current="";return}const intersectingElements=intersectingElementsRef.current;observerRef.current=new IntersectionObserver(entries=>{requestAnimationFrame(()=>{for(const entry of entries){const id=entry.target.id;if(entry.isIntersecting){intersectingElements.set(id,entry)}else{intersectingElements.delete(id)}}let bestMatch=null;for(const[id,entry]of intersectingElements){const scrollpoint=scrollpoints.find(sp=>sp.id===id);if(!scrollpoint)continue;const rect=entry.boundingClientRect||entry.target.getBoundingClientRect();const containsHeader=rect.top<=HEADER_HEIGHT&&rect.bottom>=HEADER_HEIGHT;if(containsHeader){const distance=Math.abs(rect.top-HEADER_HEIGHT);if(!bestMatch||distance<bestMatch.distance){bestMatch={scrollpoint,distance}}}}if(bestMatch&&bestMatch.scrollpoint.className!==previousClassNameRef.current){previousClassNameRef.current=bestMatch.scrollpoint.className;setActiveClassName(bestMatch.scrollpoint.className)}})},{rootMargin:`-${HEADER_HEIGHT}px 0px 0px 0px`,threshold:0});scrollpoints.forEach(({id})=>{const element=document.getElementById(id);if(element){observerRef.current?.observe(element)}else{console.warn(`useThemedScrollpoints: Element with id "${id}" not found in DOM`)}});const timeoutId=setTimeout(()=>{if(initialCheckDoneRef.current){return}initialCheckDoneRef.current=true;let bestMatch=null;for(const scrollpoint of scrollpoints){const element=document.getElementById(scrollpoint.id);if(element){const rect=element.getBoundingClientRect();const containsHeader=rect.top<=HEADER_HEIGHT&&rect.bottom>=HEADER_HEIGHT;if(containsHeader){const distance=Math.abs(rect.top-HEADER_HEIGHT);if(!bestMatch||distance<bestMatch.distance){bestMatch={scrollpoint,distance}}}}}if(bestMatch){previousClassNameRef.current=bestMatch.scrollpoint.className;setActiveClassName(bestMatch.scrollpoint.className)}},0);return()=>{clearTimeout(timeoutId);observerRef.current?.disconnect();observerRef.current=null;initialCheckDoneRef.current=false;intersectingElements.clear()}},[scrollpoints]);return activeClassName}
2
+ //# sourceMappingURL=use-themed-scrollpoints.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/core/hooks/use-themed-scrollpoints.ts"],"sourcesContent":["import { useState, useEffect, useRef } from \"react\";\nimport { ThemedScrollpoint } from \"../Header/types\";\n\nconst HEADER_HEIGHT = 64;\n\nexport function useThemedScrollpoints(\n scrollpoints: ThemedScrollpoint[],\n): string {\n const [activeClassName, setActiveClassName] = useState<string>(\"\");\n\n const previousClassNameRef = useRef<string>(\"\");\n const observerRef = useRef<IntersectionObserver | null>(null);\n const initialCheckDoneRef = useRef<boolean>(false);\n const intersectingElementsRef = useRef<\n Map<string, IntersectionObserverEntry>\n >(new Map());\n\n useEffect(() => {\n if (scrollpoints.length === 0) {\n // Clear active className when scrollpoints becomes empty\n // eslint-disable-next-line react-hooks/set-state-in-effect\n setActiveClassName(\"\");\n previousClassNameRef.current = \"\";\n return;\n }\n\n const intersectingElements = intersectingElementsRef.current;\n\n observerRef.current = new IntersectionObserver(\n (entries) => {\n requestAnimationFrame(() => {\n // Update the map of currently intersecting elements\n for (const entry of entries) {\n const id = (entry.target as HTMLElement).id;\n if (entry.isIntersecting) {\n intersectingElements.set(id, entry);\n } else {\n intersectingElements.delete(id);\n }\n }\n\n // Find the best match from ALL currently intersecting elements\n let bestMatch: {\n scrollpoint: ThemedScrollpoint;\n distance: number;\n } | null = null;\n\n for (const [id, entry] of intersectingElements) {\n const scrollpoint = scrollpoints.find((sp) => sp.id === id);\n if (!scrollpoint) continue;\n\n // Use boundingClientRect from entry if available, otherwise get from target\n const rect =\n entry.boundingClientRect || entry.target.getBoundingClientRect();\n const containsHeader =\n rect.top <= HEADER_HEIGHT && rect.bottom >= HEADER_HEIGHT;\n\n if (containsHeader) {\n const distance = Math.abs(rect.top - HEADER_HEIGHT);\n if (!bestMatch || distance < bestMatch.distance) {\n bestMatch = { scrollpoint, distance };\n }\n }\n }\n\n if (\n bestMatch &&\n bestMatch.scrollpoint.className !== previousClassNameRef.current\n ) {\n previousClassNameRef.current = bestMatch.scrollpoint.className;\n setActiveClassName(bestMatch.scrollpoint.className);\n }\n });\n },\n {\n rootMargin: `-${HEADER_HEIGHT}px 0px 0px 0px`,\n threshold: 0,\n },\n );\n\n scrollpoints.forEach(({ id }) => {\n const element = document.getElementById(id);\n if (element) {\n observerRef.current?.observe(element);\n } else {\n console.warn(\n `useThemedScrollpoints: Element with id \"${id}\" not found in DOM`,\n );\n }\n });\n\n // Manually check initial intersection state since IntersectionObserver\n // callbacks only fire on changes, not on initial observation\n // Use a small timeout to ensure DOM is fully laid out\n const timeoutId = setTimeout(() => {\n if (initialCheckDoneRef.current) {\n return;\n }\n initialCheckDoneRef.current = true;\n\n // Find the element closest to the header position\n let bestMatch: {\n scrollpoint: ThemedScrollpoint;\n distance: number;\n } | null = null;\n\n for (const scrollpoint of scrollpoints) {\n const element = document.getElementById(scrollpoint.id);\n if (element) {\n const rect = element.getBoundingClientRect();\n const containsHeader =\n rect.top <= HEADER_HEIGHT && rect.bottom >= HEADER_HEIGHT;\n\n if (containsHeader) {\n const distance = Math.abs(rect.top - HEADER_HEIGHT);\n if (!bestMatch || distance < bestMatch.distance) {\n bestMatch = { scrollpoint, distance };\n }\n }\n }\n }\n\n if (bestMatch) {\n previousClassNameRef.current = bestMatch.scrollpoint.className;\n setActiveClassName(bestMatch.scrollpoint.className);\n }\n }, 0);\n\n return () => {\n clearTimeout(timeoutId);\n observerRef.current?.disconnect();\n observerRef.current = null;\n initialCheckDoneRef.current = false;\n intersectingElements.clear();\n };\n }, [scrollpoints]);\n\n return activeClassName;\n}\n"],"names":["useState","useEffect","useRef","HEADER_HEIGHT","useThemedScrollpoints","scrollpoints","activeClassName","setActiveClassName","previousClassNameRef","observerRef","initialCheckDoneRef","intersectingElementsRef","Map","length","current","intersectingElements","IntersectionObserver","entries","requestAnimationFrame","entry","id","target","isIntersecting","set","delete","bestMatch","scrollpoint","find","sp","rect","boundingClientRect","getBoundingClientRect","containsHeader","top","bottom","distance","Math","abs","className","rootMargin","threshold","forEach","element","document","getElementById","observe","console","warn","timeoutId","setTimeout","clearTimeout","disconnect","clear"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{describe,it,expect,beforeEach,afterEach,vi}from"vitest";import{renderHook,act}from"@testing-library/react";import{useThemedScrollpoints}from"./use-themed-scrollpoints";describe("useThemedScrollpoints",()=>{let observerCallback;let mockObserve;let mockUnobserve;let mockDisconnect;let originalIntersectionObserver;let originalRequestAnimationFrame;beforeEach(()=>{vi.useFakeTimers();originalIntersectionObserver=global.IntersectionObserver;originalRequestAnimationFrame=global.requestAnimationFrame;mockObserve=vi.fn();mockUnobserve=vi.fn();mockDisconnect=vi.fn();global.IntersectionObserver=vi.fn(callback=>{observerCallback=callback;return{observe:mockObserve,unobserve:mockUnobserve,disconnect:mockDisconnect}});global.requestAnimationFrame=vi.fn(cb=>{cb(0);return 0})});afterEach(()=>{vi.clearAllMocks();vi.useRealTimers();document.body.innerHTML="";global.IntersectionObserver=originalIntersectionObserver;global.requestAnimationFrame=originalRequestAnimationFrame});it("returns first scrollpoint className on mount",()=>{const elem1=document.createElement("div");elem1.id="zone1";elem1.getBoundingClientRect=vi.fn().mockReturnValue({top:0,bottom:200});const elem2=document.createElement("div");elem2.id="zone2";elem2.getBoundingClientRect=vi.fn().mockReturnValue({top:200,bottom:400});document.body.appendChild(elem1);document.body.appendChild(elem2);const scrollpoints=[{id:"zone1",className:"theme-light"},{id:"zone2",className:"theme-dark"}];const{result}=renderHook(()=>useThemedScrollpoints(scrollpoints));expect(result.current).toBe("");act(()=>{vi.runAllTimers()});expect(result.current).toBe("theme-light")});it("returns empty string when no scrollpoints provided",()=>{const{result}=renderHook(()=>useThemedScrollpoints([]));expect(result.current).toBe("")});it("clears activeClassName when scrollpoints changes from non-empty to empty",()=>{const elem=document.createElement("div");elem.id="zone1";elem.getBoundingClientRect=vi.fn().mockReturnValue({top:0,bottom:200});document.body.appendChild(elem);const{result,rerender}=renderHook(({scrollpoints})=>useThemedScrollpoints(scrollpoints),{initialProps:{scrollpoints:[{id:"zone1",className:"theme-light"}]}});act(()=>{vi.runAllTimers()});expect(result.current).toBe("theme-light");rerender({scrollpoints:[]});expect(result.current).toBe("")});it("does not create IntersectionObserver when no scrollpoints provided",()=>{renderHook(()=>useThemedScrollpoints([]));expect(IntersectionObserver).not.toHaveBeenCalled()});it("creates IntersectionObserver with correct config",()=>{const scrollpoints=[{id:"zone1",className:"theme-light"}];const elem=document.createElement("div");elem.id="zone1";document.body.appendChild(elem);renderHook(()=>useThemedScrollpoints(scrollpoints));expect(IntersectionObserver).toHaveBeenCalledWith(expect.any(Function),expect.objectContaining({rootMargin:"-64px 0px 0px 0px",threshold:0}))});it("observes all scrollpoint elements that exist in DOM",()=>{const elem1=document.createElement("div");elem1.id="zone1";const elem2=document.createElement("div");elem2.id="zone2";document.body.appendChild(elem1);document.body.appendChild(elem2);const scrollpoints=[{id:"zone1",className:"theme-light"},{id:"zone2",className:"theme-dark"}];renderHook(()=>useThemedScrollpoints(scrollpoints));expect(mockObserve).toHaveBeenCalledTimes(2);expect(mockObserve).toHaveBeenCalledWith(elem1);expect(mockObserve).toHaveBeenCalledWith(elem2)});it("logs warning for missing DOM elements",()=>{const consoleWarn=vi.spyOn(console,"warn").mockImplementation(()=>{});const scrollpoints=[{id:"non-existent",className:"theme"}];renderHook(()=>useThemedScrollpoints(scrollpoints));expect(consoleWarn).toHaveBeenCalledWith(expect.stringContaining('Element with id "non-existent" not found'));consoleWarn.mockRestore()});it("observes existing elements and warns about missing ones",()=>{const consoleWarn=vi.spyOn(console,"warn").mockImplementation(()=>{});const elem=document.createElement("div");elem.id="zone1";document.body.appendChild(elem);const scrollpoints=[{id:"zone1",className:"theme-light"},{id:"missing",className:"theme-dark"}];renderHook(()=>useThemedScrollpoints(scrollpoints));expect(mockObserve).toHaveBeenCalledTimes(1);expect(mockObserve).toHaveBeenCalledWith(elem);expect(consoleWarn).toHaveBeenCalledWith(expect.stringContaining('Element with id "missing" not found'));consoleWarn.mockRestore()});it("updates className when intersection occurs",()=>{const elem1=document.createElement("div");elem1.id="zone1";elem1.getBoundingClientRect=vi.fn().mockReturnValue({top:0,bottom:200});const elem2=document.createElement("div");elem2.id="zone2";elem2.getBoundingClientRect=vi.fn().mockReturnValue({top:50,bottom:250});document.body.appendChild(elem1);document.body.appendChild(elem2);const scrollpoints=[{id:"zone1",className:"theme-light"},{id:"zone2",className:"theme-dark"}];const{result}=renderHook(()=>useThemedScrollpoints(scrollpoints));act(()=>{vi.runAllTimers()});expect(result.current).toBe("theme-dark");act(()=>{observerCallback([{target:elem1,isIntersecting:true,boundingClientRect:{top:60,bottom:260,left:0,right:0,x:0,y:60,width:0,height:200}},{target:elem2,isIntersecting:true,boundingClientRect:{top:100,bottom:300,left:0,right:0,x:0,y:100,width:0,height:200}}],{})});expect(result.current).toBe("theme-light")});it("does not update className if same scrollpoint intersects again",()=>{const elem=document.createElement("div");elem.id="zone1";elem.getBoundingClientRect=vi.fn().mockReturnValue({top:0,bottom:200});document.body.appendChild(elem);const scrollpoints=[{id:"zone1",className:"theme-light"}];const{result}=renderHook(()=>useThemedScrollpoints(scrollpoints));act(()=>{vi.runAllTimers()});expect(result.current).toBe("theme-light");const renderCount=result.current;observerCallback([{target:elem,isIntersecting:true,boundingClientRect:{top:0,bottom:200,left:0,right:0,x:0,y:0,width:0,height:200}}],{});expect(result.current).toBe(renderCount)});it("ignores non-intersecting entries",()=>{const elem1=document.createElement("div");elem1.id="zone1";elem1.getBoundingClientRect=vi.fn().mockReturnValue({top:0,bottom:200});const elem2=document.createElement("div");elem2.id="zone2";elem2.getBoundingClientRect=vi.fn().mockReturnValue({top:200,bottom:400});document.body.appendChild(elem1);document.body.appendChild(elem2);const scrollpoints=[{id:"zone1",className:"theme-light"},{id:"zone2",className:"theme-dark"}];const{result}=renderHook(()=>useThemedScrollpoints(scrollpoints));act(()=>{vi.runAllTimers()});expect(result.current).toBe("theme-light");observerCallback([{target:elem2,isIntersecting:false}],{});expect(result.current).toBe("theme-light")});it("uses closest element to header when multiple entries intersect",()=>{const elem1=document.createElement("div");elem1.id="zone1";elem1.getBoundingClientRect=vi.fn().mockReturnValue({top:0,bottom:50});const elem2=document.createElement("div");elem2.id="zone2";elem2.getBoundingClientRect=vi.fn().mockReturnValue({top:60,bottom:200});const elem3=document.createElement("div");elem3.id="zone3";elem3.getBoundingClientRect=vi.fn().mockReturnValue({top:10,bottom:100});document.body.appendChild(elem1);document.body.appendChild(elem2);document.body.appendChild(elem3);const scrollpoints=[{id:"zone1",className:"theme-light"},{id:"zone2",className:"theme-dark"},{id:"zone3",className:"theme-blue"}];const{result}=renderHook(()=>useThemedScrollpoints(scrollpoints));act(()=>{vi.runAllTimers()});expect(result.current).toBe("theme-dark");act(()=>{observerCallback([{target:elem1,isIntersecting:false,boundingClientRect:{top:0,bottom:50,left:0,right:0,x:0,y:0,width:0,height:50}},{target:elem2,isIntersecting:true,boundingClientRect:{top:100,bottom:300,left:0,right:0,x:0,y:100,width:0,height:200}},{target:elem3,isIntersecting:true,boundingClientRect:{top:62,bottom:152,left:0,right:0,x:0,y:62,width:0,height:90}}],{})});expect(result.current).toBe("theme-blue")});it("disconnects observer on unmount",()=>{const elem=document.createElement("div");elem.id="zone1";document.body.appendChild(elem);const scrollpoints=[{id:"zone1",className:"theme-light"}];const{unmount}=renderHook(()=>useThemedScrollpoints(scrollpoints));unmount();expect(mockDisconnect).toHaveBeenCalled()});it("recreates observer when scrollpoints change",()=>{const elem1=document.createElement("div");elem1.id="zone1";const elem2=document.createElement("div");elem2.id="zone2";document.body.appendChild(elem1);document.body.appendChild(elem2);const{rerender}=renderHook(({scrollpoints})=>useThemedScrollpoints(scrollpoints),{initialProps:{scrollpoints:[{id:"zone1",className:"theme-light"}]}});expect(IntersectionObserver).toHaveBeenCalledTimes(1);expect(mockObserve).toHaveBeenCalledTimes(1);rerender({scrollpoints:[{id:"zone1",className:"theme-light"},{id:"zone2",className:"theme-dark"}]});expect(mockDisconnect).toHaveBeenCalledTimes(1);expect(IntersectionObserver).toHaveBeenCalledTimes(2);expect(mockObserve).toHaveBeenCalledTimes(3)});it("uses requestAnimationFrame for state updates",()=>{const elem=document.createElement("div");elem.id="zone1";document.body.appendChild(elem);const scrollpoints=[{id:"zone1",className:"theme-light"},{id:"zone2",className:"theme-dark"}];renderHook(()=>useThemedScrollpoints(scrollpoints));const rafSpy=vi.spyOn(global,"requestAnimationFrame");observerCallback([{target:elem,isIntersecting:true}],{});expect(rafSpy).toHaveBeenCalled();rafSpy.mockRestore()})});
2
+ //# sourceMappingURL=use-themed-scrollpoints.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/core/hooks/use-themed-scrollpoints.test.ts"],"sourcesContent":["/**\n * @vitest-environment jsdom\n */\n\nimport { describe, it, expect, beforeEach, afterEach, vi } from \"vitest\";\nimport { renderHook, act } from \"@testing-library/react\";\nimport { useThemedScrollpoints } from \"./use-themed-scrollpoints\";\n\ndescribe(\"useThemedScrollpoints\", () => {\n let observerCallback: IntersectionObserverCallback;\n let mockObserve: ReturnType<typeof vi.fn>;\n let mockUnobserve: ReturnType<typeof vi.fn>;\n let mockDisconnect: ReturnType<typeof vi.fn>;\n let originalIntersectionObserver: typeof IntersectionObserver;\n let originalRequestAnimationFrame: typeof requestAnimationFrame;\n\n beforeEach(() => {\n vi.useFakeTimers();\n\n // Save originals to prevent test pollution\n originalIntersectionObserver = global.IntersectionObserver;\n originalRequestAnimationFrame = global.requestAnimationFrame;\n\n // Mock IntersectionObserver\n mockObserve = vi.fn();\n mockUnobserve = vi.fn();\n mockDisconnect = vi.fn();\n\n global.IntersectionObserver = vi.fn((callback) => {\n observerCallback = callback;\n return {\n observe: mockObserve,\n unobserve: mockUnobserve,\n disconnect: mockDisconnect,\n };\n }) as unknown as typeof IntersectionObserver;\n\n // Mock requestAnimationFrame\n global.requestAnimationFrame = vi.fn((cb) => {\n cb(0);\n return 0;\n });\n });\n\n afterEach(() => {\n vi.clearAllMocks();\n vi.useRealTimers();\n document.body.innerHTML = \"\";\n // Restore originals to prevent test pollution\n global.IntersectionObserver = originalIntersectionObserver;\n global.requestAnimationFrame = originalRequestAnimationFrame;\n });\n\n it(\"returns first scrollpoint className on mount\", () => {\n // Create DOM elements\n const elem1 = document.createElement(\"div\");\n elem1.id = \"zone1\";\n elem1.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 0, bottom: 200 });\n const elem2 = document.createElement(\"div\");\n elem2.id = \"zone2\";\n elem2.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 200, bottom: 400 });\n document.body.appendChild(elem1);\n document.body.appendChild(elem2);\n\n const scrollpoints = [\n { id: \"zone1\", className: \"theme-light\" },\n { id: \"zone2\", className: \"theme-dark\" },\n ];\n\n const { result } = renderHook(() => useThemedScrollpoints(scrollpoints));\n\n // Initial state is empty\n expect(result.current).toBe(\"\");\n\n // Advance timer to trigger initial check\n act(() => {\n vi.runAllTimers();\n });\n\n // Should now have the first scrollpoint's className\n expect(result.current).toBe(\"theme-light\");\n });\n\n it(\"returns empty string when no scrollpoints provided\", () => {\n const { result } = renderHook(() => useThemedScrollpoints([]));\n expect(result.current).toBe(\"\");\n });\n\n it(\"clears activeClassName when scrollpoints changes from non-empty to empty\", () => {\n const elem = document.createElement(\"div\");\n elem.id = \"zone1\";\n elem.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 0, bottom: 200 });\n document.body.appendChild(elem);\n\n const { result, rerender } = renderHook(\n ({ scrollpoints }) => useThemedScrollpoints(scrollpoints),\n {\n initialProps: {\n scrollpoints: [{ id: \"zone1\", className: \"theme-light\" }],\n },\n },\n );\n\n // Wait for initial check\n act(() => {\n vi.runAllTimers();\n });\n expect(result.current).toBe(\"theme-light\");\n\n // Change to empty scrollpoints\n rerender({ scrollpoints: [] });\n\n // Should clear the className\n expect(result.current).toBe(\"\");\n });\n\n it(\"does not create IntersectionObserver when no scrollpoints provided\", () => {\n renderHook(() => useThemedScrollpoints([]));\n\n expect(IntersectionObserver).not.toHaveBeenCalled();\n });\n\n it(\"creates IntersectionObserver with correct config\", () => {\n const scrollpoints = [{ id: \"zone1\", className: \"theme-light\" }];\n\n // Create DOM element\n const elem = document.createElement(\"div\");\n elem.id = \"zone1\";\n document.body.appendChild(elem);\n\n renderHook(() => useThemedScrollpoints(scrollpoints));\n\n expect(IntersectionObserver).toHaveBeenCalledWith(\n expect.any(Function),\n expect.objectContaining({\n rootMargin: \"-64px 0px 0px 0px\",\n threshold: 0,\n }),\n );\n });\n\n it(\"observes all scrollpoint elements that exist in DOM\", () => {\n // Create DOM elements\n const elem1 = document.createElement(\"div\");\n elem1.id = \"zone1\";\n const elem2 = document.createElement(\"div\");\n elem2.id = \"zone2\";\n document.body.appendChild(elem1);\n document.body.appendChild(elem2);\n\n const scrollpoints = [\n { id: \"zone1\", className: \"theme-light\" },\n { id: \"zone2\", className: \"theme-dark\" },\n ];\n\n renderHook(() => useThemedScrollpoints(scrollpoints));\n\n expect(mockObserve).toHaveBeenCalledTimes(2);\n expect(mockObserve).toHaveBeenCalledWith(elem1);\n expect(mockObserve).toHaveBeenCalledWith(elem2);\n });\n\n it(\"logs warning for missing DOM elements\", () => {\n const consoleWarn = vi.spyOn(console, \"warn\").mockImplementation(() => {});\n\n const scrollpoints = [{ id: \"non-existent\", className: \"theme\" }];\n\n renderHook(() => useThemedScrollpoints(scrollpoints));\n\n expect(consoleWarn).toHaveBeenCalledWith(\n expect.stringContaining('Element with id \"non-existent\" not found'),\n );\n\n consoleWarn.mockRestore();\n });\n\n it(\"observes existing elements and warns about missing ones\", () => {\n const consoleWarn = vi.spyOn(console, \"warn\").mockImplementation(() => {});\n\n // Create only one element\n const elem = document.createElement(\"div\");\n elem.id = \"zone1\";\n document.body.appendChild(elem);\n\n const scrollpoints = [\n { id: \"zone1\", className: \"theme-light\" },\n { id: \"missing\", className: \"theme-dark\" },\n ];\n\n renderHook(() => useThemedScrollpoints(scrollpoints));\n\n expect(mockObserve).toHaveBeenCalledTimes(1);\n expect(mockObserve).toHaveBeenCalledWith(elem);\n expect(consoleWarn).toHaveBeenCalledWith(\n expect.stringContaining('Element with id \"missing\" not found'),\n );\n\n consoleWarn.mockRestore();\n });\n\n it(\"updates className when intersection occurs\", () => {\n // Create DOM elements\n const elem1 = document.createElement(\"div\");\n elem1.id = \"zone1\";\n elem1.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 0, bottom: 200 });\n const elem2 = document.createElement(\"div\");\n elem2.id = \"zone2\";\n elem2.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 50, bottom: 250 });\n document.body.appendChild(elem1);\n document.body.appendChild(elem2);\n\n const scrollpoints = [\n { id: \"zone1\", className: \"theme-light\" },\n { id: \"zone2\", className: \"theme-dark\" },\n ];\n\n const { result } = renderHook(() => useThemedScrollpoints(scrollpoints));\n\n // Wait for initial check\n // elem2 (top=50) is closer to header (64) than elem1 (top=0)\n // distance for elem1: |0 - 64| = 64\n // distance for elem2: |50 - 64| = 14\n act(() => {\n vi.runAllTimers();\n });\n expect(result.current).toBe(\"theme-dark\");\n\n // Simulate scrolling - elem1 moves closer to header position\n act(() => {\n observerCallback(\n [\n {\n target: elem1,\n isIntersecting: true,\n boundingClientRect: {\n top: 60,\n bottom: 260,\n left: 0,\n right: 0,\n x: 0,\n y: 60,\n width: 0,\n height: 200,\n },\n } as unknown as IntersectionObserverEntry,\n {\n target: elem2,\n isIntersecting: true,\n boundingClientRect: {\n top: 100,\n bottom: 300,\n left: 0,\n right: 0,\n x: 0,\n y: 100,\n width: 0,\n height: 200,\n },\n } as unknown as IntersectionObserverEntry,\n ],\n {} as IntersectionObserver,\n );\n });\n\n // elem1 is now closer to header (distance=4 vs elem2 distance=36)\n expect(result.current).toBe(\"theme-light\");\n });\n\n it(\"does not update className if same scrollpoint intersects again\", () => {\n const elem = document.createElement(\"div\");\n elem.id = \"zone1\";\n elem.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 0, bottom: 200 });\n document.body.appendChild(elem);\n\n const scrollpoints = [{ id: \"zone1\", className: \"theme-light\" }];\n\n const { result } = renderHook(() => useThemedScrollpoints(scrollpoints));\n\n // Wait for initial check\n act(() => {\n vi.runAllTimers();\n });\n expect(result.current).toBe(\"theme-light\");\n\n // Simulate intersection with same element\n const renderCount = result.current;\n observerCallback(\n [\n {\n target: elem,\n isIntersecting: true,\n boundingClientRect: {\n top: 0,\n bottom: 200,\n left: 0,\n right: 0,\n x: 0,\n y: 0,\n width: 0,\n height: 200,\n },\n } as unknown as IntersectionObserverEntry,\n ],\n {} as IntersectionObserver,\n );\n\n // Should not trigger re-render (className unchanged)\n expect(result.current).toBe(renderCount);\n });\n\n it(\"ignores non-intersecting entries\", () => {\n const elem1 = document.createElement(\"div\");\n elem1.id = \"zone1\";\n elem1.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 0, bottom: 200 });\n const elem2 = document.createElement(\"div\");\n elem2.id = \"zone2\";\n elem2.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 200, bottom: 400 });\n document.body.appendChild(elem1);\n document.body.appendChild(elem2);\n\n const scrollpoints = [\n { id: \"zone1\", className: \"theme-light\" },\n { id: \"zone2\", className: \"theme-dark\" },\n ];\n\n const { result } = renderHook(() => useThemedScrollpoints(scrollpoints));\n\n // Wait for initial check\n act(() => {\n vi.runAllTimers();\n });\n expect(result.current).toBe(\"theme-light\");\n\n // Simulate non-intersecting entry\n observerCallback(\n [\n {\n target: elem2,\n isIntersecting: false,\n } as unknown as IntersectionObserverEntry,\n ],\n {} as IntersectionObserver,\n );\n\n // Should remain unchanged\n expect(result.current).toBe(\"theme-light\");\n });\n\n it(\"uses closest element to header when multiple entries intersect\", () => {\n const elem1 = document.createElement(\"div\");\n elem1.id = \"zone1\";\n elem1.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 0, bottom: 50 });\n const elem2 = document.createElement(\"div\");\n elem2.id = \"zone2\";\n // zone2 top is at 60, which is closer to HEADER_HEIGHT (64) than zone3\n elem2.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 60, bottom: 200 });\n const elem3 = document.createElement(\"div\");\n elem3.id = \"zone3\";\n elem3.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 10, bottom: 100 });\n document.body.appendChild(elem1);\n document.body.appendChild(elem2);\n document.body.appendChild(elem3);\n\n const scrollpoints = [\n { id: \"zone1\", className: \"theme-light\" },\n { id: \"zone2\", className: \"theme-dark\" },\n { id: \"zone3\", className: \"theme-blue\" },\n ];\n\n const { result } = renderHook(() => useThemedScrollpoints(scrollpoints));\n\n // Wait for initial check\n // elem2 (top=60) is closest to header (64) with distance=4\n // elem3 (top=10) has distance=54\n // elem1 (top=0) doesn't contain header (bottom=50 < 64)\n act(() => {\n vi.runAllTimers();\n });\n expect(result.current).toBe(\"theme-dark\");\n\n // Simulate scrolling where elem3 becomes closest to header\n act(() => {\n observerCallback(\n [\n {\n target: elem1,\n isIntersecting: false,\n boundingClientRect: {\n top: 0,\n bottom: 50,\n left: 0,\n right: 0,\n x: 0,\n y: 0,\n width: 0,\n height: 50,\n },\n } as unknown as IntersectionObserverEntry,\n {\n target: elem2,\n isIntersecting: true,\n boundingClientRect: {\n top: 100,\n bottom: 300,\n left: 0,\n right: 0,\n x: 0,\n y: 100,\n width: 0,\n height: 200,\n },\n } as unknown as IntersectionObserverEntry,\n {\n target: elem3,\n isIntersecting: true,\n boundingClientRect: {\n top: 62,\n bottom: 152,\n left: 0,\n right: 0,\n x: 0,\n y: 62,\n width: 0,\n height: 90,\n },\n } as unknown as IntersectionObserverEntry,\n ],\n {} as IntersectionObserver,\n );\n });\n\n // elem3 is now closest to HEADER_HEIGHT (64)\n // distance for elem2: |100 - 64| = 36\n // distance for elem3: |62 - 64| = 2\n expect(result.current).toBe(\"theme-blue\");\n });\n\n it(\"disconnects observer on unmount\", () => {\n const elem = document.createElement(\"div\");\n elem.id = \"zone1\";\n document.body.appendChild(elem);\n\n const scrollpoints = [{ id: \"zone1\", className: \"theme-light\" }];\n\n const { unmount } = renderHook(() => useThemedScrollpoints(scrollpoints));\n\n unmount();\n\n expect(mockDisconnect).toHaveBeenCalled();\n });\n\n it(\"recreates observer when scrollpoints change\", () => {\n const elem1 = document.createElement(\"div\");\n elem1.id = \"zone1\";\n const elem2 = document.createElement(\"div\");\n elem2.id = \"zone2\";\n document.body.appendChild(elem1);\n document.body.appendChild(elem2);\n\n const { rerender } = renderHook(\n ({ scrollpoints }) => useThemedScrollpoints(scrollpoints),\n {\n initialProps: {\n scrollpoints: [{ id: \"zone1\", className: \"theme-light\" }],\n },\n },\n );\n\n expect(IntersectionObserver).toHaveBeenCalledTimes(1);\n expect(mockObserve).toHaveBeenCalledTimes(1);\n\n // Change scrollpoints\n rerender({\n scrollpoints: [\n { id: \"zone1\", className: \"theme-light\" },\n { id: \"zone2\", className: \"theme-dark\" },\n ],\n });\n\n // Should disconnect old observer and create new one\n expect(mockDisconnect).toHaveBeenCalledTimes(1);\n expect(IntersectionObserver).toHaveBeenCalledTimes(2);\n expect(mockObserve).toHaveBeenCalledTimes(3); // 1 from first render + 2 from second\n });\n\n it(\"uses requestAnimationFrame for state updates\", () => {\n const elem = document.createElement(\"div\");\n elem.id = \"zone1\";\n document.body.appendChild(elem);\n\n const scrollpoints = [\n { id: \"zone1\", className: \"theme-light\" },\n { id: \"zone2\", className: \"theme-dark\" },\n ];\n\n renderHook(() => useThemedScrollpoints(scrollpoints));\n\n const rafSpy = vi.spyOn(global, \"requestAnimationFrame\");\n\n // Simulate intersection\n observerCallback(\n [\n {\n target: elem,\n isIntersecting: true,\n } as unknown as IntersectionObserverEntry,\n ],\n {} as IntersectionObserver,\n );\n\n expect(rafSpy).toHaveBeenCalled();\n\n rafSpy.mockRestore();\n });\n});\n"],"names":["describe","it","expect","beforeEach","afterEach","vi","renderHook","act","useThemedScrollpoints","observerCallback","mockObserve","mockUnobserve","mockDisconnect","originalIntersectionObserver","originalRequestAnimationFrame","useFakeTimers","global","IntersectionObserver","requestAnimationFrame","fn","callback","observe","unobserve","disconnect","cb","clearAllMocks","useRealTimers","document","body","innerHTML","elem1","createElement","id","getBoundingClientRect","mockReturnValue","top","bottom","elem2","appendChild","scrollpoints","className","result","current","toBe","runAllTimers","elem","rerender","initialProps","not","toHaveBeenCalled","toHaveBeenCalledWith","any","Function","objectContaining","rootMargin","threshold","toHaveBeenCalledTimes","consoleWarn","spyOn","console","mockImplementation","stringContaining","mockRestore","target","isIntersecting","boundingClientRect","left","right","x","y","width","height","renderCount","elem3","unmount","rafSpy"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/remote-blogs-posts.js"],"sourcesContent":["/* global __ENABLE_FETCH_WITH_CREDENTIALS__ */\n\nimport { isJsonResponse } from \"./remote-data-util\";\n\nconst fetchBlogPosts = async (store, blogUrl) => {\n try {\n if (!blogUrl) {\n console.log(\n `Skipping fetching blog posts, invalid blogUrl: \"${blogUrl}\"`,\n );\n return;\n }\n\n const options = {\n headers: {\n accept: \"application/json\",\n },\n cache: \"no-cache\",\n };\n\n if (__ENABLE_FETCH_WITH_CREDENTIALS__) {\n options.credentials = \"include\";\n }\n\n const res = await fetch(blogUrl, options);\n\n if (isJsonResponse(res.headers.get(\"content-type\"))) {\n const payload = await res.json();\n store.dispatch({ type: \"blog/loaded\", payload });\n } else {\n throw new Error(\"Blog posts url is not serving json\");\n }\n } catch (e) {\n console.warn(\"Could not fetch blog posts due to error:\", e);\n }\n};\n\nconst initialState = { recent: null };\n\nconst REDUCER_KEY = \"blogPosts\";\n\nconst reducerBlogPosts = {\n [REDUCER_KEY]: (state = initialState, action) => {\n switch (action.type) {\n case \"blog/loaded\":\n return { ...state, recent: action.payload };\n default:\n return state;\n }\n },\n};\n\nconst selectRecentBlogPosts = (store) => store.getState()[REDUCER_KEY]?.recent;\n\nexport { fetchBlogPosts, reducerBlogPosts, selectRecentBlogPosts };\n"],"names":["isJsonResponse","fetchBlogPosts","store","blogUrl","console","log","options","headers","accept","cache","credentials","res","fetch","get","payload","json","dispatch","type","Error","e","warn","initialState","recent","REDUCER_KEY","reducerBlogPosts","state","action","selectRecentBlogPosts","getState"],"mappings":"AAEA,OAASA,cAAc,KAAQ,oBAAqB,CAEpD,MAAMC,eAAiB,MAAOC,MAAOC,WACnC,GAAI,CACF,GAAI,CAACA,QAAS,CACZC,QAAQC,GAAG,CACT,CAAC,gDAAgD,EAAEF,QAAQ,CAAC,CAAC,EAE/D,MACF,CAEA,MAAMG,QAAU,CACdC,QAAS,CACPC,OAAQ,kBACV,EACAC,MAAO,UACT,EAEA,GAWJ,MAX2C,CACrCH,QAAQI,WAAW,CAAG,SACxB,CAEA,MAAMC,IAAM,MAAMC,MAAMT,QAASG,SAEjC,GAAIN,eAAeW,IAAIJ,OAAO,CAACM,GAAG,CAAC,iBAAkB,CACnD,MAAMC,QAAU,MAAMH,IAAII,IAAI,GAC9Bb,MAAMc,QAAQ,CAAC,CAAEC,KAAM,cAAeH,OAAQ,EAChD,KAAO,CACL,MAAM,IAAII,MAAM,qCAClB,CACF,CAAE,MAAOC,EAAG,CACVf,QAAQgB,IAAI,CAAC,2CAA4CD,EAC3D,CACF,EAEA,MAAME,aAAe,CAAEC,OAAQ,IAAK,EAEpC,MAAMC,YAAc,YAEpB,MAAMC,iBAAmB,CACvB,CAACD,YAAY,CAAE,CAACE,MAAQJ,YAAY,CAAEK,UACpC,OAAQA,OAAOT,IAAI,EACjB,IAAK,cACH,MAAO,CAAE,GAAGQ,KAAK,CAAEH,OAAQI,OAAOZ,OAAO,AAAC,CAC5C,SACE,OAAOW,KACX,CACF,CACF,EAEA,MAAME,sBAAwB,AAACzB,OAAUA,MAAM0B,QAAQ,EAAE,CAACL,YAAY,EAAED,MAExE,QAASrB,cAAc,CAAEuB,gBAAgB,CAAEG,qBAAqB,CAAG"}
1
+ {"version":3,"sources":["../../src/core/remote-blogs-posts.js"],"sourcesContent":["/* global __ENABLE_FETCH_WITH_CREDENTIALS__ */\n\nimport { isJsonResponse } from \"./remote-data-util\";\n\nconst fetchBlogPosts = async (store, blogUrl) => {\n try {\n if (!blogUrl) {\n console.log(\n `Skipping fetching blog posts, invalid blogUrl: \"${blogUrl}\"`,\n );\n return;\n }\n\n const options = {\n headers: {\n accept: \"application/json\",\n },\n cache: \"no-cache\",\n };\n\n if (__ENABLE_FETCH_WITH_CREDENTIALS__) {\n options.credentials = \"include\";\n }\n\n const res = await fetch(blogUrl, options);\n\n if (isJsonResponse(res.headers.get(\"content-type\"))) {\n const payload = await res.json();\n store.dispatch({ type: \"blog/loaded\", payload });\n } else {\n throw new Error(\"Blog posts url is not serving json\");\n }\n } catch (e) {\n console.warn(\"Could not fetch blog posts due to error:\", e);\n }\n};\n\nconst initialState = { recent: null };\n\nconst REDUCER_KEY = \"blogPosts\";\n\nconst reducerBlogPosts = {\n [REDUCER_KEY]: (state = initialState, action) => {\n switch (action.type) {\n case \"blog/loaded\":\n return { ...state, recent: action.payload };\n default:\n return state;\n }\n },\n};\n\nconst selectRecentBlogPosts = (store) => store.getState()[REDUCER_KEY]?.recent;\n\nexport { fetchBlogPosts, reducerBlogPosts, selectRecentBlogPosts };\n"],"names":["isJsonResponse","fetchBlogPosts","store","blogUrl","console","log","options","headers","accept","cache","credentials","res","fetch","get","payload","json","dispatch","type","Error","e","warn","initialState","recent","REDUCER_KEY","reducerBlogPosts","state","action","selectRecentBlogPosts","getState"],"mappings":"AAEA,OAASA,cAAc,KAAQ,oBAAqB,CAEpD,MAAMC,eAAiB,MAAOC,MAAOC,WACnC,GAAI,CACF,GAAI,CAACA,QAAS,CACZC,QAAQC,GAAG,CACT,CAAC,gDAAgD,EAAEF,QAAQ,CAAC,CAAC,EAE/D,MACF,CAEA,MAAMG,QAAU,CACdC,QAAS,CACPC,OAAQ,kBACV,EACAC,MAAO,UACT,EAEA,SAAuC,CACrCH,QAAQI,WAAW,CAAG,SACxB,CAEA,MAAMC,IAAM,MAAMC,MAAMT,QAASG,SAEjC,GAAIN,eAAeW,IAAIJ,OAAO,CAACM,GAAG,CAAC,iBAAkB,CACnD,MAAMC,QAAU,MAAMH,IAAII,IAAI,GAC9Bb,MAAMc,QAAQ,CAAC,CAAEC,KAAM,cAAeH,OAAQ,EAChD,KAAO,CACL,MAAM,IAAII,MAAM,qCAClB,CACF,CAAE,MAAOC,EAAG,CACVf,QAAQgB,IAAI,CAAC,2CAA4CD,EAC3D,CACF,EAEA,MAAME,aAAe,CAAEC,OAAQ,IAAK,EAEpC,MAAMC,YAAc,YAEpB,MAAMC,iBAAmB,CACvB,CAACD,YAAY,CAAE,CAACE,MAAQJ,YAAY,CAAEK,UACpC,OAAQA,OAAOT,IAAI,EACjB,IAAK,cACH,MAAO,CAAE,GAAGQ,KAAK,CAAEH,OAAQI,OAAOZ,OAAO,AAAC,CAC5C,SACE,OAAOW,KACX,CACF,CACF,EAEA,MAAME,sBAAwB,AAACzB,OAAUA,MAAM0B,QAAQ,EAAE,CAACL,YAAY,EAAED,MAExE,QAASrB,cAAc,CAAEuB,gBAAgB,CAAEG,qBAAqB,CAAG"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/remote-session-data.js"],"sourcesContent":["/* global __ENABLE_FETCH_WITH_CREDENTIALS__ */\n\n// Fetches current users session data\n// Assumes an authenticated session, so will only work when used on ably.com/ably.io\n\nimport { isJsonResponse } from \"./remote-data-util\";\n\nconst NOT_FOUND_ERROR_CODE = \"not-found\";\n\nconst fetchSessionData = async (store, sessionUrl) => {\n const sessionLoaded = (payload = {}) =>\n store.dispatch({ type: \"session/loaded\", payload });\n\n try {\n if (!sessionUrl) {\n console.log(\n `Skipping fetching session, invalid sessionUrl: \"${sessionUrl}\"`,\n );\n sessionLoaded();\n return;\n }\n\n const options = {\n headers: {\n accept: \"application/json\",\n },\n cache: \"no-cache\",\n };\n\n if (__ENABLE_FETCH_WITH_CREDENTIALS__) {\n options.credentials = \"include\";\n }\n\n const res = await fetch(sessionUrl, options);\n const jsonResponse = isJsonResponse(res.headers.get(\"content-type\"));\n\n if (!jsonResponse) {\n throw new Error(\"Session endpoint is not serving json\");\n }\n\n const payload = await res.json();\n\n if (payload.error === NOT_FOUND_ERROR_CODE) {\n sessionLoaded();\n } else {\n sessionLoaded(payload);\n }\n } catch (e) {\n sessionLoaded();\n console.warn(\"Could not fetch session data due to error:\", e);\n }\n};\n\nconst initialState = { data: null };\n\nconst REDUCER_KEY = \"session\";\n\nconst reducerSessionData = {\n [REDUCER_KEY]: (state = initialState, action) => {\n switch (action.type) {\n case \"session/loaded\":\n return { ...state, data: action.payload };\n default:\n return state;\n }\n },\n};\n\nconst selectSessionData = (store) => store.getState()[REDUCER_KEY]?.data;\n\nexport { fetchSessionData, reducerSessionData, selectSessionData };\n"],"names":["isJsonResponse","NOT_FOUND_ERROR_CODE","fetchSessionData","store","sessionUrl","sessionLoaded","payload","dispatch","type","console","log","options","headers","accept","cache","credentials","res","fetch","jsonResponse","get","Error","json","error","e","warn","initialState","data","REDUCER_KEY","reducerSessionData","state","action","selectSessionData","getState"],"mappings":"AAKA,OAASA,cAAc,KAAQ,oBAAqB,CAEpD,MAAMC,qBAAuB,YAE7B,MAAMC,iBAAmB,MAAOC,MAAOC,cACrC,MAAMC,cAAgB,CAACC,QAAU,CAAC,CAAC,GACjCH,MAAMI,QAAQ,CAAC,CAAEC,KAAM,iBAAkBF,OAAQ,GAEnD,GAAI,CACF,GAAI,CAACF,WAAY,CACfK,QAAQC,GAAG,CACT,CAAC,gDAAgD,EAAEN,WAAW,CAAC,CAAC,EAElEC,gBACA,MACF,CAEA,MAAMM,QAAU,CACdC,QAAS,CACPC,OAAQ,kBACV,EACAC,MAAO,UACT,EAEA,GACkB,MADqB,CACrCH,QAAQI,WAAW,CAAG,SACxB,CAEA,MAAMC,IAAM,MAAMC,MAAMb,WAAYO,SACpC,MAAMO,aAAelB,eAAegB,IAAIJ,OAAO,CAACO,GAAG,CAAC,iBAEpD,GAAI,CAACD,aAAc,CACjB,MAAM,IAAIE,MAAM,uCAClB,CAEA,MAAMd,QAAU,MAAMU,IAAIK,IAAI,GAE9B,GAAIf,QAAQgB,KAAK,GAAKrB,qBAAsB,CAC1CI,eACF,KAAO,CACLA,cAAcC,QAChB,CACF,CAAE,MAAOiB,EAAG,CACVlB,gBACAI,QAAQe,IAAI,CAAC,6CAA8CD,EAC7D,CACF,EAEA,MAAME,aAAe,CAAEC,KAAM,IAAK,EAElC,MAAMC,YAAc,UAEpB,MAAMC,mBAAqB,CACzB,CAACD,YAAY,CAAE,CAACE,MAAQJ,YAAY,CAAEK,UACpC,OAAQA,OAAOtB,IAAI,EACjB,IAAK,iBACH,MAAO,CAAE,GAAGqB,KAAK,CAAEH,KAAMI,OAAOxB,OAAO,AAAC,CAC1C,SACE,OAAOuB,KACX,CACF,CACF,EAEA,MAAME,kBAAoB,AAAC5B,OAAUA,MAAM6B,QAAQ,EAAE,CAACL,YAAY,EAAED,IAEpE,QAASxB,gBAAgB,CAAE0B,kBAAkB,CAAEG,iBAAiB,CAAG"}
1
+ {"version":3,"sources":["../../src/core/remote-session-data.js"],"sourcesContent":["/* global __ENABLE_FETCH_WITH_CREDENTIALS__ */\n\n// Fetches current users session data\n// Assumes an authenticated session, so will only work when used on ably.com/ably.io\n\nimport { isJsonResponse } from \"./remote-data-util\";\n\nconst NOT_FOUND_ERROR_CODE = \"not-found\";\n\nconst fetchSessionData = async (store, sessionUrl) => {\n const sessionLoaded = (payload = {}) =>\n store.dispatch({ type: \"session/loaded\", payload });\n\n try {\n if (!sessionUrl) {\n console.log(\n `Skipping fetching session, invalid sessionUrl: \"${sessionUrl}\"`,\n );\n sessionLoaded();\n return;\n }\n\n const options = {\n headers: {\n accept: \"application/json\",\n },\n cache: \"no-cache\",\n };\n\n if (__ENABLE_FETCH_WITH_CREDENTIALS__) {\n options.credentials = \"include\";\n }\n\n const res = await fetch(sessionUrl, options);\n const jsonResponse = isJsonResponse(res.headers.get(\"content-type\"));\n\n if (!jsonResponse) {\n throw new Error(\"Session endpoint is not serving json\");\n }\n\n const payload = await res.json();\n\n if (payload.error === NOT_FOUND_ERROR_CODE) {\n sessionLoaded();\n } else {\n sessionLoaded(payload);\n }\n } catch (e) {\n sessionLoaded();\n console.warn(\"Could not fetch session data due to error:\", e);\n }\n};\n\nconst initialState = { data: null };\n\nconst REDUCER_KEY = \"session\";\n\nconst reducerSessionData = {\n [REDUCER_KEY]: (state = initialState, action) => {\n switch (action.type) {\n case \"session/loaded\":\n return { ...state, data: action.payload };\n default:\n return state;\n }\n },\n};\n\nconst selectSessionData = (store) => store.getState()[REDUCER_KEY]?.data;\n\nexport { fetchSessionData, reducerSessionData, selectSessionData };\n"],"names":["isJsonResponse","NOT_FOUND_ERROR_CODE","fetchSessionData","store","sessionUrl","sessionLoaded","payload","dispatch","type","console","log","options","headers","accept","cache","credentials","res","fetch","jsonResponse","get","Error","json","error","e","warn","initialState","data","REDUCER_KEY","reducerSessionData","state","action","selectSessionData","getState"],"mappings":"AAKA,OAASA,cAAc,KAAQ,oBAAqB,CAEpD,MAAMC,qBAAuB,YAE7B,MAAMC,iBAAmB,MAAOC,MAAOC,cACrC,MAAMC,cAAgB,CAACC,QAAU,CAAC,CAAC,GACjCH,MAAMI,QAAQ,CAAC,CAAEC,KAAM,iBAAkBF,OAAQ,GAEnD,GAAI,CACF,GAAI,CAACF,WAAY,CACfK,QAAQC,GAAG,CACT,CAAC,gDAAgD,EAAEN,WAAW,CAAC,CAAC,EAElEC,gBACA,MACF,CAEA,MAAMM,QAAU,CACdC,QAAS,CACPC,OAAQ,kBACV,EACAC,MAAO,UACT,EAEA,SAAuC,CACrCH,QAAQI,WAAW,CAAG,SACxB,CAEA,MAAMC,IAAM,MAAMC,MAAMb,WAAYO,SACpC,MAAMO,aAAelB,eAAegB,IAAIJ,OAAO,CAACO,GAAG,CAAC,iBAEpD,GAAI,CAACD,aAAc,CACjB,MAAM,IAAIE,MAAM,uCAClB,CAEA,MAAMd,QAAU,MAAMU,IAAIK,IAAI,GAE9B,GAAIf,QAAQgB,KAAK,GAAKrB,qBAAsB,CAC1CI,eACF,KAAO,CACLA,cAAcC,QAChB,CACF,CAAE,MAAOiB,EAAG,CACVlB,gBACAI,QAAQe,IAAI,CAAC,6CAA8CD,EAC7D,CACF,EAEA,MAAME,aAAe,CAAEC,KAAM,IAAK,EAElC,MAAMC,YAAc,UAEpB,MAAMC,mBAAqB,CACzB,CAACD,YAAY,CAAE,CAACE,MAAQJ,YAAY,CAAEK,UACpC,OAAQA,OAAOtB,IAAI,EACjB,IAAK,iBACH,MAAO,CAAE,GAAGqB,KAAK,CAAEH,KAAMI,OAAOxB,OAAO,AAAC,CAC1C,SACE,OAAOuB,KACX,CACF,CACF,EAEA,MAAME,kBAAoB,AAAC5B,OAAUA,MAAM6B,QAAQ,EAAE,CAACL,YAAY,EAAED,IAEpE,QAASxB,gBAAgB,CAAE0B,kBAAkB,CAAEG,iBAAiB,CAAG"}
package/index.d.ts CHANGED
@@ -10,7 +10,6 @@ import { ColorThemeSet } from ".@ably/ui/core/styles/colors/types";
10
10
  export type AccordionData = {
11
11
  /**
12
12
  * The name of the accordion item.
13
- * Can be a string or React element for custom content.
14
13
  */
15
14
  name: string;
16
15
  /**
@@ -785,12 +784,18 @@ export const HeaderLinks: React.FC<Pick<HeaderProps, "sessionState" | "headerLin
785
784
  //# sourceMappingURL=HeaderLinks.d.ts.map
786
785
  }
787
786
 
788
- declare module '@ably/ui/core/Header' {
789
- import React, { ReactNode } from "react";
787
+ declare module '@ably/ui/core/Header/types' {
790
788
  export type ThemedScrollpoint = {
791
789
  id: string;
792
790
  className: string;
793
791
  };
792
+ //# sourceMappingURL=types.d.ts.map
793
+ }
794
+
795
+ declare module '@ably/ui/core/Header' {
796
+ import React, { ReactNode } from "react";
797
+ import { ThemedScrollpoint } from "@ably/ui/core/Header/types";
798
+ export type { ThemedScrollpoint };
794
799
  /**
795
800
  * Represents the state of the user session in the header.
796
801
  */
@@ -2491,17 +2496,6 @@ export default ForwardRef;
2491
2496
  //# sourceMappingURL=icon-gui-spinner-light.d.ts.map
2492
2497
  }
2493
2498
 
2494
- declare module '@ably/ui/core/Icon/components/icon-gui-square-3-stack-3d' {
2495
- import * as React from "react";
2496
- interface SVGRProps {
2497
- title?: string;
2498
- titleId?: string;
2499
- }
2500
- const ForwardRef: React.ForwardRefExoticComponent<Omit<React.SVGProps<SVGSVGElement> & SVGRProps, "ref"> & React.RefAttributes<SVGSVGElement>>;
2501
- export default ForwardRef;
2502
- //# sourceMappingURL=icon-gui-square-3-stack-3d.d.ts.map
2503
- }
2504
-
2505
2499
  declare module '@ably/ui/core/Icon/components/icon-product-ai-transport-mono' {
2506
2500
  import * as React from "react";
2507
2501
  interface SVGRProps {
@@ -6243,6 +6237,21 @@ export const queryIdAll: (val: string, root?: ParentNode) => NodeListOf<Element>
6243
6237
  //# sourceMappingURL=dom-query.d.ts.map
6244
6238
  }
6245
6239
 
6240
+ declare module '@ably/ui/core/hooks/use-content-height' {
6241
+ import { RefObject } from "react";
6242
+ /**
6243
+ * Custom hook that tracks the content height of an element using ResizeObserver.
6244
+ * This eliminates forced reflows by using the browser's native resize observation API
6245
+ * instead of synchronous clientHeight/getBoundingClientRect queries.
6246
+ *
6247
+ * @param ref - React ref to the element to observe
6248
+ * @param initialHeight - Initial height value (default: 0)
6249
+ * @returns Current content height in pixels
6250
+ */
6251
+ export function useContentHeight(ref: RefObject<HTMLElement>, initialHeight?: number): number;
6252
+ //# sourceMappingURL=use-content-height.d.ts.map
6253
+ }
6254
+
6246
6255
  declare module '@ably/ui/core/hooks/use-rails-ujs-hooks' {
6247
6256
  import { RefObject } from "react";
6248
6257
  const useRailsUjsLinks: (containerRef: RefObject<HTMLElement>) => void;
@@ -6250,6 +6259,20 @@ export default useRailsUjsLinks;
6250
6259
  //# sourceMappingURL=use-rails-ujs-hooks.d.ts.map
6251
6260
  }
6252
6261
 
6262
+ declare module '@ably/ui/core/hooks/use-themed-scrollpoints' {
6263
+ import { ThemedScrollpoint } from ".@ably/ui/core/Header/types";
6264
+ export function useThemedScrollpoints(scrollpoints: ThemedScrollpoint[]): string;
6265
+ //# sourceMappingURL=use-themed-scrollpoints.d.ts.map
6266
+ }
6267
+
6268
+ declare module '@ably/ui/core/hooks/use-themed-scrollpoints.test' {
6269
+ /**
6270
+ * @vitest-environment jsdom
6271
+ */
6272
+ export {};
6273
+ //# sourceMappingURL=use-themed-scrollpoints.test.d.ts.map
6274
+ }
6275
+
6253
6276
  declare module '@ably/ui/core/insights/command-queue' {
6254
6277
  import { AnalyticsService, InsightsConfig, InsightsIdentity, TrackPageViewOptions } from "@ably/ui/core/types";
6255
6278
  export class InsightsCommandQueue implements AnalyticsService {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ably/ui",
3
- "version": "17.12.0-dev.7bcf1327",
3
+ "version": "17.13.0-dev.2764bbe8",
4
4
  "description": "Home of the Ably design system library ([design.ably.com](https://design.ably.com)). It provides a showcase, development/test environment and a publishing pipeline for different distributables.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -31,6 +31,7 @@
31
31
  "@swc/cli": "^0.7.8",
32
32
  "@swc/core": "^1.13.5",
33
33
  "@tailwindcss/container-queries": "^0.1.1",
34
+ "@testing-library/react": "^16.3.0",
34
35
  "@types/js-cookie": "^3.0.6",
35
36
  "@types/node": "^20",
36
37
  "@types/react": "^18.3.1",
@@ -56,7 +57,7 @@
56
57
  "msw": "2.12.0",
57
58
  "msw-storybook-addon": "^2.0.5",
58
59
  "playwright": "^1.49.1",
59
- "posthog-js": "^1.321.0",
60
+ "posthog-js": "^1.328.0",
60
61
  "prettier": "^3.8.0",
61
62
  "storybook": "^10.1.10",
62
63
  "svg-sprite": "^2.0.4",
@@ -81,7 +82,7 @@
81
82
  "embla-carousel": "^8.6.0",
82
83
  "embla-carousel-autoplay": "^8.6.0",
83
84
  "embla-carousel-react": "^8.6.0",
84
- "es-toolkit": "^1.43.0",
85
+ "es-toolkit": "^1.44.0",
85
86
  "highlight.js": "^11.11.1",
86
87
  "highlightjs-curl": "^1.3.0",
87
88
  "js-cookie": "^3.0.5",
@@ -1,2 +0,0 @@
1
- import*as React from"react";import{forwardRef}from"react";const IconGuiSquare3Stack3d=({title,titleId,...props},ref)=>React.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",width:24,height:24,fill:"currentColor",viewBox:"0 0 24 24",ref:ref,"aria-labelledby":titleId,...props},title?React.createElement("title",{id:titleId},title):null,React.createElement("path",{stroke:"#03020D",strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:1.5,d:"M6.429 9.75 2.25 12l4.179 2.25m0-4.5 5.571 3 5.571-3m-11.142 0L2.25 7.5 12 2.25l9.75 5.25-4.179 2.25m0 0L21.75 12l-4.179 2.25m0 0 4.179 2.25L12 21.75 2.25 16.5l4.179-2.25m11.142 0-5.571 3-5.571-3"}));const ForwardRef=forwardRef(IconGuiSquare3Stack3d);export default ForwardRef;
2
- //# sourceMappingURL=icon-gui-square-3-stack-3d.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../src/core/Icon/components/icon-gui-square-3-stack-3d.tsx"],"sourcesContent":["import * as React from \"react\";\nimport type { SVGProps } from \"react\";\nimport { Ref, forwardRef } from \"react\";\ninterface SVGRProps {\n title?: string;\n titleId?: string;\n}\nconst IconGuiSquare3Stack3d = ({\n title,\n titleId,\n ...props\n}: SVGProps<SVGSVGElement> & SVGRProps, ref: Ref<SVGSVGElement>) => <svg xmlns=\"http://www.w3.org/2000/svg\" width={24} height={24} fill=\"currentColor\" viewBox=\"0 0 24 24\" ref={ref} aria-labelledby={titleId} {...props}>{title ? <title id={titleId}>{title}</title> : null}<path stroke=\"#03020D\" strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={1.5} d=\"M6.429 9.75 2.25 12l4.179 2.25m0-4.5 5.571 3 5.571-3m-11.142 0L2.25 7.5 12 2.25l9.75 5.25-4.179 2.25m0 0L21.75 12l-4.179 2.25m0 0 4.179 2.25L12 21.75 2.25 16.5l4.179-2.25m11.142 0-5.571 3-5.571-3\" /></svg>;\nconst ForwardRef = forwardRef(IconGuiSquare3Stack3d);\nexport default ForwardRef;"],"names":["React","forwardRef","IconGuiSquare3Stack3d","title","titleId","props","ref","svg","xmlns","width","height","fill","viewBox","aria-labelledby","id","path","stroke","strokeLinecap","strokeLinejoin","strokeWidth","d","ForwardRef"],"mappings":"AAAA,UAAYA,UAAW,OAAQ,AAE/B,QAAcC,UAAU,KAAQ,OAAQ,CAKxC,MAAMC,sBAAwB,CAAC,CAC7BC,KAAK,CACLC,OAAO,CACP,GAAGC,MACiC,CAAEC,MAA4B,oBAACC,OAAIC,MAAM,6BAA6BC,MAAO,GAAIC,OAAQ,GAAIC,KAAK,eAAeC,QAAQ,YAAYN,IAAKA,IAAKO,kBAAiBT,QAAU,GAAGC,KAAK,EAAGF,MAAQ,oBAACA,SAAMW,GAAIV,SAAUD,OAAiB,KAAK,oBAACY,QAAKC,OAAO,UAAUC,cAAc,QAAQC,eAAe,QAAQC,YAAa,IAAKC,EAAE,yMACtW,MAAMC,WAAapB,WAAWC,sBAC9B,gBAAemB,UAAW"}