From c55a1392367e47906858c2961bbf30bc7698fcf7 Mon Sep 17 00:00:00 2001 From: Jialecl Date: Thu, 15 May 2025 13:59:05 +0200 Subject: [PATCH 1/3] TabId prop added to identify each tab --- .../components/tabs/code/TabsCodePage.tsx | 18 ++++- .../tabs/code/examples-new/controlled.ts | 6 +- .../tabs/code/examples-new/icons.ts | 6 +- .../tabs/code/examples-new/uncontrolled.ts | 6 +- packages/lib/src/tabs/Tab.tsx | 24 +++---- packages/lib/src/tabs/Tabs.stories.tsx | 68 +++++++++---------- packages/lib/src/tabs/Tabs.test.tsx | 40 +++++------ packages/lib/src/tabs/Tabs.tsx | 16 ++--- packages/lib/src/tabs/types.ts | 17 +++-- 9 files changed, 106 insertions(+), 95 deletions(-) diff --git a/apps/website/screens/components/tabs/code/TabsCodePage.tsx b/apps/website/screens/components/tabs/code/TabsCodePage.tsx index 1ad41c729..c552d2259 100644 --- a/apps/website/screens/components/tabs/code/TabsCodePage.tsx +++ b/apps/website/screens/components/tabs/code/TabsCodePage.tsx @@ -254,21 +254,21 @@ const sections = [ {" "} name or SVG element as the icon that will be displayed in the tab. When using Material Symbols, replace spaces with underscores. By default they are outlined if you want it to be filled prefix the - symbol name with "filled_". + symbol name with "filled_". The icon or the label, either of which must have a + valid value. - - label string - Tab label text. + Tab label text. The icon or the label, either of which must have a valid value. - @@ -301,6 +301,18 @@ const sections = [ This function will be called when the user hovers this tab. - + + + + tabId + + + + string + + Value used to identify the tab internally. + - + diff --git a/apps/website/screens/components/tabs/code/examples-new/controlled.ts b/apps/website/screens/components/tabs/code/examples-new/controlled.ts index a90e3173d..6cae0bd8c 100644 --- a/apps/website/screens/components/tabs/code/examples-new/controlled.ts +++ b/apps/website/screens/components/tabs/code/examples-new/controlled.ts @@ -7,13 +7,13 @@ const code = `() => { return ( - setSelectedTab("Mail")}> + setSelectedTab("Mail")}> <> - setSelectedTab("Calendar")}> + setSelectedTab("Calendar")}> <> - setSelectedTab("Contacts")}> + setSelectedTab("Contacts")}> <> diff --git a/apps/website/screens/components/tabs/code/examples-new/icons.ts b/apps/website/screens/components/tabs/code/examples-new/icons.ts index fb6d885da..57b80713a 100644 --- a/apps/website/screens/components/tabs/code/examples-new/icons.ts +++ b/apps/website/screens/components/tabs/code/examples-new/icons.ts @@ -23,13 +23,13 @@ const code = `() => { return ( - + <> - + <> - + <> diff --git a/apps/website/screens/components/tabs/code/examples-new/uncontrolled.ts b/apps/website/screens/components/tabs/code/examples-new/uncontrolled.ts index 308383f31..ebb50c0eb 100644 --- a/apps/website/screens/components/tabs/code/examples-new/uncontrolled.ts +++ b/apps/website/screens/components/tabs/code/examples-new/uncontrolled.ts @@ -4,13 +4,13 @@ const code = `() => { return ( - + <> - + <> - + <> diff --git a/packages/lib/src/tabs/Tab.tsx b/packages/lib/src/tabs/Tab.tsx index ea8a87b8d..1b5fabf00 100644 --- a/packages/lib/src/tabs/Tab.tsx +++ b/packages/lib/src/tabs/Tab.tsx @@ -94,15 +94,15 @@ const Underline = styled.span<{ active: boolean }>` const DxcTab = forwardRef( ( - { active, disabled, icon, label, notificationNumber, onClick, onHover, title }: TabProps, + { active, disabled, icon, label, notificationNumber, onClick, onHover, title, tabId }: TabProps, ref: Ref ) => { const { - activeLabel, - focusedLabel, + activeTabId, + focusedTabId, iconPosition, isControlled, - setActiveLabel, + setActiveTabId, tabIndex = 0, } = useContext(TabsContext) ?? {}; const tabRef = useRef(null); @@ -120,22 +120,22 @@ const DxcTab = forwardRef( }; useEffect(() => { - if (focusedLabel === label) tabRef?.current?.focus(); - }, [focusedLabel, label]); + if (focusedTabId === tabId) tabRef?.current?.focus(); + }, [focusedTabId, tabId]); useEffect(() => { - if (active) setActiveLabel?.(label); - }, [active, label, setActiveLabel]); + if (active) setActiveTabId?.(tabId); + }, [active, tabId, setActiveTabId]); return ( { - if (!isControlled) setActiveLabel?.(label); + if (!isControlled) setActiveTabId?.(tabId); onClick?.(); }} onKeyDown={handleOnKeyDown} @@ -151,7 +151,7 @@ const DxcTab = forwardRef( } }} role="tab" - tabIndex={activeLabel === label && !disabled ? tabIndex : -1} + tabIndex={activeTabId === label && !disabled ? tabIndex : -1} type="button" > @@ -167,7 +167,7 @@ const DxcTab = forwardRef( /> )} - + ); diff --git a/packages/lib/src/tabs/Tabs.stories.tsx b/packages/lib/src/tabs/Tabs.stories.tsx index 1561c52e5..656de288a 100644 --- a/packages/lib/src/tabs/Tabs.stories.tsx +++ b/packages/lib/src/tabs/Tabs.stories.tsx @@ -24,25 +24,25 @@ const iconSVG = ( const tabs = (margin?: Space | Margin) => ( - + <> - + <> - + <> - + <> - + <> - + <> - + <> @@ -50,13 +50,13 @@ const tabs = (margin?: Space | Margin) => ( const disabledTabs = ( - + <> - + <> - + <> @@ -64,13 +64,13 @@ const disabledTabs = ( const firstDisabledTabs = ( - + <> - + <> - + <> @@ -78,25 +78,25 @@ const firstDisabledTabs = ( const tabsNotification = (iconPosition?: "top" | "left") => ( - + <> - + <> - + <> - + <> - + <> - + <> - + <> @@ -104,25 +104,25 @@ const tabsNotification = (iconPosition?: "top" | "left") => ( const tabsIcon = (iconPosition?: "top" | "left") => ( - + <> - + <> - + <> - + <> - + <> - + <> - + <> @@ -130,25 +130,25 @@ const tabsIcon = (iconPosition?: "top" | "left") => ( const tabsNotificationIcon = (iconPosition?: "top" | "left") => ( - + <> - + <> - + <> - + <> - + <> - + <> - + <> diff --git a/packages/lib/src/tabs/Tabs.test.tsx b/packages/lib/src/tabs/Tabs.test.tsx index 7518e6f0e..30ded3b65 100644 --- a/packages/lib/src/tabs/Tabs.test.tsx +++ b/packages/lib/src/tabs/Tabs.test.tsx @@ -10,26 +10,26 @@ import DxcTabs from "./Tabs"; const sampleTabs = ( - + <> - + <> - + <> ); const sampleTabsWithBadge = ( - + <> - + <> - + <> @@ -37,36 +37,36 @@ const sampleTabsWithBadge = ( const sampleTabsFirstDisabled = ( - + <> - + <> ); const sampleTabsInteraction = (onTabClick: (() => void)[]) => ( - + <> - + <> - + <> ); const sampleTabsMiddleDisabled = (onTabClick: (() => void)[]) => ( - + <> - + <> - + <> @@ -74,13 +74,13 @@ const sampleTabsMiddleDisabled = (onTabClick: (() => void)[]) => ( const sampleTabsLastTabNonDisabledFirstActive = (onTabClick: (() => void)[]) => ( - + <> - + <> - + <> @@ -88,13 +88,13 @@ const sampleTabsLastTabNonDisabledFirstActive = (onTabClick: (() => void)[]) => const sampleControlledTabsInteraction = (onTabClick: (() => void)[]) => ( - + <> - + <> - + <> diff --git a/packages/lib/src/tabs/Tabs.tsx b/packages/lib/src/tabs/Tabs.tsx index 43a56a694..3404e6aeb 100644 --- a/packages/lib/src/tabs/Tabs.tsx +++ b/packages/lib/src/tabs/Tabs.tsx @@ -106,7 +106,7 @@ const DxcTabs = ({ () => Children.toArray(children) as ReactElement[], [children] ); - const [activeTabLabel, setActiveTabLabel] = useState(() => { + const [activeTabId, setActiveTabId] = useState(() => { const hasActiveChild = childrenArray.some( (child) => isValidElement(child) && (child.props.active || child.props.defaultActive) && !child.props.disabled ); @@ -116,7 +116,7 @@ const DxcTabs = ({ ) : childrenArray.find((child) => isValidElement(child) && !child.props.disabled); - return isValidElement(initialActiveTab) ? initialActiveTab.props.label : ""; + return isValidElement(initialActiveTab) ? initialActiveTab.props.tabId : ""; }); const [countClick, setCountClick] = useState(0); const [innerFocusIndex, setInnerFocusIndex] = useState(null); @@ -130,14 +130,14 @@ const DxcTabs = ({ const contextValue = useMemo(() => { const focusedChild = innerFocusIndex != null ? childrenArray[innerFocusIndex] : null; return { - activeLabel: activeTabLabel, - focusedLabel: isValidElement(focusedChild) ? focusedChild.props.label : "", + activeTabId: activeTabId, + focusedTabId: isValidElement(focusedChild) ? focusedChild.props.tabId : "", iconPosition, isControlled: childrenArray.some((child) => isValidElement(child) && typeof child.props.active !== "undefined"), - setActiveLabel: setActiveTabLabel, + setActiveTabId: setActiveTabId, tabIndex, }; - }, [activeTabLabel, childrenArray, iconPosition, innerFocusIndex, tabIndex]); + }, [activeTabId, childrenArray, iconPosition, innerFocusIndex, tabIndex]); const scrollLeft = () => { const offsetHeight = refTabList?.current?.offsetHeight ?? 0; @@ -172,7 +172,7 @@ const DxcTabs = ({ }; const handleOnKeyDown = (event: KeyboardEvent) => { - const activeTab = childrenArray.findIndex((child: ReactElement) => child.props.label === activeTabLabel); + const activeTab = childrenArray.findIndex((child: ReactElement) => child.props.tabId === activeTabId); switch (event.key) { case "Left": case "ArrowLeft": @@ -245,7 +245,7 @@ const DxcTabs = ({ {Children.map(children, (child) => - isValidElement(child) && child.props.label === activeTabLabel ? child.props.children : null + isValidElement(child) && child.props.tabId === activeTabId ? child.props.children : null )} ) : ( diff --git a/packages/lib/src/tabs/types.ts b/packages/lib/src/tabs/types.ts index 269100013..6272fd16c 100644 --- a/packages/lib/src/tabs/types.ts +++ b/packages/lib/src/tabs/types.ts @@ -18,15 +18,15 @@ type TabCommonProps = { }; export type TabsContextProps = { - activeLabel: string; - focusedLabel: string; + activeTabId: string; + focusedTabId: string; iconPosition?: "top" | "left"; isControlled: boolean; - setActiveLabel: (_tab: string) => void; + setActiveTabId: (_tab: string) => void; tabIndex: number; }; -export type TabLabelProps = TabCommonProps & { +export type TabLabelProps = { /** * Tab label. */ @@ -37,7 +37,7 @@ export type TabLabelProps = TabCommonProps & { icon?: string | SVG; }; -export type TabIconProps = TabCommonProps & { +export type TabIconProps = { /** * Tab label. */ @@ -49,7 +49,7 @@ export type TabIconProps = TabCommonProps & { }; export type TabPropsLegacy = { - tab: TabLabelProps | TabIconProps; + tab: TabCommonProps & (TabLabelProps | TabIconProps); active: boolean; tabIndex: number; hasLabelAndIcon: boolean; @@ -62,15 +62,14 @@ export type TabPropsLegacy = { export type TabProps = { defaultActive?: boolean; active?: boolean; - icon?: string | SVG; - label: string; title?: string; + tabId: string; disabled?: boolean; notificationNumber?: boolean | number; children: ReactNode; onClick?: () => void; onHover?: () => void; -}; +} & (TabLabelProps | TabIconProps); type LegacyProps = { /** From 5d2092f30bcdb299b994a1dabd869278e14dd362 Mon Sep 17 00:00:00 2001 From: Jialecl Date: Thu, 15 May 2025 16:00:18 +0200 Subject: [PATCH 2/3] required tab added --- apps/website/screens/components/tabs/code/TabsCodePage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/website/screens/components/tabs/code/TabsCodePage.tsx b/apps/website/screens/components/tabs/code/TabsCodePage.tsx index c552d2259..57c5ef095 100644 --- a/apps/website/screens/components/tabs/code/TabsCodePage.tsx +++ b/apps/website/screens/components/tabs/code/TabsCodePage.tsx @@ -304,6 +304,7 @@ const sections = [ + tabId From aaf1818f51370fa92d7456d2cc48618492e4685a Mon Sep 17 00:00:00 2001 From: Jialecl Date: Thu, 15 May 2025 16:17:59 +0200 Subject: [PATCH 3/3] types error in legacyTab fixed --- packages/lib/src/tabs/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lib/src/tabs/types.ts b/packages/lib/src/tabs/types.ts index 6272fd16c..0f0157e5b 100644 --- a/packages/lib/src/tabs/types.ts +++ b/packages/lib/src/tabs/types.ts @@ -112,7 +112,7 @@ type LegacyProps = { * @deprecated This prop is deprecated and will be removed in future versions. * An array of objects representing the tabs. */ - tabs?: (TabLabelProps | TabIconProps)[]; + tabs?: (TabCommonProps & (TabLabelProps | TabIconProps))[]; }; type NewProps = {