@khanacademy/wonder-blocks-tabs 0.5.35 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @khanacademy/wonder-blocks-tabs
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f7eef63: Breadcrumbs' default `aria-label` and the TabsDropdown and NavigationTabsDropdown default opener label now read translated strings from `WonderBlocksConfigProvider`. The `aria-label` and `labels.defaultOpenerLabel` props override the built-in translations.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [f7eef63]
|
|
12
|
+
- @khanacademy/wonder-blocks-config@0.3.0
|
|
13
|
+
|
|
14
|
+
## 0.5.36
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- Updated dependencies [4421914]
|
|
19
|
+
- @khanacademy/wonder-blocks-dropdown@10.13.1
|
|
20
|
+
|
|
3
21
|
## 0.5.35
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
|
@@ -76,12 +76,12 @@ export type NavigationTabsDropdownProps = {
|
|
|
76
76
|
*/
|
|
77
77
|
"aria-labelledby"?: string;
|
|
78
78
|
/**
|
|
79
|
-
*
|
|
79
|
+
* Custom labels for the dropdown. These override the default labels
|
|
80
|
+
* provided by `WonderBlocksConfigProvider`.
|
|
80
81
|
*/
|
|
81
82
|
labels?: {
|
|
82
83
|
/**
|
|
83
|
-
* The label used for the opener when there is no selected tab.
|
|
84
|
-
* to an untranslated "Tabs" string.
|
|
84
|
+
* The label used for the opener when there is no selected tab.
|
|
85
85
|
*/
|
|
86
86
|
defaultOpenerLabel?: string;
|
|
87
87
|
};
|
|
@@ -63,7 +63,8 @@ export type TabsDropdownProps = AriaLabelOrAriaLabelledby & {
|
|
|
63
63
|
*/
|
|
64
64
|
onTabSelected: (id: string) => unknown;
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
66
|
+
* Custom labels for the dropdown. These override the default labels
|
|
67
|
+
* provided by `WonderBlocksConfigProvider`.
|
|
67
68
|
*/
|
|
68
69
|
labels?: {
|
|
69
70
|
defaultOpenerLabel?: string;
|
package/dist/es/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import Button from '@khanacademy/wonder-blocks-button';
|
|
|
10
10
|
import caretDown from '@phosphor-icons/core/bold/caret-down-bold.svg';
|
|
11
11
|
import checkCircleIcon from '@phosphor-icons/core/fill/check-circle-fill.svg';
|
|
12
12
|
import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
|
|
13
|
+
import { useWonderBlocksI18n } from '@khanacademy/wonder-blocks-config';
|
|
13
14
|
import Link from '@khanacademy/wonder-blocks-link';
|
|
14
15
|
|
|
15
16
|
const useTabIndicator=props=>{const{animated,tabsContainerRef,isTabActive}=props;const indicatorIsReady=React.useRef(false);const[underlineStyle,setUnderlineStyle]=React.useState({left:0,width:0});const updateUnderlineStyle=React.useCallback(()=>{if(!tabsContainerRef.current){return}const activeTab=Array.from(tabsContainerRef.current.children).find(isTabActive);if(activeTab){const tabRect=activeTab.getBoundingClientRect();const parentRect=tabsContainerRef.current.getBoundingClientRect();const zoomFactor=parentRect.width/tabsContainerRef.current.offsetWidth;const left=(tabRect.left-parentRect.left)/zoomFactor;const width=tabRect.width/zoomFactor;setUnderlineStyle({left,width});}},[setUnderlineStyle,tabsContainerRef,isTabActive]);useOnMountEffect(()=>{if(!tabsContainerRef.current||!window?.ResizeObserver||!window?.MutationObserver){return}const resizeObserver=new window.ResizeObserver(([entry])=>{if(entry){updateUnderlineStyle();if(!indicatorIsReady.current){indicatorIsReady.current=true;}}});resizeObserver.observe(tabsContainerRef.current);const mutationObserver=new window.MutationObserver(([entry])=>{if(entry){updateUnderlineStyle();}});mutationObserver.observe(tabsContainerRef.current,{attributes:true,childList:true,subtree:true});return ()=>{resizeObserver.disconnect();mutationObserver.disconnect();}});const positioningStyle={transform:`translateX(${underlineStyle.left}px)`,width:`${underlineStyle.width}px`};const indicatorProps={style:{...styles$a.currentUnderline,...positioningStyle,...animated?styles$a.underlineTransition:{},...!indicatorIsReady.current?{display:"none"}:{}},role:"presentation"};return {indicatorProps}};const styles$a={currentUnderline:{position:"absolute",bottom:0,left:0,height:border.width.thick,backgroundColor:semanticColor.core.background.instructive.default},underlineTransition:{transition:"transform 0.3s ease, width 0.3s ease"}};
|
|
@@ -30,13 +31,13 @@ const StyledDiv$1=addStyle("div");const Tablist=React.forwardRef(function Tablis
|
|
|
30
31
|
|
|
31
32
|
function getTabId(tabId){return `${tabId}-tab`}function getTabPanelId(tabId){return `${tabId}-panel`}const StyledDiv=addStyle("div");const Tabs=React.forwardRef(function Tabs(props,ref){const{tabs,selectedTabId,onTabSelected,"aria-label":ariaLabel,"aria-labelledby":ariaLabelledby,activationMode="manual",id,testId,animated=false,styles:stylesProp,mountAllPanels=false,scrollableElementRef}=props;const focusedTabId=React.useRef(selectedTabId);const tabRefs=React.useRef({});const generatedUniqueId=React.useId();const uniqueId=id??generatedUniqueId;const tablistId=`${uniqueId}-tablist`;const visitedTabsRef=React.useRef(new Set);visitedTabsRef.current.add(selectedTabId);const tablistRef=React.useRef(null);const isTabActive=React.useCallback(tabElement=>{return tabElement.ariaSelected==="true"},[]);const{indicatorProps}=useTabIndicator({animated,tabsContainerRef:tablistRef,isTabActive});React.useEffect(()=>{focusedTabId.current=selectedTabId;},[selectedTabId]);const selectTab=React.useCallback(tabId=>{if(tabId!==selectedTabId){onTabSelected(tabId);}},[onTabSelected,selectedTabId]);const handleKeyInteraction=React.useCallback(tabId=>{const tabElement=tabRefs.current[tabId];tabElement?.focus();switch(activationMode){case"manual":{focusedTabId.current=tabId;break}case"automatic":{selectTab(tabId);break}}},[activationMode,selectTab,focusedTabId]);const handleKeyDown=React.useCallback(event=>{const currentIndex=tabs.findIndex(tab=>tab.id===focusedTabId.current);const element=event.currentTarget;const isRtl=!!element.closest("[dir=rtl]");switch(event.key){case isRtl&&keys.right:case!isRtl&&keys.left:{event.preventDefault();const prevIndex=(currentIndex-1+tabs.length)%tabs.length;handleKeyInteraction(tabs[prevIndex].id);break}case isRtl&&keys.left:case!isRtl&&keys.right:{event.preventDefault();const nextIndex=(currentIndex+1)%tabs.length;handleKeyInteraction(tabs[nextIndex].id);break}case keys.home:{event.preventDefault();handleKeyInteraction(tabs[0].id);break}case keys.end:{event.preventDefault();handleKeyInteraction(tabs[tabs.length-1].id);break}case keys.enter:case keys.space:{selectTab(focusedTabId.current);break}}},[handleKeyInteraction,selectTab,tabs,focusedTabId]);const handleTablistBlur=React.useCallback(()=>{focusedTabId.current=selectedTabId;},[selectedTabId]);if(tabs.length===0){return jsx(React.Fragment,{})}return jsxs(StyledDiv,{ref:ref,id:uniqueId,"data-testid":testId,style:[styles$4.tabs,stylesProp?.root],children:[jsxs(StyledDiv,{ref:scrollableElementRef,style:styles$4.tablistWrapper,children:[jsx(Tablist,{"aria-label":ariaLabel,"aria-labelledby":ariaLabelledby,onBlur:handleTablistBlur,id:tablistId,testId:testId&&`${testId}-tablist`,ref:tablistRef,style:stylesProp?.tablist,children:tabs.map(tab=>{const{id,label,panel:_,testId:tabTestId,icon,...otherProps}=tab;const tabProps={...otherProps,key:id,id:getTabId(id),testId:tabTestId&&getTabId(tabTestId),selected:id===selectedTabId,icon,"aria-controls":getTabPanelId(id),onClick:()=>{onTabSelected(id);},onKeyDown:handleKeyDown,ref:element=>{tabRefs.current[tab.id]=element;},style:stylesProp?.tab};if(typeof label==="function"){return label(tabProps)}return jsx(Tab,{...tabProps,children:label})})}),jsx("div",{...indicatorProps})]}),tabs.map(tab=>{return jsx(TabPanel,{id:getTabPanelId(tab.id),"aria-labelledby":getTabId(tab.id),active:selectedTabId===tab.id,testId:tab.testId&&getTabPanelId(tab.testId),style:stylesProp?.tabPanel,children:(mountAllPanels||visitedTabsRef.current.has(tab.id))&&tab.panel},tab.id)})]})});const styles$4=StyleSheet.create({tabs:{display:"inline-flex",flexDirection:"column",alignItems:"stretch",position:"relative"},tablistWrapper:{position:"relative",overflowX:"auto",flexShrink:0}});
|
|
32
33
|
|
|
33
|
-
const
|
|
34
|
+
const TabsDropdown=React.forwardRef((props,ref)=>{const{tabs,selectedTabId,onTabSelected,labels:labelsProp,opened,id:idProp,testId,"aria-label":ariaLabel,"aria-labelledby":ariaLabelledby,styles:stylesProp}=props;const{strings}=useWonderBlocksI18n();const labels=React.useMemo(()=>{return {defaultOpenerLabel:labelsProp?.defaultOpenerLabel||strings.tabs}},[labelsProp,strings.tabs]);const generatedUniqueId=React.useId();const uniqueId=idProp??generatedUniqueId;const openerId=`${uniqueId}-opener`;const panelId=`${uniqueId}-panel`;const selectedTabItem=React.useMemo(()=>{return tabs.find(tab=>tab.id===selectedTabId)},[tabs,selectedTabId]);const processedTabs=React.useMemo(()=>{return tabs.map(tab=>({...tab,leftAccessory:tab.icon?React.cloneElement(tab.icon,{size:tab.icon.props.size??"medium"}):undefined,handleClick:()=>{onTabSelected(tab.id);}}))},[tabs,onTabSelected]);if(tabs.length===0){return jsx(React.Fragment,{})}const menuText=selectedTabItem?.label||labels.defaultOpenerLabel;return jsxs(View,{ref:ref,id:uniqueId,testId:testId,style:stylesProp?.root,role:"region","aria-label":ariaLabel,"aria-labelledby":ariaLabelledby,children:[jsx(ActionMenu,{opened:opened,id:openerId,menuText:menuText,opener:()=>jsx(Button,{testId:testId?`${testId}-opener`:undefined,kind:"tertiary",endIcon:caretDown,style:[styles$3.opener,stylesProp?.opener],labelStyle:styles$3.labelStyle,"aria-label":selectedTabItem?.["aria-label"],startIcon:selectedTabItem?.icon,children:menuText}),style:[styles$3.actionMenu,stylesProp?.actionMenu],children:processedTabs.map(tab=>{return jsx(ActionItem,{label:tab.label,"aria-label":tab["aria-label"],onClick:tab.handleClick,active:tab.id===selectedTabId,testId:tab.testId,rightAccessory:tab.id===selectedTabId?jsx(PhosphorIcon,{icon:checkCircleIcon,size:"medium","aria-hidden":"true"}):undefined,leftAccessory:tab.leftAccessory},tab.id)})}),jsx(View,{id:panelId,role:"group","aria-labelledby":openerId,testId:testId?`${testId}-panel`:undefined,style:stylesProp?.tabPanel,children:selectedTabItem?.panel})]})});const styles$3=StyleSheet.create({actionMenu:{width:"100%",alignItems:"flex-start",borderBlockEnd:`${border.width.thin} solid ${semanticColor.core.border.neutral.subtle}`},opener:{position:"relative",height:"unset",paddingBlockStart:sizing.size_120,paddingBlockEnd:sizing.size_140,paddingInline:sizing.size_180,width:"100%",justifyContent:"space-between",gap:sizing.size_020},labelStyle:{flexGrow:1,maxInlineSize:"100%",textAlign:"start"}});
|
|
34
35
|
|
|
35
36
|
function useResponsiveLayout(options){const{tabs,elementWithOverflowRef,containerRef,onLayoutChange}=options;const[showDropdown,setShowDropdown]=React.useState(false);const tabsWidthRef=React.useRef(null);const containerWidthRef=React.useRef(null);const tabsSignature=React.useMemo(()=>tabs.map(t=>`${t.id}:${t.label}-${t.icon?"with-icon":"without-icon"}`).join("|"),[tabs]);const checkOverflow=React.useCallback(()=>{const container=containerRef.current;if(!container){return}if(!showDropdown&&elementWithOverflowRef.current){const scrollableWrapper=elementWithOverflowRef.current;if(scrollableWrapper){const hasOverflow=scrollableWrapper.scrollWidth>scrollableWrapper.clientWidth;if(hasOverflow){tabsWidthRef.current=scrollableWrapper.scrollWidth+(container.clientWidth-scrollableWrapper.clientWidth);setShowDropdown(true);}}}else if(showDropdown&&tabsWidthRef.current){const containerWidth=container.clientWidth;if(containerWidth>=tabsWidthRef.current){setShowDropdown(false);}}},[showDropdown,elementWithOverflowRef,containerRef]);React.useEffect(()=>{if(showDropdown){tabsWidthRef.current=null;setShowDropdown(false);}},[tabsSignature]);React.useEffect(()=>{const container=containerRef.current;if(!container||!window.ResizeObserver){return}const resizeObserver=new ResizeObserver(()=>{if(containerWidthRef.current!==container.clientWidth){containerWidthRef.current=container.clientWidth;checkOverflow();}});resizeObserver.observe(container);return ()=>{resizeObserver.disconnect();}},[checkOverflow,containerRef]);React.useEffect(()=>{const element=elementWithOverflowRef.current;if(!element){return}checkOverflow();const mutationObserver=new MutationObserver(()=>{checkOverflow();});mutationObserver.observe(element,{childList:true,subtree:true,characterData:true});return ()=>{mutationObserver.disconnect();}},[checkOverflow,elementWithOverflowRef]);React.useEffect(()=>{onLayoutChange?.(showDropdown?"dropdown":"tabs");},[showDropdown,onLayoutChange]);return {showDropdown}}
|
|
36
37
|
|
|
37
38
|
const ResponsiveTabs=props=>{const{tabs,selectedTabId,onTabSelected,onLayoutChange,id,testId,tabsProps,dropdownProps,styles:stylesProp,...ariaProps}=props;const tabsRef=React.useRef(null);const scrollableTabsRef=React.useRef(null);const containerRef=React.useRef(null);const{showDropdown}=useResponsiveLayout({tabs,elementWithOverflowRef:scrollableTabsRef,containerRef,onLayoutChange});return jsx(View,{ref:containerRef,style:[styles$2.container,stylesProp?.root],id:id,testId:testId,children:showDropdown?createElement(TabsDropdown,{...dropdownProps,...ariaProps,key:"dropdown",tabs:tabs,selectedTabId:selectedTabId,onTabSelected:onTabSelected,styles:{...dropdownProps?.styles,root:[styles$2.fadeIn,dropdownProps?.styles?.root]}}):createElement(Tabs,{...tabsProps,...ariaProps,key:"tabs",ref:tabsRef,scrollableElementRef:scrollableTabsRef,tabs:tabs,selectedTabId:selectedTabId,onTabSelected:onTabSelected,styles:{...tabsProps?.styles,root:[styles$2.fadeIn,tabsProps?.styles?.root]}})})};const fadeInKeyframes$1={from:{opacity:0},to:{opacity:1}};const styles$2=StyleSheet.create({fadeIn:{animationName:fadeInKeyframes$1,animationDuration:"150ms",animationTimingFunction:"ease-in-out"},container:{width:"100%",minBlockSize:"auto"}});
|
|
38
39
|
|
|
39
|
-
const
|
|
40
|
+
const NavigationTabsDropdown=React.forwardRef((props,ref)=>{const{tabs,selectedTabId,onTabSelected,id:idProp,testId,labels:labelsProp,opened,"aria-label":ariaLabel,"aria-labelledby":ariaLabelledby,tag="nav",styles:stylesProp,showDivider=false}=props;const StyledTag=React.useMemo(()=>addStyle(tag),[tag]);const{strings}=useWonderBlocksI18n();const labels=React.useMemo(()=>{return {defaultOpenerLabel:labelsProp?.defaultOpenerLabel||strings.tabs}},[labelsProp,strings.tabs]);const selectedTabItem=React.useMemo(()=>{return tabs.find(tab=>tab.id===selectedTabId)},[tabs,selectedTabId]);const processedTabs=React.useMemo(()=>{return tabs.map(tab=>({...tab,leftAccessory:tab.icon?React.cloneElement(tab.icon,{size:tab.icon.props.size??"medium"}):undefined,handleClick:onTabSelected?()=>onTabSelected(tab.id):undefined}))},[tabs,onTabSelected]);const generatedUniqueId=React.useId();const uniqueId=idProp??generatedUniqueId;const openerId=`${uniqueId}-opener`;if(tabs.length===0){return jsx(React.Fragment,{})}const menuText=selectedTabItem?.label||labels.defaultOpenerLabel;return jsx(StyledTag,{ref:ref,id:uniqueId,"data-testid":testId,style:[showDivider&&styles$1.divider,stylesProp?.root],"aria-label":ariaLabel,"aria-labelledby":ariaLabelledby,children:jsx(ActionMenu,{opened:opened,id:openerId,menuText:menuText,opener:()=>jsx(Button,{testId:testId?`${testId}-opener`:undefined,kind:"tertiary",endIcon:caretDown,style:[styles$1.opener,stylesProp?.opener],labelStyle:styles$1.labelStyle,"aria-label":selectedTabItem?.["aria-label"],startIcon:selectedTabItem?.icon,children:menuText}),style:[styles$1.actionMenu,stylesProp?.actionMenu],children:processedTabs.map(tab=>{return jsx(ActionItem,{label:tab.label,href:tab.href,"aria-label":tab["aria-label"],active:tab.id===selectedTabId,testId:tab.testId,leftAccessory:tab.leftAccessory,onClick:tab.handleClick,rightAccessory:tab.id===selectedTabId?jsx(PhosphorIcon,{icon:checkCircleIcon,size:"medium","aria-hidden":"true"}):undefined},tab.id)})})})});const styles$1=StyleSheet.create({actionMenu:{width:"100%",alignItems:"flex-start"},divider:{borderBlockEnd:`${border.width.thin} solid ${semanticColor.core.border.neutral.subtle}`},opener:{position:"relative",height:"unset",paddingBlockStart:sizing.size_120,paddingBlockEnd:sizing.size_140,paddingInline:sizing.size_180,width:"100%",justifyContent:"space-between",gap:sizing.size_020},labelStyle:{flexGrow:1,maxInlineSize:"100%",textAlign:"start"}});
|
|
40
41
|
|
|
41
42
|
const ResponsiveNavigationTabs=props=>{const{tabs,selectedTabId,onTabSelected,onLayoutChange,id,testId,"aria-label":ariaLabel,"aria-labelledby":ariaLabelledby,tag,tabsProps,dropdownProps,styles:stylesProp,showDivider=false}=props;const navigationTabsRef=React.useRef(null);const containerRef=React.useRef(null);const{showDropdown}=useResponsiveLayout({tabs,elementWithOverflowRef:navigationTabsRef,containerRef,onLayoutChange});const processedTabs=React.useMemo(()=>{return tabs.map(tab=>({...tab,startIcon:tab.icon?React.cloneElement(tab.icon,{size:tab.icon.props.size??"medium"}):undefined,handleClick:onTabSelected?()=>onTabSelected(tab.id):undefined}))},[tabs,onTabSelected]);return jsx(View,{ref:containerRef,style:[styles.container,stylesProp?.root],id:id,testId:testId,children:showDropdown?createElement(NavigationTabsDropdown,{...dropdownProps,key:"dropdown",tabs:tabs,selectedTabId:selectedTabId,onTabSelected:onTabSelected,"aria-label":ariaLabel,"aria-labelledby":ariaLabelledby,tag:tag,styles:{...dropdownProps?.styles,root:[styles.fadeIn,dropdownProps?.styles?.root]},showDivider:showDivider}):createElement(NavigationTabs,{...tabsProps,key:"tabs",ref:navigationTabsRef,"aria-label":ariaLabel,"aria-labelledby":ariaLabelledby,tag:tag,styles:{...tabsProps?.styles,root:[styles.fadeIn,tabsProps?.styles?.root]},showDivider:showDivider},processedTabs.map(tab=>jsx(NavigationTabItem,{current:tab.id===selectedTabId,children:jsx(Link,{href:tab.href,onClick:tab.handleClick,startIcon:tab.startIcon,"aria-label":tab["aria-label"],testId:tab.testId,children:tab.label})},tab.id)))})};const fadeInKeyframes={from:{opacity:0},to:{opacity:1}};const styles=StyleSheet.create({fadeIn:{animationName:fadeInKeyframes,animationDuration:"150ms",animationTimingFunction:"ease-in-out"},container:{width:"100%",minBlockSize:"auto"}});
|
|
42
43
|
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ var Button = require('@khanacademy/wonder-blocks-button');
|
|
|
13
13
|
var caretDown = require('@phosphor-icons/core/bold/caret-down-bold.svg');
|
|
14
14
|
var checkCircleIcon = require('@phosphor-icons/core/fill/check-circle-fill.svg');
|
|
15
15
|
var wonderBlocksIcon = require('@khanacademy/wonder-blocks-icon');
|
|
16
|
+
var wonderBlocksConfig = require('@khanacademy/wonder-blocks-config');
|
|
16
17
|
var Link = require('@khanacademy/wonder-blocks-link');
|
|
17
18
|
|
|
18
19
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
@@ -59,13 +60,13 @@ const StyledDiv$1=wonderBlocksCore.addStyle("div");const Tablist=React__namespac
|
|
|
59
60
|
|
|
60
61
|
function getTabId(tabId){return `${tabId}-tab`}function getTabPanelId(tabId){return `${tabId}-panel`}const StyledDiv=wonderBlocksCore.addStyle("div");const Tabs=React__namespace.forwardRef(function Tabs(props,ref){const{tabs,selectedTabId,onTabSelected,"aria-label":ariaLabel,"aria-labelledby":ariaLabelledby,activationMode="manual",id,testId,animated=false,styles:stylesProp,mountAllPanels=false,scrollableElementRef}=props;const focusedTabId=React__namespace.useRef(selectedTabId);const tabRefs=React__namespace.useRef({});const generatedUniqueId=React__namespace.useId();const uniqueId=id??generatedUniqueId;const tablistId=`${uniqueId}-tablist`;const visitedTabsRef=React__namespace.useRef(new Set);visitedTabsRef.current.add(selectedTabId);const tablistRef=React__namespace.useRef(null);const isTabActive=React__namespace.useCallback(tabElement=>{return tabElement.ariaSelected==="true"},[]);const{indicatorProps}=useTabIndicator({animated,tabsContainerRef:tablistRef,isTabActive});React__namespace.useEffect(()=>{focusedTabId.current=selectedTabId;},[selectedTabId]);const selectTab=React__namespace.useCallback(tabId=>{if(tabId!==selectedTabId){onTabSelected(tabId);}},[onTabSelected,selectedTabId]);const handleKeyInteraction=React__namespace.useCallback(tabId=>{const tabElement=tabRefs.current[tabId];tabElement?.focus();switch(activationMode){case"manual":{focusedTabId.current=tabId;break}case"automatic":{selectTab(tabId);break}}},[activationMode,selectTab,focusedTabId]);const handleKeyDown=React__namespace.useCallback(event=>{const currentIndex=tabs.findIndex(tab=>tab.id===focusedTabId.current);const element=event.currentTarget;const isRtl=!!element.closest("[dir=rtl]");switch(event.key){case isRtl&&wonderBlocksCore.keys.right:case!isRtl&&wonderBlocksCore.keys.left:{event.preventDefault();const prevIndex=(currentIndex-1+tabs.length)%tabs.length;handleKeyInteraction(tabs[prevIndex].id);break}case isRtl&&wonderBlocksCore.keys.left:case!isRtl&&wonderBlocksCore.keys.right:{event.preventDefault();const nextIndex=(currentIndex+1)%tabs.length;handleKeyInteraction(tabs[nextIndex].id);break}case wonderBlocksCore.keys.home:{event.preventDefault();handleKeyInteraction(tabs[0].id);break}case wonderBlocksCore.keys.end:{event.preventDefault();handleKeyInteraction(tabs[tabs.length-1].id);break}case wonderBlocksCore.keys.enter:case wonderBlocksCore.keys.space:{selectTab(focusedTabId.current);break}}},[handleKeyInteraction,selectTab,tabs,focusedTabId]);const handleTablistBlur=React__namespace.useCallback(()=>{focusedTabId.current=selectedTabId;},[selectedTabId]);if(tabs.length===0){return jsxRuntime.jsx(React__namespace.Fragment,{})}return jsxRuntime.jsxs(StyledDiv,{ref:ref,id:uniqueId,"data-testid":testId,style:[styles$4.tabs,stylesProp?.root],children:[jsxRuntime.jsxs(StyledDiv,{ref:scrollableElementRef,style:styles$4.tablistWrapper,children:[jsxRuntime.jsx(Tablist,{"aria-label":ariaLabel,"aria-labelledby":ariaLabelledby,onBlur:handleTablistBlur,id:tablistId,testId:testId&&`${testId}-tablist`,ref:tablistRef,style:stylesProp?.tablist,children:tabs.map(tab=>{const{id,label,panel:_,testId:tabTestId,icon,...otherProps}=tab;const tabProps={...otherProps,key:id,id:getTabId(id),testId:tabTestId&&getTabId(tabTestId),selected:id===selectedTabId,icon,"aria-controls":getTabPanelId(id),onClick:()=>{onTabSelected(id);},onKeyDown:handleKeyDown,ref:element=>{tabRefs.current[tab.id]=element;},style:stylesProp?.tab};if(typeof label==="function"){return label(tabProps)}return jsxRuntime.jsx(Tab,{...tabProps,children:label})})}),jsxRuntime.jsx("div",{...indicatorProps})]}),tabs.map(tab=>{return jsxRuntime.jsx(TabPanel,{id:getTabPanelId(tab.id),"aria-labelledby":getTabId(tab.id),active:selectedTabId===tab.id,testId:tab.testId&&getTabPanelId(tab.testId),style:stylesProp?.tabPanel,children:(mountAllPanels||visitedTabsRef.current.has(tab.id))&&tab.panel},tab.id)})]})});const styles$4=aphrodite.StyleSheet.create({tabs:{display:"inline-flex",flexDirection:"column",alignItems:"stretch",position:"relative"},tablistWrapper:{position:"relative",overflowX:"auto",flexShrink:0}});
|
|
61
62
|
|
|
62
|
-
const
|
|
63
|
+
const TabsDropdown=React__namespace.forwardRef((props,ref)=>{const{tabs,selectedTabId,onTabSelected,labels:labelsProp,opened,id:idProp,testId,"aria-label":ariaLabel,"aria-labelledby":ariaLabelledby,styles:stylesProp}=props;const{strings}=wonderBlocksConfig.useWonderBlocksI18n();const labels=React__namespace.useMemo(()=>{return {defaultOpenerLabel:labelsProp?.defaultOpenerLabel||strings.tabs}},[labelsProp,strings.tabs]);const generatedUniqueId=React__namespace.useId();const uniqueId=idProp??generatedUniqueId;const openerId=`${uniqueId}-opener`;const panelId=`${uniqueId}-panel`;const selectedTabItem=React__namespace.useMemo(()=>{return tabs.find(tab=>tab.id===selectedTabId)},[tabs,selectedTabId]);const processedTabs=React__namespace.useMemo(()=>{return tabs.map(tab=>({...tab,leftAccessory:tab.icon?React__namespace.cloneElement(tab.icon,{size:tab.icon.props.size??"medium"}):undefined,handleClick:()=>{onTabSelected(tab.id);}}))},[tabs,onTabSelected]);if(tabs.length===0){return jsxRuntime.jsx(React__namespace.Fragment,{})}const menuText=selectedTabItem?.label||labels.defaultOpenerLabel;return jsxRuntime.jsxs(wonderBlocksCore.View,{ref:ref,id:uniqueId,testId:testId,style:stylesProp?.root,role:"region","aria-label":ariaLabel,"aria-labelledby":ariaLabelledby,children:[jsxRuntime.jsx(wonderBlocksDropdown.ActionMenu,{opened:opened,id:openerId,menuText:menuText,opener:()=>jsxRuntime.jsx(Button__default["default"],{testId:testId?`${testId}-opener`:undefined,kind:"tertiary",endIcon:caretDown__default["default"],style:[styles$3.opener,stylesProp?.opener],labelStyle:styles$3.labelStyle,"aria-label":selectedTabItem?.["aria-label"],startIcon:selectedTabItem?.icon,children:menuText}),style:[styles$3.actionMenu,stylesProp?.actionMenu],children:processedTabs.map(tab=>{return jsxRuntime.jsx(wonderBlocksDropdown.ActionItem,{label:tab.label,"aria-label":tab["aria-label"],onClick:tab.handleClick,active:tab.id===selectedTabId,testId:tab.testId,rightAccessory:tab.id===selectedTabId?jsxRuntime.jsx(wonderBlocksIcon.PhosphorIcon,{icon:checkCircleIcon__default["default"],size:"medium","aria-hidden":"true"}):undefined,leftAccessory:tab.leftAccessory},tab.id)})}),jsxRuntime.jsx(wonderBlocksCore.View,{id:panelId,role:"group","aria-labelledby":openerId,testId:testId?`${testId}-panel`:undefined,style:stylesProp?.tabPanel,children:selectedTabItem?.panel})]})});const styles$3=aphrodite.StyleSheet.create({actionMenu:{width:"100%",alignItems:"flex-start",borderBlockEnd:`${wonderBlocksTokens.border.width.thin} solid ${wonderBlocksTokens.semanticColor.core.border.neutral.subtle}`},opener:{position:"relative",height:"unset",paddingBlockStart:wonderBlocksTokens.sizing.size_120,paddingBlockEnd:wonderBlocksTokens.sizing.size_140,paddingInline:wonderBlocksTokens.sizing.size_180,width:"100%",justifyContent:"space-between",gap:wonderBlocksTokens.sizing.size_020},labelStyle:{flexGrow:1,maxInlineSize:"100%",textAlign:"start"}});
|
|
63
64
|
|
|
64
65
|
function useResponsiveLayout(options){const{tabs,elementWithOverflowRef,containerRef,onLayoutChange}=options;const[showDropdown,setShowDropdown]=React__namespace.useState(false);const tabsWidthRef=React__namespace.useRef(null);const containerWidthRef=React__namespace.useRef(null);const tabsSignature=React__namespace.useMemo(()=>tabs.map(t=>`${t.id}:${t.label}-${t.icon?"with-icon":"without-icon"}`).join("|"),[tabs]);const checkOverflow=React__namespace.useCallback(()=>{const container=containerRef.current;if(!container){return}if(!showDropdown&&elementWithOverflowRef.current){const scrollableWrapper=elementWithOverflowRef.current;if(scrollableWrapper){const hasOverflow=scrollableWrapper.scrollWidth>scrollableWrapper.clientWidth;if(hasOverflow){tabsWidthRef.current=scrollableWrapper.scrollWidth+(container.clientWidth-scrollableWrapper.clientWidth);setShowDropdown(true);}}}else if(showDropdown&&tabsWidthRef.current){const containerWidth=container.clientWidth;if(containerWidth>=tabsWidthRef.current){setShowDropdown(false);}}},[showDropdown,elementWithOverflowRef,containerRef]);React__namespace.useEffect(()=>{if(showDropdown){tabsWidthRef.current=null;setShowDropdown(false);}},[tabsSignature]);React__namespace.useEffect(()=>{const container=containerRef.current;if(!container||!window.ResizeObserver){return}const resizeObserver=new ResizeObserver(()=>{if(containerWidthRef.current!==container.clientWidth){containerWidthRef.current=container.clientWidth;checkOverflow();}});resizeObserver.observe(container);return ()=>{resizeObserver.disconnect();}},[checkOverflow,containerRef]);React__namespace.useEffect(()=>{const element=elementWithOverflowRef.current;if(!element){return}checkOverflow();const mutationObserver=new MutationObserver(()=>{checkOverflow();});mutationObserver.observe(element,{childList:true,subtree:true,characterData:true});return ()=>{mutationObserver.disconnect();}},[checkOverflow,elementWithOverflowRef]);React__namespace.useEffect(()=>{onLayoutChange?.(showDropdown?"dropdown":"tabs");},[showDropdown,onLayoutChange]);return {showDropdown}}
|
|
65
66
|
|
|
66
67
|
const ResponsiveTabs=props=>{const{tabs,selectedTabId,onTabSelected,onLayoutChange,id,testId,tabsProps,dropdownProps,styles:stylesProp,...ariaProps}=props;const tabsRef=React__namespace.useRef(null);const scrollableTabsRef=React__namespace.useRef(null);const containerRef=React__namespace.useRef(null);const{showDropdown}=useResponsiveLayout({tabs,elementWithOverflowRef:scrollableTabsRef,containerRef,onLayoutChange});return jsxRuntime.jsx(wonderBlocksCore.View,{ref:containerRef,style:[styles$2.container,stylesProp?.root],id:id,testId:testId,children:showDropdown?React.createElement(TabsDropdown,{...dropdownProps,...ariaProps,key:"dropdown",tabs:tabs,selectedTabId:selectedTabId,onTabSelected:onTabSelected,styles:{...dropdownProps?.styles,root:[styles$2.fadeIn,dropdownProps?.styles?.root]}}):React.createElement(Tabs,{...tabsProps,...ariaProps,key:"tabs",ref:tabsRef,scrollableElementRef:scrollableTabsRef,tabs:tabs,selectedTabId:selectedTabId,onTabSelected:onTabSelected,styles:{...tabsProps?.styles,root:[styles$2.fadeIn,tabsProps?.styles?.root]}})})};const fadeInKeyframes$1={from:{opacity:0},to:{opacity:1}};const styles$2=aphrodite.StyleSheet.create({fadeIn:{animationName:fadeInKeyframes$1,animationDuration:"150ms",animationTimingFunction:"ease-in-out"},container:{width:"100%",minBlockSize:"auto"}});
|
|
67
68
|
|
|
68
|
-
const
|
|
69
|
+
const NavigationTabsDropdown=React__namespace.forwardRef((props,ref)=>{const{tabs,selectedTabId,onTabSelected,id:idProp,testId,labels:labelsProp,opened,"aria-label":ariaLabel,"aria-labelledby":ariaLabelledby,tag="nav",styles:stylesProp,showDivider=false}=props;const StyledTag=React__namespace.useMemo(()=>wonderBlocksCore.addStyle(tag),[tag]);const{strings}=wonderBlocksConfig.useWonderBlocksI18n();const labels=React__namespace.useMemo(()=>{return {defaultOpenerLabel:labelsProp?.defaultOpenerLabel||strings.tabs}},[labelsProp,strings.tabs]);const selectedTabItem=React__namespace.useMemo(()=>{return tabs.find(tab=>tab.id===selectedTabId)},[tabs,selectedTabId]);const processedTabs=React__namespace.useMemo(()=>{return tabs.map(tab=>({...tab,leftAccessory:tab.icon?React__namespace.cloneElement(tab.icon,{size:tab.icon.props.size??"medium"}):undefined,handleClick:onTabSelected?()=>onTabSelected(tab.id):undefined}))},[tabs,onTabSelected]);const generatedUniqueId=React__namespace.useId();const uniqueId=idProp??generatedUniqueId;const openerId=`${uniqueId}-opener`;if(tabs.length===0){return jsxRuntime.jsx(React__namespace.Fragment,{})}const menuText=selectedTabItem?.label||labels.defaultOpenerLabel;return jsxRuntime.jsx(StyledTag,{ref:ref,id:uniqueId,"data-testid":testId,style:[showDivider&&styles$1.divider,stylesProp?.root],"aria-label":ariaLabel,"aria-labelledby":ariaLabelledby,children:jsxRuntime.jsx(wonderBlocksDropdown.ActionMenu,{opened:opened,id:openerId,menuText:menuText,opener:()=>jsxRuntime.jsx(Button__default["default"],{testId:testId?`${testId}-opener`:undefined,kind:"tertiary",endIcon:caretDown__default["default"],style:[styles$1.opener,stylesProp?.opener],labelStyle:styles$1.labelStyle,"aria-label":selectedTabItem?.["aria-label"],startIcon:selectedTabItem?.icon,children:menuText}),style:[styles$1.actionMenu,stylesProp?.actionMenu],children:processedTabs.map(tab=>{return jsxRuntime.jsx(wonderBlocksDropdown.ActionItem,{label:tab.label,href:tab.href,"aria-label":tab["aria-label"],active:tab.id===selectedTabId,testId:tab.testId,leftAccessory:tab.leftAccessory,onClick:tab.handleClick,rightAccessory:tab.id===selectedTabId?jsxRuntime.jsx(wonderBlocksIcon.PhosphorIcon,{icon:checkCircleIcon__default["default"],size:"medium","aria-hidden":"true"}):undefined},tab.id)})})})});const styles$1=aphrodite.StyleSheet.create({actionMenu:{width:"100%",alignItems:"flex-start"},divider:{borderBlockEnd:`${wonderBlocksTokens.border.width.thin} solid ${wonderBlocksTokens.semanticColor.core.border.neutral.subtle}`},opener:{position:"relative",height:"unset",paddingBlockStart:wonderBlocksTokens.sizing.size_120,paddingBlockEnd:wonderBlocksTokens.sizing.size_140,paddingInline:wonderBlocksTokens.sizing.size_180,width:"100%",justifyContent:"space-between",gap:wonderBlocksTokens.sizing.size_020},labelStyle:{flexGrow:1,maxInlineSize:"100%",textAlign:"start"}});
|
|
69
70
|
|
|
70
71
|
const ResponsiveNavigationTabs=props=>{const{tabs,selectedTabId,onTabSelected,onLayoutChange,id,testId,"aria-label":ariaLabel,"aria-labelledby":ariaLabelledby,tag,tabsProps,dropdownProps,styles:stylesProp,showDivider=false}=props;const navigationTabsRef=React__namespace.useRef(null);const containerRef=React__namespace.useRef(null);const{showDropdown}=useResponsiveLayout({tabs,elementWithOverflowRef:navigationTabsRef,containerRef,onLayoutChange});const processedTabs=React__namespace.useMemo(()=>{return tabs.map(tab=>({...tab,startIcon:tab.icon?React__namespace.cloneElement(tab.icon,{size:tab.icon.props.size??"medium"}):undefined,handleClick:onTabSelected?()=>onTabSelected(tab.id):undefined}))},[tabs,onTabSelected]);return jsxRuntime.jsx(wonderBlocksCore.View,{ref:containerRef,style:[styles.container,stylesProp?.root],id:id,testId:testId,children:showDropdown?React.createElement(NavigationTabsDropdown,{...dropdownProps,key:"dropdown",tabs:tabs,selectedTabId:selectedTabId,onTabSelected:onTabSelected,"aria-label":ariaLabel,"aria-labelledby":ariaLabelledby,tag:tag,styles:{...dropdownProps?.styles,root:[styles.fadeIn,dropdownProps?.styles?.root]},showDivider:showDivider}):React.createElement(NavigationTabs,{...tabsProps,key:"tabs",ref:navigationTabsRef,"aria-label":ariaLabel,"aria-labelledby":ariaLabelledby,tag:tag,styles:{...tabsProps?.styles,root:[styles.fadeIn,tabsProps?.styles?.root]},showDivider:showDivider},processedTabs.map(tab=>jsxRuntime.jsx(NavigationTabItem,{current:tab.id===selectedTabId,children:jsxRuntime.jsx(Link__default["default"],{href:tab.href,onClick:tab.handleClick,startIcon:tab.startIcon,"aria-label":tab["aria-label"],testId:tab.testId,children:tab.label})},tab.id)))})};const fadeInKeyframes={from:{opacity:0},to:{opacity:1}};const styles=aphrodite.StyleSheet.create({fadeIn:{animationName:fadeInKeyframes,animationDuration:"150ms",animationTimingFunction:"ease-in-out"},container:{width:"100%",minBlockSize:"auto"}});
|
|
71
72
|
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Tabs are used to control what content is shown",
|
|
4
4
|
"author": "Khan Academy",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.6.0",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public"
|
|
9
9
|
},
|
|
@@ -22,11 +22,12 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@phosphor-icons/core": "^2.0.2",
|
|
24
24
|
"@khanacademy/wonder-blocks-button": "11.7.12",
|
|
25
|
-
"@khanacademy/wonder-blocks-
|
|
26
|
-
"@khanacademy/wonder-blocks-dropdown": "10.13.
|
|
27
|
-
"@khanacademy/wonder-blocks-icon": "6.0.2",
|
|
25
|
+
"@khanacademy/wonder-blocks-config": "0.3.0",
|
|
26
|
+
"@khanacademy/wonder-blocks-dropdown": "10.13.1",
|
|
28
27
|
"@khanacademy/wonder-blocks-link": "10.4.1",
|
|
28
|
+
"@khanacademy/wonder-blocks-core": "12.5.0",
|
|
29
29
|
"@khanacademy/wonder-blocks-tokens": "18.0.0",
|
|
30
|
+
"@khanacademy/wonder-blocks-icon": "6.0.2",
|
|
30
31
|
"@khanacademy/wonder-blocks-typography": "5.0.5"
|
|
31
32
|
},
|
|
32
33
|
"peerDependencies": {
|