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 1ad41c7292..c552d22591 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 a90e3173db..6cae0bd8c4 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 fb6d885da3..57b80713a9 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 308383f318..ebb50c0eba 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 ea8a87b8d3..1b5fabf006 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 1561c52e5e..656de288a5 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 7518e6f0eb..30ded3b65c 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 43a56a694b..3404e6aebd 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 2691000135..6272fd16c3 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 c552d22591..57c5ef095d 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 6272fd16c3..0f0157e5b8 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 = {