From c5893d76b428d0e43ad8571933097c6adca3d1e1 Mon Sep 17 00:00:00 2001 From: Randall Krauskopf <104226843+randall-krauskopf@users.noreply.github.com> Date: Wed, 4 Dec 2024 00:13:02 +0000 Subject: [PATCH 01/11] convert UnderlineItem --- .../UnderlineTabbedInterface.module.css | 103 ++++++++++ .../components/UnderlineTabbedInterface.tsx | 183 +++++++++--------- 2 files changed, 199 insertions(+), 87 deletions(-) create mode 100644 packages/react/src/internal/components/UnderlineTabbedInterface.module.css diff --git a/packages/react/src/internal/components/UnderlineTabbedInterface.module.css b/packages/react/src/internal/components/UnderlineTabbedInterface.module.css new file mode 100644 index 00000000000..e6d8090fcae --- /dev/null +++ b/packages/react/src/internal/components/UnderlineTabbedInterface.module.css @@ -0,0 +1,103 @@ +.UnderlineItem { + /* underline tab specific styles */ + position: relative; + display: inline-flex; + font: inherit; + font-size: var(--text-body-size-medium); + line-height: var(--text-body-lineHeight-medium, 1.4285); + color: var(--fgColor-default); + text-align: center; + text-decoration: none; + cursor: pointer; + background-color: transparent; + border: 0; + border-radius: var(--borderRadius-medium, var(--borderRadius-small)); + + /* button resets */ + appearance: none; + padding-inline: var(--base-size-8); + padding-block: var(--base-size-6); + align-items: center; + + @media (hover: hover) { + &:hover { + text-decoration: none; + background-color: var(--bgColor-neutral-muted); + transition: background-color 0.12s ease-out; + } + } + +} + +.UnderlineItem:focus { + outline: 2px solid transparent; + /* stylelint-disable-next-line primer/box-shadow */ + box-shadow: inset 0 0 0 2px var(--fgColor-accent); + + /* where focus-visible is supported, remove the focus box-shadow */ + &:not(:focus-visible) { + box-shadow: none; + } +} + +.UnderlineItem:focus-visible { + outline: 2px solid transparent; + /* stylelint-disable-next-line primer/box-shadow */ + box-shadow: inset 0 0 0 2px var(--fgColor-accent); +} + +/* renders a visibly hidden "copy" of the label in bold, reserving box space for when label becomes bold on selected */ +.UnderlineItem [data-content]::before { + display: block; + height: 0; + font-weight: var(--base-text-weight-semibold); + white-space: nowrap; + visibility: hidden; + content: attr(data-content); +} + +.UnderlineItem[data-component='icon'] { + display: inline-flex; + color: var(--fgColor-muted); + align-items: center; + margin-inline-end: var(--base-size-8); +} + +.UnderlineItem[data-component='counter'] { + margin-inline-start: var(--base-size-8); + display: flex; + align-items: center; +} + +.UnderlineItem::after { + position: absolute; + right: 50%; + + /* TODO: see if we can simplify this positioning */ + + /* 48px total height / 2 (24px) + 1px */ + /* stylelint-disable-next-line primer/spacing */ + bottom: calc(50% - calc(var(--control-xlarge-size, var(--base-size-48)) / 2 + 1px)); + width: 100%; + height: 2px; + content: ''; + background-color: transparent; + border-radius: 0; + transform: translate(50%, -50%); +} + +.UnderlineItem[aria-current]:not([aria-current='false']) [data-component='text'], +.UnderlineItem[aria-selected='true'] [data-component='text']{ + font-weight: var(--base-text-weight-semibold); +} + +.UnderlineItem[aria-current]:not([aria-current='false'])::after, +.UnderlineItem[aria-selected='true']::after { + /* stylelint-disable-next-line primer/colors */ + background-color: var(--underlineNav-borderColor-active, var(--color-primer-border-active, #fd8c73)); + + @media (forced-colors: active) { + /* Support for Window Force Color Mode https://learn.microsoft.com/en-us/fluent-ui/web-components/design-system/high-contrast */ + background-color: LinkText; + } +} \ No newline at end of file diff --git a/packages/react/src/internal/components/UnderlineTabbedInterface.tsx b/packages/react/src/internal/components/UnderlineTabbedInterface.tsx index 57198833a0e..2d741846093 100644 --- a/packages/react/src/internal/components/UnderlineTabbedInterface.tsx +++ b/packages/react/src/internal/components/UnderlineTabbedInterface.tsx @@ -9,10 +9,15 @@ import sx, {type SxProp} from '../../sx' import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../../utils/polymorphic' import {defaultSxProp} from '../../utils/defaultSxProp' import {get} from '../../constants' +import {toggleStyledComponent} from '../utils/toggleStyledComponent' + +import classes from './UnderlineTabbedInterface.module.css' // The gap between the list items. It is a constant because the gap is used to calculate the possible number of items that can fit in the container. export const GAP = 8 +const CSS_MODULES_FEATURE_FLAG = 'primer_react_css_modules_team' + export const StyledUnderlineWrapper = styled.div` display: flex; padding-inline: var(--stack-padding-normal, ${get('space.3')}); @@ -37,110 +42,114 @@ export const StyledUnderlineItemList = styled.ul` position: relative; ` -export const StyledUnderlineItem = styled.div` - /* button resets */ - appearance: none; - background-color: transparent; - border: 0; - cursor: pointer; - font: inherit; +export const StyledUnderlineItem = toggleStyledComponent( + CSS_MODULES_FEATURE_FLAG, + 'div', + styled.div` + /* button resets */ + appearance: none; + background-color: transparent; + border: 0; + cursor: pointer; + font: inherit; - /* underline tab specific styles */ - position: relative; - display: inline-flex; - color: ${get('colors.fg.default')}; - text-align: center; - text-decoration: none; - line-height: var(--text-body-lineHeight-medium, 1.4285); - border-radius: var(--borderRadius-medium, ${get('radii.2')}); - font-size: var(--text-body-size-medium, ${get('fontSizes.1')}); - padding-inline: var(--control-medium-paddingInline-condensed, ${get('space.2')}); - padding-block: var(--control-medium-paddingBlock, 6px); - align-items: center; + /* underline tab specific styles */ + position: relative; + display: inline-flex; + color: ${get('colors.fg.default')}; + text-align: center; + text-decoration: none; + line-height: var(--text-body-lineHeight-medium, 1.4285); + border-radius: var(--borderRadius-medium, ${get('radii.2')}); + font-size: var(--text-body-size-medium, ${get('fontSizes.1')}); + padding-inline: var(--control-medium-paddingInline-condensed, ${get('space.2')}); + padding-block: var(--control-medium-paddingBlock, 6px); + align-items: center; - @media (hover: hover) { - &:hover { - background-color: var(--bgColor-neutral-muted, ${get('colors.neutral.subtle')}); - transition: background 0.12s ease-out; - text-decoration: none; + @media (hover: hover) { + &:hover { + background-color: var(--bgColor-neutral-muted, ${get('colors.neutral.subtle')}); + transition: background 0.12s ease-out; + text-decoration: none; + } } - } - &:focus: { - outline: 2px solid transparent; - box-shadow: inset 0 0 0 2px var(--fgColor-accent, ${get('colors.accent.fg')}); + &:focus: { + outline: 2px solid transparent; + box-shadow: inset 0 0 0 2px var(--fgColor-accent, ${get('colors.accent.fg')}); - /* where focus-visible is supported, remove the focus box-shadow */ - &:not(:focus-visible) { - box-shadow: none; + /* where focus-visible is supported, remove the focus box-shadow */ + &:not(:focus-visible) { + box-shadow: none; + } } - } - - &:focus-visible { - outline: 2px solid transparent; - box-shadow: inset 0 0 0 2px var(--fgColor-accent, ${get('colors.accent.fg')}); - } - - /* renders a visibly hidden "copy" of the label in bold, reserving box space for when label becomes bold on selected */ - [data-content]::before { - content: attr(data-content); - display: block; - height: 0; - font-weight: var(--base-text-weight-semibold, ${get('fontWeights.semibold')}); - visibility: hidden; - white-space: nowrap; - } - - [data-component='icon'] { - color: var(--fgColor-muted, ${get('colors.fg.muted')}); - align-items: center; - display: inline-flex; - margin-inline-end: var(--control-medium-gap, ${get('space.2')}); - } - [data-component='counter'] { - margin-inline-start: var(--control-medium-gap, ${get('space.2')}); - display: flex; - align-items: center; - } - - /* selected state styles */ - &::after { - position: absolute; - right: 50%; - /* TODO: see if we can simplify this positioning */ - /* 48px total height / 2 (24px) + 1px */ - bottom: calc(50% - calc(var(--control-xlarge-size, 48px) / 2 + 1px)); - width: 100%; - height: 2px; - content: ''; - background-color: transparent; - border-radius: 0; - transform: translate(50%, -50%); - } + &:focus-visible { + outline: 2px solid transparent; + box-shadow: inset 0 0 0 2px var(--fgColor-accent, ${get('colors.accent.fg')}); + } - &[aria-current]:not([aria-current='false']), - &[aria-selected='true'] { - [data-component='text'] { + /* renders a visibly hidden "copy" of the label in bold, reserving box space for when label becomes bold on selected */ + [data-content]::before { + content: attr(data-content); + display: block; + height: 0; font-weight: var(--base-text-weight-semibold, ${get('fontWeights.semibold')}); + visibility: hidden; + white-space: nowrap; + } + + [data-component='icon'] { + color: var(--fgColor-muted, ${get('colors.fg.muted')}); + align-items: center; + display: inline-flex; + margin-inline-end: var(--control-medium-gap, ${get('space.2')}); } + [data-component='counter'] { + margin-inline-start: var(--control-medium-gap, ${get('space.2')}); + display: flex; + align-items: center; + } + + /* selected state styles */ &::after { - background-color: var(--underlineNav-borderColor-active, var(--color-primer-border-active, #fd8c73)); + position: absolute; + right: 50%; + /* TODO: see if we can simplify this positioning */ + /* 48px total height / 2 (24px) + 1px */ + bottom: calc(50% - calc(var(--control-xlarge-size, 48px) / 2 + 1px)); + width: 100%; + height: 2px; + content: ''; + background-color: transparent; + border-radius: 0; + transform: translate(50%, -50%); } - } - @media (forced-colors: active) { &[aria-current]:not([aria-current='false']), &[aria-selected='true'] { - ::after { - // Support for Window Force Color Mode https://learn.microsoft.com/en-us/fluent-ui/web-components/design-system/high-contrast - background-color: LinkText; + [data-component='text'] { + font-weight: var(--base-text-weight-semibold, ${get('fontWeights.semibold')}); + } + + &::after { + background-color: var(--underlineNav-borderColor-active, var(--color-primer-border-active, #fd8c73)); } } - } - ${sx}; -` + + @media (forced-colors: active) { + &[aria-current]:not([aria-current='false']), + &[aria-selected='true'] { + ::after { + // Support for Window Force Color Mode https://learn.microsoft.com/en-us/fluent-ui/web-components/design-system/high-contrast + background-color: LinkText; + } + } + } + ${sx}; + `, +) const loadingKeyframes = keyframes` from { opacity: 1; } @@ -213,7 +222,7 @@ export const UnderlineItem = forwardRef( forwardedRef, ) => { return ( - + {iconsVisible && Icon && {isElement(Icon) ? Icon : }} {children && ( From 1709d78f8d3839f1c2ba31a2fcda1695e3824579 Mon Sep 17 00:00:00 2001 From: Randall Krauskopf <104226843+randall-krauskopf@users.noreply.github.com> Date: Wed, 4 Dec 2024 19:25:17 +0000 Subject: [PATCH 02/11] convert item list --- .../UnderlineTabbedInterface.module.css | 26 +++++++++++ .../components/UnderlineTabbedInterface.tsx | 43 ++++++++++++++----- 2 files changed, 59 insertions(+), 10 deletions(-) diff --git a/packages/react/src/internal/components/UnderlineTabbedInterface.module.css b/packages/react/src/internal/components/UnderlineTabbedInterface.module.css index e6d8090fcae..7eaf8ab49cb 100644 --- a/packages/react/src/internal/components/UnderlineTabbedInterface.module.css +++ b/packages/react/src/internal/components/UnderlineTabbedInterface.module.css @@ -1,3 +1,29 @@ +.UnderlineWrapper { + display: flex; + /* stylelint-disable-next-line primer/spacing */ + padding-inline: var(--stack-padding-normal); + justify-content: flex-start; + align-items: center; + + /* make space for the underline */ + min-height: var(--control-xlarge-size, 48px); + + /* using a box-shadow instead of a border to accomodate 'overflow-y: hidden' on UnderlinePanels */ + /* stylelint-disable-next-line primer/box-shadow */ + box-shadow: inset 0 -1px var(--borderColor-muted); +} + +.UnderlineItemList { + position: relative; + display: flex; + padding: 0; + margin: 0; + white-space: nowrap; + list-style: none; + align-items: center; + gap: 8px; +} + .UnderlineItem { /* underline tab specific styles */ position: relative; diff --git a/packages/react/src/internal/components/UnderlineTabbedInterface.tsx b/packages/react/src/internal/components/UnderlineTabbedInterface.tsx index 2d741846093..a518c36e17e 100644 --- a/packages/react/src/internal/components/UnderlineTabbedInterface.tsx +++ b/packages/react/src/internal/components/UnderlineTabbedInterface.tsx @@ -12,6 +12,7 @@ import {get} from '../../constants' import {toggleStyledComponent} from '../utils/toggleStyledComponent' import classes from './UnderlineTabbedInterface.module.css' +import {useFeatureFlag} from '../../FeatureFlags' // The gap between the list items. It is a constant because the gap is used to calculate the possible number of items that can fit in the container. export const GAP = 8 @@ -31,16 +32,38 @@ export const StyledUnderlineWrapper = styled.div` ${sx}; ` -export const StyledUnderlineItemList = styled.ul` - display: flex; - list-style: none; - white-space: nowrap; - padding: 0; - margin: 0; - align-items: center; - gap: ${GAP}px; - position: relative; -` +const StyledComponentUnderlineItemList = toggleStyledComponent( + CSS_MODULES_FEATURE_FLAG, + 'ul', + styled.ul` + display: flex; + list-style: none; + white-space: nowrap; + padding: 0; + margin: 0; + align-items: center; + gap: ${GAP}px; + position: relative; + `, +) + +export const StyledUnderlineItemList = forwardRef(({children, ...rest}: PropsWithChildren, forwardedRef) => { + const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) + + if (enabled) { + return ( + + {children} + + ) + } + + return ( + + {children} + + ) +}) as PolymorphicForwardRefComponent<'ul'> export const StyledUnderlineItem = toggleStyledComponent( CSS_MODULES_FEATURE_FLAG, From 196eb0525eb7d7e9084f15bbf4d352ddf5279bdc Mon Sep 17 00:00:00 2001 From: Randall Krauskopf <104226843+randall-krauskopf@users.noreply.github.com> Date: Wed, 4 Dec 2024 20:36:43 +0000 Subject: [PATCH 03/11] additional css module migration --- .../UnderlinePanels.module.css | 10 ++++ .../UnderlinePanels/UnderlinePanels.tsx | 25 ++++++++ .../components/UnderlineTabbedInterface.tsx | 59 +++++++++++++++---- 3 files changed, 82 insertions(+), 12 deletions(-) create mode 100644 packages/react/src/experimental/UnderlinePanels/UnderlinePanels.module.css diff --git a/packages/react/src/experimental/UnderlinePanels/UnderlinePanels.module.css b/packages/react/src/experimental/UnderlinePanels/UnderlinePanels.module.css new file mode 100644 index 00000000000..2b9bb28eb09 --- /dev/null +++ b/packages/react/src/experimental/UnderlinePanels/UnderlinePanels.module.css @@ -0,0 +1,10 @@ +.StyledUnderlineWrapper { + width: 100%; + overflow-x: auto; + overflow-y: hidden; + -webkit-overflow-scrolling: auto; +} + +.StyledUnderlineWrapper[data-icons-visible='false'] [data-component='icon'] { + display: none; +} \ No newline at end of file diff --git a/packages/react/src/experimental/UnderlinePanels/UnderlinePanels.tsx b/packages/react/src/experimental/UnderlinePanels/UnderlinePanels.tsx index b683cb35054..39600a71fe6 100644 --- a/packages/react/src/experimental/UnderlinePanels/UnderlinePanels.tsx +++ b/packages/react/src/experimental/UnderlinePanels/UnderlinePanels.tsx @@ -15,6 +15,10 @@ import {merge, type BetterSystemStyleObject, type SxProp} from '../../sx' import {defaultSxProp} from '../../utils/defaultSxProp' import {useResizeObserver, type ResizeObserverEntry} from '../../hooks/useResizeObserver' import useIsomorphicLayoutEffect from '../../utils/useIsomorphicLayoutEffect' +import {useFeatureFlag} from '../../FeatureFlags' +import classes from './UnderlinePanels.module.css' + +const CSS_MODULES_FEATURE_FLAG = 'primer_react_css_modules_team' export type UnderlinePanelsProps = { /** @@ -141,6 +145,27 @@ const UnderlinePanels: FC = ({ ) } + const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) + + if (enabled) { + return ( + + + + {tabs.current} + + + {tabPanels.current} + + ) + } return ( , + forwardedRef, + ) => { + const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) + + if (enabled) { + return ( + + {children} + + ) + } + return ( + + {children} + + ) + }, +) const StyledComponentUnderlineItemList = toggleStyledComponent( CSS_MODULES_FEATURE_FLAG, From 485e8ff9dbad63cda16036779bf4724da85cf490 Mon Sep 17 00:00:00 2001 From: Randall Krauskopf <104226843+randall-krauskopf@users.noreply.github.com> Date: Wed, 4 Dec 2024 20:39:23 +0000 Subject: [PATCH 04/11] formatting --- .../experimental/UnderlinePanels/UnderlinePanels.module.css | 2 +- .../internal/components/UnderlineTabbedInterface.module.css | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/react/src/experimental/UnderlinePanels/UnderlinePanels.module.css b/packages/react/src/experimental/UnderlinePanels/UnderlinePanels.module.css index 2b9bb28eb09..9e4905c2c7f 100644 --- a/packages/react/src/experimental/UnderlinePanels/UnderlinePanels.module.css +++ b/packages/react/src/experimental/UnderlinePanels/UnderlinePanels.module.css @@ -7,4 +7,4 @@ .StyledUnderlineWrapper[data-icons-visible='false'] [data-component='icon'] { display: none; -} \ No newline at end of file +} diff --git a/packages/react/src/internal/components/UnderlineTabbedInterface.module.css b/packages/react/src/internal/components/UnderlineTabbedInterface.module.css index 7eaf8ab49cb..6f193c13e74 100644 --- a/packages/react/src/internal/components/UnderlineTabbedInterface.module.css +++ b/packages/react/src/internal/components/UnderlineTabbedInterface.module.css @@ -52,7 +52,6 @@ transition: background-color 0.12s ease-out; } } - } .UnderlineItem:focus { @@ -113,7 +112,7 @@ } .UnderlineItem[aria-current]:not([aria-current='false']) [data-component='text'], -.UnderlineItem[aria-selected='true'] [data-component='text']{ +.UnderlineItem[aria-selected='true'] [data-component='text'] { font-weight: var(--base-text-weight-semibold); } @@ -126,4 +125,4 @@ /* Support for Window Force Color Mode https://learn.microsoft.com/en-us/fluent-ui/web-components/design-system/high-contrast */ background-color: LinkText; } -} \ No newline at end of file +} From c6e777bfd4f11f7e3dc249edb645640686a03ffd Mon Sep 17 00:00:00 2001 From: Randall Krauskopf <104226843+randall-krauskopf@users.noreply.github.com> Date: Wed, 4 Dec 2024 21:16:16 +0000 Subject: [PATCH 05/11] fix selectors --- .../internal/components/UnderlineTabbedInterface.module.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react/src/internal/components/UnderlineTabbedInterface.module.css b/packages/react/src/internal/components/UnderlineTabbedInterface.module.css index 6f193c13e74..23d19348f95 100644 --- a/packages/react/src/internal/components/UnderlineTabbedInterface.module.css +++ b/packages/react/src/internal/components/UnderlineTabbedInterface.module.css @@ -81,14 +81,14 @@ content: attr(data-content); } -.UnderlineItem[data-component='icon'] { +.UnderlineItem [data-component='icon'] { display: inline-flex; color: var(--fgColor-muted); align-items: center; margin-inline-end: var(--base-size-8); } -.UnderlineItem[data-component='counter'] { +.UnderlineItem [data-component='counter'] { margin-inline-start: var(--base-size-8); display: flex; align-items: center; From a376d864b1ed05e9faf36735cd68367d638d3ffb Mon Sep 17 00:00:00 2001 From: Randall Krauskopf <104226843+randall-krauskopf@users.noreply.github.com> Date: Wed, 4 Dec 2024 21:33:32 +0000 Subject: [PATCH 06/11] Migrate Loading Counter --- .changeset/slimy-chefs-divide.md | 5 +++++ .../UnderlineTabbedInterface.module.css | 21 +++++++++++++++++++ .../components/UnderlineTabbedInterface.tsx | 12 ++++++++++- 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 .changeset/slimy-chefs-divide.md diff --git a/.changeset/slimy-chefs-divide.md b/.changeset/slimy-chefs-divide.md new file mode 100644 index 00000000000..1c69f707b90 --- /dev/null +++ b/.changeset/slimy-chefs-divide.md @@ -0,0 +1,5 @@ +--- +"@primer/react": minor +--- + +Convert UnderlinePanels to CSS modules behind feature flags diff --git a/packages/react/src/internal/components/UnderlineTabbedInterface.module.css b/packages/react/src/internal/components/UnderlineTabbedInterface.module.css index 23d19348f95..2955f1a6aeb 100644 --- a/packages/react/src/internal/components/UnderlineTabbedInterface.module.css +++ b/packages/react/src/internal/components/UnderlineTabbedInterface.module.css @@ -126,3 +126,24 @@ background-color: LinkText; } } + +.LoadingCounter { + display: inline-block; + width: 1.5rem; + height: 1rem; /* 16px */ + background-color: var(--bgColor-neutral-muted); + border-color: var(--borderColor-default); + /* stylelint-disable-next-line primer/borders */ + border-radius: 20px; + animation: loadingCounterKeyFrames 1.2s ease-in-out infinite alternate; +} + +@keyframes loadingCounterKeyFrames { + from { + opacity: 1; + } + + to { + opacity: 0.2; + } +} diff --git a/packages/react/src/internal/components/UnderlineTabbedInterface.tsx b/packages/react/src/internal/components/UnderlineTabbedInterface.tsx index 8fa5d4ffe04..5ce01b99a36 100644 --- a/packages/react/src/internal/components/UnderlineTabbedInterface.tsx +++ b/packages/react/src/internal/components/UnderlineTabbedInterface.tsx @@ -214,7 +214,7 @@ const loadingKeyframes = keyframes` to { opacity: 0.2; } ` -export const LoadingCounter = styled.span` +export const StyledComponentLoadingCounter = styled.span` animation: ${loadingKeyframes} 1.2s ease-in-out infinite alternate; background-color: var(--bgColor-neutral-muted, ${get('colors.neutral.subtle')}); border-color: var(--borderColor-default, ${get('colors.border.default')}); @@ -224,6 +224,16 @@ export const LoadingCounter = styled.span` border-radius: 20px; ` +export const LoadingCounter = () => { + const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) + + if (enabled) { + return + } + + return +} + // We can uncomment these when/if we add overflow behavior // to the UnderlinePanels component // From b1f8fb1e9068b4c72a64790faa37f26f1794536e Mon Sep 17 00:00:00 2001 From: Randall Krauskopf <104226843+randall-krauskopf@users.noreply.github.com> Date: Thu, 5 Dec 2024 16:32:36 +0000 Subject: [PATCH 07/11] key off of FF --- .../components/UnderlineTabbedInterface.tsx | 65 ++++++++++++++----- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/packages/react/src/internal/components/UnderlineTabbedInterface.tsx b/packages/react/src/internal/components/UnderlineTabbedInterface.tsx index 5ce01b99a36..25af0d0954d 100644 --- a/packages/react/src/internal/components/UnderlineTabbedInterface.tsx +++ b/packages/react/src/internal/components/UnderlineTabbedInterface.tsx @@ -37,15 +37,14 @@ const StyledComponentUnderlineWrapper = toggleStyledComponent( `, ) +type StyledUnderlineWrapperProps = { + slot?: string + as?: React.ElementType + className?: string +} & SxProp + export const StyledUnderlineWrapper = forwardRef( - ( - { - children, - className, - ...rest - }: PropsWithChildren<{slot?: string; as?: React.ElementType; className?: string} & SxProp>, - forwardedRef, - ) => { + ({children, className, ...rest}: PropsWithChildren, forwardedRef) => { const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) if (enabled) { @@ -214,15 +213,19 @@ const loadingKeyframes = keyframes` to { opacity: 0.2; } ` -export const StyledComponentLoadingCounter = styled.span` - animation: ${loadingKeyframes} 1.2s ease-in-out infinite alternate; - background-color: var(--bgColor-neutral-muted, ${get('colors.neutral.subtle')}); - border-color: var(--borderColor-default, ${get('colors.border.default')}); - width: 1.5rem; - height: 1rem; /*16px*/ - display: inline-block; - border-radius: 20px; -` +export const StyledComponentLoadingCounter = toggleStyledComponent( + CSS_MODULES_FEATURE_FLAG, + 'span', + styled.span` + animation: ${loadingKeyframes} 1.2s ease-in-out infinite alternate; + background-color: var(--bgColor-neutral-muted, ${get('colors.neutral.subtle')}); + border-color: var(--borderColor-default, ${get('colors.border.default')}); + width: 1.5rem; + height: 1rem; /*16px*/ + display: inline-block; + border-radius: 20px; + `, +) export const LoadingCounter = () => { const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) @@ -289,8 +292,34 @@ export const UnderlineItem = forwardRef( }: PropsWithChildren, forwardedRef, ) => { + const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) + + if (enabled) { + return ( + + {iconsVisible && Icon && {isElement(Icon) ? Icon : }} + {children && ( + + {children} + + )} + {counter !== undefined ? ( + loadingCounters ? ( + + + + ) : ( + + {counter} + + ) + ) : null} + + ) + } + return ( - + {iconsVisible && Icon && {isElement(Icon) ? Icon : }} {children && ( From 613e920ee502ded96bdc1964d2fb66359ecbcedc Mon Sep 17 00:00:00 2001 From: Randall Krauskopf <104226843+randall-krauskopf@users.noreply.github.com> Date: Thu, 5 Dec 2024 20:13:35 +0000 Subject: [PATCH 08/11] add toggle for the tabContainer --- .../UnderlinePanels/UnderlinePanels.tsx | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/react/src/experimental/UnderlinePanels/UnderlinePanels.tsx b/packages/react/src/experimental/UnderlinePanels/UnderlinePanels.tsx index 39600a71fe6..a1e417133ec 100644 --- a/packages/react/src/experimental/UnderlinePanels/UnderlinePanels.tsx +++ b/packages/react/src/experimental/UnderlinePanels/UnderlinePanels.tsx @@ -1,5 +1,7 @@ import React, {Children, isValidElement, cloneElement, useState, useRef, type FC, type PropsWithChildren} from 'react' import {TabContainerElement} from '@github/tab-container-element' +import {clsx} from 'clsx' +import type {IconProps} from '@primer/octicons-react' import {createComponent} from '../../utils/create-component' import { StyledUnderlineItemList, @@ -10,13 +12,13 @@ import { import Box, {type BoxProps} from '../../Box' import {useId} from '../../hooks' import {invariant} from '../../utils/invariant' -import type {IconProps} from '@primer/octicons-react' import {merge, type BetterSystemStyleObject, type SxProp} from '../../sx' import {defaultSxProp} from '../../utils/defaultSxProp' import {useResizeObserver, type ResizeObserverEntry} from '../../hooks/useResizeObserver' import useIsomorphicLayoutEffect from '../../utils/useIsomorphicLayoutEffect' import {useFeatureFlag} from '../../FeatureFlags' import classes from './UnderlinePanels.module.css' +import {toggleStyledComponent} from '../../internal/utils/toggleStyledComponent' const CSS_MODULES_FEATURE_FLAG = 'primer_react_css_modules_team' @@ -63,6 +65,12 @@ export type PanelProps = Omit const TabContainerComponent = createComponent(TabContainerElement, 'tab-container') +const StyledTabContainerComponent = toggleStyledComponent( + CSS_MODULES_FEATURE_FLAG, + 'tab-container', + TabContainerComponent, +) + const UnderlinePanels: FC = ({ 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, @@ -149,12 +157,12 @@ const UnderlinePanels: FC = ({ if (enabled) { return ( - + @@ -163,11 +171,12 @@ const UnderlinePanels: FC = ({ {tabPanels.current} - + ) } + return ( - + = ({ {tabPanels.current} - + ) } From e94584c1b0f6319fed4d5972d0565dd46044f668 Mon Sep 17 00:00:00 2001 From: Randall Krauskopf <104226843+randall-krauskopf@users.noreply.github.com> Date: Mon, 9 Dec 2024 19:59:50 +0000 Subject: [PATCH 09/11] update to take additional optional dependencies --- packages/react/src/hooks/useResizeObserver.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/react/src/hooks/useResizeObserver.ts b/packages/react/src/hooks/useResizeObserver.ts index 2fdd18326e9..32a0d496e00 100644 --- a/packages/react/src/hooks/useResizeObserver.ts +++ b/packages/react/src/hooks/useResizeObserver.ts @@ -9,7 +9,11 @@ export interface ResizeObserverEntry { contentRect: DOMRectReadOnly } -export function useResizeObserver(callback: ResizeObserverCallback, target?: RefObject) { +export function useResizeObserver( + callback: ResizeObserverCallback, + target?: RefObject, + depsArray: unknown[] = [], +) { const savedCallback = useRef(callback) useLayoutEffect(() => { @@ -31,5 +35,6 @@ export function useResizeObserver(callback: ResizeObserve return () => { observer.disconnect() } - }, [target]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [target, ...depsArray]) } From 8a1ea64b5675b1f265bdffcd4e97ac3927492c1f Mon Sep 17 00:00:00 2001 From: Randall Krauskopf <104226843+randall-krauskopf@users.noreply.github.com> Date: Mon, 9 Dec 2024 20:04:46 +0000 Subject: [PATCH 10/11] update resize observer to key off of feature flag --- .../UnderlinePanels/UnderlinePanels.tsx | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/react/src/experimental/UnderlinePanels/UnderlinePanels.tsx b/packages/react/src/experimental/UnderlinePanels/UnderlinePanels.tsx index a1e417133ec..130a7e2b424 100644 --- a/packages/react/src/experimental/UnderlinePanels/UnderlinePanels.tsx +++ b/packages/react/src/experimental/UnderlinePanels/UnderlinePanels.tsx @@ -114,6 +114,8 @@ const UnderlinePanels: FC = ({ ) const tabsHaveIcons = tabs.current.some(tab => React.isValidElement(tab) && tab.props.icon) + const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) + // this is a workaround to get the list's width on the first render const [listWidth, setListWidth] = useState(0) useIsomorphicLayoutEffect(() => { @@ -126,15 +128,19 @@ const UnderlinePanels: FC = ({ // when the wrapper resizes, check if the icons should be visible // by comparing the wrapper width to the list width - useResizeObserver((resizeObserverEntries: ResizeObserverEntry[]) => { - if (!tabsHaveIcons) { - return - } - - const wrapperWidth = resizeObserverEntries[0].contentRect.width - - setIconsVisible(wrapperWidth > listWidth) - }, wrapperRef) + useResizeObserver( + (resizeObserverEntries: ResizeObserverEntry[]) => { + if (!tabsHaveIcons) { + return + } + + const wrapperWidth = resizeObserverEntries[0].contentRect.width + + setIconsVisible(wrapperWidth > listWidth) + }, + wrapperRef, + [enabled], + ) if (__DEV__) { // only one tab can be selected at a time @@ -153,8 +159,6 @@ const UnderlinePanels: FC = ({ ) } - const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) - if (enabled) { return ( From f2af74eeca1edb0255133584045a93577d514027 Mon Sep 17 00:00:00 2001 From: Randall Krauskopf <104226843+randall-krauskopf@users.noreply.github.com> Date: Mon, 9 Dec 2024 20:09:23 +0000 Subject: [PATCH 11/11] fix lint issue --- .../react/src/experimental/UnderlinePanels/UnderlinePanels.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react/src/experimental/UnderlinePanels/UnderlinePanels.tsx b/packages/react/src/experimental/UnderlinePanels/UnderlinePanels.tsx index 130a7e2b424..2224d77d8cc 100644 --- a/packages/react/src/experimental/UnderlinePanels/UnderlinePanels.tsx +++ b/packages/react/src/experimental/UnderlinePanels/UnderlinePanels.tsx @@ -1,6 +1,5 @@ import React, {Children, isValidElement, cloneElement, useState, useRef, type FC, type PropsWithChildren} from 'react' import {TabContainerElement} from '@github/tab-container-element' -import {clsx} from 'clsx' import type {IconProps} from '@primer/octicons-react' import {createComponent} from '../../utils/create-component' import {