From 448a77631d5bba6d9d26a400bd6082272e033259 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Thu, 4 Sep 2025 12:18:02 -0500 Subject: [PATCH 01/15] refactor(styled-react): update how we re-export components --- eslint.config.mjs | 1 + package-lock.json | 3 + .../__snapshots__/exports.test.ts.snap | 1 + packages/react/src/index.ts | 1 + packages/styled-react/package.json | 1 + packages/styled-react/rollup.config.js | 4 +- .../__snapshots__/exports.test.ts.snap | 14 +-- .../src/__tests__/exports.test.ts | 18 ++- .../src/{deprecated.ts => deprecated.tsx} | 0 .../src/{experimental.ts => experimental.tsx} | 0 packages/styled-react/src/index.ts | 70 ------------ packages/styled-react/src/index.tsx | 105 ++++++++++++++++++ packages/styled-react/src/polymorphic.d.ts | 60 ++++++++++ .../createStyledComponent.browser.test.tsx | 45 -------- .../src/utils/createStyledComponent.ts | 58 ---------- 15 files changed, 196 insertions(+), 185 deletions(-) rename packages/styled-react/src/{deprecated.ts => deprecated.tsx} (100%) rename packages/styled-react/src/{experimental.ts => experimental.tsx} (100%) delete mode 100644 packages/styled-react/src/index.ts create mode 100644 packages/styled-react/src/index.tsx create mode 100644 packages/styled-react/src/polymorphic.d.ts delete mode 100644 packages/styled-react/src/utils/__tests__/createStyledComponent.browser.test.tsx delete mode 100644 packages/styled-react/src/utils/createStyledComponent.ts diff --git a/eslint.config.mjs b/eslint.config.mjs index ad93e3b1df3..64252485dc5 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -42,6 +42,7 @@ const config = defineConfig([ 'contributor-docs/adrs/*', 'examples/codesandbox/**/*', 'packages/react/src/utils/polymorphic.ts', + 'packages/styled-react/src/polymorphic.d.ts', '**/storybook-static', '**/CHANGELOG.md', '**/node_modules/**/*', diff --git a/package-lock.json b/package-lock.json index dc65a6ed215..cdaa7baaf6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1875,6 +1875,8 @@ }, "node_modules/@babel/preset-react": { "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.27.1.tgz", + "integrity": "sha512-oJHWh2gLhU9dW9HHr42q0cI0/iHHXTLGe39qvpAZZzagHy0MzYLCnCVV0symeRvzmjHyVU7mw2K06E6u/JwbhA==", "dev": true, "license": "MIT", "dependencies": { @@ -26320,6 +26322,7 @@ "name": "@primer/styled-react", "version": "1.0.0-rc.1", "devDependencies": { + "@babel/preset-react": "^7.27.1", "@babel/preset-typescript": "^7.27.1", "@primer/react": "^38.0.0-rc.1", "@rollup/plugin-babel": "^6.0.4", diff --git a/packages/react/src/__tests__/__snapshots__/exports.test.ts.snap b/packages/react/src/__tests__/__snapshots__/exports.test.ts.snap index c7db44a1990..8c70316ab29 100644 --- a/packages/react/src/__tests__/__snapshots__/exports.test.ts.snap +++ b/packages/react/src/__tests__/__snapshots__/exports.test.ts.snap @@ -177,6 +177,7 @@ exports[`@primer/react > should not update exports without a semver change 1`] = "type TimelineItemsProps", "type TimelineProps", "ToggleSwitch", + "type ToggleSwitchProps", "Token", "type TokenProps", "Tooltip", diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index a1e066b265d..7ff72480dc0 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -151,6 +151,7 @@ export type {StateLabelProps} from './StateLabel' export {default as SubNav} from './SubNav' export type {SubNavProps, SubNavLinkProps, SubNavLinksProps} from './SubNav' export {default as ToggleSwitch} from './ToggleSwitch' +export type {ToggleSwitchProps} from './ToggleSwitch' export {default as TextInput} from './TextInput' export type {TextInputProps} from './TextInput' export {default as TextInputWithTokens} from './TextInputWithTokens' diff --git a/packages/styled-react/package.json b/packages/styled-react/package.json index 81d43316eb0..74b1451201b 100644 --- a/packages/styled-react/package.json +++ b/packages/styled-react/package.json @@ -27,6 +27,7 @@ "type-check": "tsc --noEmit" }, "devDependencies": { + "@babel/preset-react": "^7.27.1", "@babel/preset-typescript": "^7.27.1", "@primer/react": "^38.0.0-rc.1", "@rollup/plugin-babel": "^6.0.4", diff --git a/packages/styled-react/rollup.config.js b/packages/styled-react/rollup.config.js index 0c5deb298b0..3d8bd571154 100644 --- a/packages/styled-react/rollup.config.js +++ b/packages/styled-react/rollup.config.js @@ -14,14 +14,14 @@ function createPackageRegex(name) { } export default defineConfig({ - input: ['src/index.ts', 'src/experimental.ts', 'src/deprecated.ts'], + input: ['src/index.tsx', 'src/experimental.tsx', 'src/deprecated.tsx'], external: dependencies.map(createPackageRegex), plugins: [ typescript({ tsconfig: 'tsconfig.build.json', }), babel({ - presets: ['@babel/preset-typescript'], + presets: ['@babel/preset-typescript', '@babel/preset-react'], extensions: ['.ts', '.tsx'], babelHelpers: 'bundled', }), diff --git a/packages/styled-react/src/__tests__/__snapshots__/exports.test.ts.snap b/packages/styled-react/src/__tests__/__snapshots__/exports.test.ts.snap index 625ff55c68f..c8204ea3c48 100644 --- a/packages/styled-react/src/__tests__/__snapshots__/exports.test.ts.snap +++ b/packages/styled-react/src/__tests__/__snapshots__/exports.test.ts.snap @@ -2,11 +2,11 @@ exports[`@primer/styled-react exports 1`] = ` [ - "ToggleSwitch", "ActionList", "ActionMenu", "Autocomplete", "Avatar", + "Box", "BranchName", "Breadcrumbs", "Button", @@ -25,6 +25,7 @@ exports[`@primer/styled-react exports 1`] = ` "LabelGroup", "Link", "LinkButton", + "merge", "NavList", "Overlay", "PageHeader", @@ -41,21 +42,20 @@ exports[`@primer/styled-react exports 1`] = ` "Stack", "StateLabel", "SubNav", + "sx", "Text", "Textarea", "TextInput", "TextInputWithTokens", + "theme", + "themeGet", + "ThemeProvider", "Timeline", + "ToggleSwitch", "Token", "Tooltip", "Truncate", "UnderlineNav", - "Box", - "sx", - "ThemeProvider", - "merge", - "theme", - "themeGet", "useColorSchemeVar", "useTheme", ] diff --git a/packages/styled-react/src/__tests__/exports.test.ts b/packages/styled-react/src/__tests__/exports.test.ts index 1ef4ed8b05d..a6c58b205e0 100644 --- a/packages/styled-react/src/__tests__/exports.test.ts +++ b/packages/styled-react/src/__tests__/exports.test.ts @@ -7,13 +7,25 @@ import * as StyledReactDeprecated from '../deprecated' import * as StyledReactExperimental from '../experimental' test('@primer/styled-react exports', () => { - expect(Object.keys(StyledReact)).toMatchSnapshot() + expect( + Object.keys(StyledReact).sort((a, b) => { + return a.localeCompare(b) + }), + ).toMatchSnapshot() }) test('@primer/styled-react/deprecated exports', () => { - expect(Object.keys(StyledReactDeprecated)).toMatchSnapshot() + expect( + Object.keys(StyledReactDeprecated).sort((a, b) => { + return a.localeCompare(b) + }), + ).toMatchSnapshot() }) test('@primer/styled-react/experimental exports', () => { - expect(Object.keys(StyledReactExperimental)).toMatchSnapshot() + expect( + Object.keys(StyledReactExperimental).sort((a, b) => { + return a.localeCompare(b) + }), + ).toMatchSnapshot() }) diff --git a/packages/styled-react/src/deprecated.ts b/packages/styled-react/src/deprecated.tsx similarity index 100% rename from packages/styled-react/src/deprecated.ts rename to packages/styled-react/src/deprecated.tsx diff --git a/packages/styled-react/src/experimental.ts b/packages/styled-react/src/experimental.tsx similarity index 100% rename from packages/styled-react/src/experimental.ts rename to packages/styled-react/src/experimental.tsx diff --git a/packages/styled-react/src/index.ts b/packages/styled-react/src/index.ts deleted file mode 100644 index 5d25e5b742b..00000000000 --- a/packages/styled-react/src/index.ts +++ /dev/null @@ -1,70 +0,0 @@ -import {ToggleSwitch as PrimerToggleSwitch} from '@primer/react' -import {createStyledComponent} from './utils/createStyledComponent' - -const ToggleSwitch: ReturnType = /*#__PURE__*/ createStyledComponent(PrimerToggleSwitch) -export {ToggleSwitch} - -export { - ActionList, - ActionMenu, - Autocomplete, - Avatar, - BranchName, - Breadcrumbs, - Button, - Checkbox, - CheckboxGroup, - CircleBadge, - CounterLabel, - Details, - Dialog, - Flash, - FormControl, - Header, - Heading, - IconButton, - Label, - LabelGroup, - Link, - LinkButton, - NavList, - Overlay, - PageHeader, - PageLayout, - Popover, - ProgressBar, - RadioGroup, - RelativeTime, - SegmentedControl, - Select, - SelectPanel, - SideNav, - Spinner, - Stack, - StateLabel, - SubNav, - Text, - Textarea, - TextInput, - TextInputWithTokens, - Timeline, - Token, - Tooltip, - Truncate, - UnderlineNav, - - // styled-components components or types - Box, - type BoxProps, - sx, - type SxProp, - type BetterSystemStyleObject, - - // theming depends on styled-components - ThemeProvider, - merge, - theme, - themeGet, - useColorSchemeVar, - useTheme, -} from '@primer/react' diff --git a/packages/styled-react/src/index.tsx b/packages/styled-react/src/index.tsx new file mode 100644 index 00000000000..1ece780ede2 --- /dev/null +++ b/packages/styled-react/src/index.tsx @@ -0,0 +1,105 @@ +import { + type BetterSystemStyleObject, + Box, + type BoxProps, + type SxProp, + ToggleSwitch as PrimerToggleSwitch, + type ToggleSwitchProps as PrimerToggleSwitchProps, +} from '@primer/react' +import {forwardRef} from 'react' +import type { + BackgroundProps, + BorderProps, + ColorProps, + FlexboxProps, + GridProps, + LayoutProps, + PositionProps, + ShadowProps, + SpaceProps, + TypographyProps, +} from 'styled-system' +import type {ForwardRefComponent} from './polymorphic' + +type StyledProps = SxProp & + SpaceProps & + ColorProps & + TypographyProps & + LayoutProps & + FlexboxProps & + GridProps & + BackgroundProps & + BorderProps & + PositionProps & + ShadowProps + +const ToggleSwitch = forwardRef(function ToggleSwitch(props, ref) { + // @ts-expect-error there is an issue with polymorphic `as` with this + // component + return +}) as ForwardRefComponent<'span', PrimerToggleSwitchProps & Omit> + +export {ToggleSwitch} + +export { + ActionList, + ActionMenu, + Autocomplete, + Avatar, + BranchName, + Breadcrumbs, + Button, + Checkbox, + CheckboxGroup, + CircleBadge, + CounterLabel, + Details, + Dialog, + Flash, + FormControl, + Header, + Heading, + IconButton, + Label, + LabelGroup, + Link, + LinkButton, + NavList, + Overlay, + PageHeader, + PageLayout, + Popover, + ProgressBar, + RadioGroup, + RelativeTime, + SegmentedControl, + Select, + SelectPanel, + SideNav, + Spinner, + Stack, + StateLabel, + SubNav, + Text, + Textarea, + TextInput, + TextInputWithTokens, + Timeline, + Token, + Tooltip, + Truncate, + UnderlineNav, + + // styled-components components or types + Box, + sx, + + // theming depends on styled-components + ThemeProvider, + merge, + theme, + themeGet, + useColorSchemeVar, + useTheme, +} from '@primer/react' +export type {BoxProps, SxProp, BetterSystemStyleObject} diff --git a/packages/styled-react/src/polymorphic.d.ts b/packages/styled-react/src/polymorphic.d.ts new file mode 100644 index 00000000000..04e61883c55 --- /dev/null +++ b/packages/styled-react/src/polymorphic.d.ts @@ -0,0 +1,60 @@ +/** + * This file is originally from `@radix-ui/react-polymorphic` before the package + * was deprecated. The original source for this lived in the URL below. + * + * @see https://github.com/radix-ui/primitives/blob/17ffcb7aaa42cbd36b3c210ba86d7d73d218e5be/packages/react/polymorphic/src/polymorphic.ts + */ + +import * as React from 'react' + +/* ------------------------------------------------------------------------------------------------- + * Utility types + * -----------------------------------------------------------------------------------------------*/ +type Merge = Omit & P2 + +/** + * Infers the OwnProps if E is a ForwardRefExoticComponentWithAs + */ +type OwnProps = E extends ForwardRefComponent ? P : {} + +/** + * Infers the JSX.IntrinsicElement if E is a ForwardRefExoticComponentWithAs + */ +type IntrinsicElement = E extends ForwardRefComponent ? I : never + +type ForwardRefExoticComponent = React.ForwardRefExoticComponent< + Merge : never, OwnProps & {as?: E}> +> + +/* ------------------------------------------------------------------------------------------------- + * ForwardRefComponent + * -----------------------------------------------------------------------------------------------*/ + +interface ForwardRefComponent< + IntrinsicElementString, + OwnProps = {}, + /** + * Extends original type to ensure built in React types play nice + * with polymorphic components still e.g. `React.ElementRef` etc. + */ +> extends ForwardRefExoticComponent { + /** + * When `as` prop is passed, use this overload. + * Merges original own props (without DOM props) and the inferred props + * from `as` element with the own props taking precedence. + * + * We explicitly avoid `React.ElementType` and manually narrow the prop types + * so that events are typed when using JSX.IntrinsicElements. + */ + ( + props: As extends '' + ? {as: keyof JSX.IntrinsicElements} + : As extends React.ComponentType + ? Merge + : As extends keyof JSX.IntrinsicElements + ? Merge + : never, + ): React.ReactElement | null +} + +export type {ForwardRefComponent, OwnProps, IntrinsicElement, Merge} diff --git a/packages/styled-react/src/utils/__tests__/createStyledComponent.browser.test.tsx b/packages/styled-react/src/utils/__tests__/createStyledComponent.browser.test.tsx deleted file mode 100644 index b713650eeca..00000000000 --- a/packages/styled-react/src/utils/__tests__/createStyledComponent.browser.test.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import {render, screen} from '@testing-library/react' -import {describe, test, expect} from 'vitest' -import {createStyledComponent} from '../createStyledComponent' - -describe('createStyledComponent', () => { - test('supports the sx prop', () => { - const Wrapper = createStyledComponent(function Test(props: React.ComponentPropsWithoutRef<'div'>) { - return
- }) - - render() - - const wrapper = screen.getByTestId('wrapper') - const style = window.getComputedStyle(wrapper) - expect(style.display).toBe('flex') - }) - - test('supports the as prop', () => { - const Wrapper = createStyledComponent(function Test({ - as: BaseComponent, - ...rest - }: { - as: React.ElementType - className?: string - }) { - return - }) - - render() - const wrapper = screen.getByTestId('wrapper') - expect(wrapper.tagName).toBe('SECTION') - - const style = window.getComputedStyle(wrapper) - expect(style.display).toBe('flex') - }) - - test('supports original component props on wrapper', () => { - const Wrapper = createStyledComponent(function Test(props: {variant: 'a' | 'b'}) { - return
- }) - - render() - expect(screen.getByTestId('wrapper')).toHaveAttribute('data-variant', 'a') - }) -}) diff --git a/packages/styled-react/src/utils/createStyledComponent.ts b/packages/styled-react/src/utils/createStyledComponent.ts deleted file mode 100644 index 5bacfd1fc96..00000000000 --- a/packages/styled-react/src/utils/createStyledComponent.ts +++ /dev/null @@ -1,58 +0,0 @@ -import {sx} from '@primer/react' -import type {SxProp} from '@primer/react' -import type React from 'react' -import styled from 'styled-components' -import {background, border, color, flexbox, grid, layout, position, shadow, space, typography} from 'styled-system' -import type { - BackgroundProps, - BorderProps, - ColorProps, - FlexboxProps, - GridProps, - LayoutProps, - PositionProps, - ShadowProps, - SpaceProps, - TypographyProps, -} from 'styled-system' - -type StyledBoxProps = SxProp & - SpaceProps & - ColorProps & - TypographyProps & - LayoutProps & - FlexboxProps & - GridProps & - BackgroundProps & - BorderProps & - PositionProps & - ShadowProps - -/** - * Utility that mirrors the functionality of the `Box` component from - * `@primer/react`. Used to create a styled component variant of a component - * from `@primer/react` that no longer supports `sx` or other styled-system - * props. - * - * Note: make sure to include #__PURE__ when using this function to create a - * component. For example: - * - * ```tsx - * const Link = \/*#__PURE__*\/ createStyledComponent(PrimerLink) - * ``` - */ -export function createStyledComponent

(Component: React.ComponentType

) { - return styled(Component)( - space, - color, - typography, - layout, - flexbox, - grid, - background, - border, - position, - shadow, - sx, - ) -} From d6817dfc35e932ecee0c673d92591cb61a383bad Mon Sep 17 00:00:00 2001 From: Josh Black Date: Thu, 4 Sep 2025 12:20:10 -0500 Subject: [PATCH 02/15] chore: add changesets --- .changeset/chubby-colts-nail.md | 5 +++++ .changeset/nine-cobras-talk.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/chubby-colts-nail.md create mode 100644 .changeset/nine-cobras-talk.md diff --git a/.changeset/chubby-colts-nail.md b/.changeset/chubby-colts-nail.md new file mode 100644 index 00000000000..6c78ba5ba51 --- /dev/null +++ b/.changeset/chubby-colts-nail.md @@ -0,0 +1,5 @@ +--- +'@primer/styled-react': patch +--- + +Refactor ToggleSwitch export type to match original type from @primer/react diff --git a/.changeset/nine-cobras-talk.md b/.changeset/nine-cobras-talk.md new file mode 100644 index 00000000000..f9e14b41019 --- /dev/null +++ b/.changeset/nine-cobras-talk.md @@ -0,0 +1,5 @@ +--- +'@primer/react': minor +--- + +Add ToggleSwitchProps type to package exports From 8fe405d94a1ea3415fac38ef74ce9137ea8039d7 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Thu, 4 Sep 2025 12:25:39 -0500 Subject: [PATCH 03/15] chore(lint): fix eslint error --- packages/styled-react/src/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/styled-react/src/index.tsx b/packages/styled-react/src/index.tsx index 1ece780ede2..e1a117d1405 100644 --- a/packages/styled-react/src/index.tsx +++ b/packages/styled-react/src/index.tsx @@ -36,6 +36,7 @@ type StyledProps = SxProp & const ToggleSwitch = forwardRef(function ToggleSwitch(props, ref) { // @ts-expect-error there is an issue with polymorphic `as` with this // component + // eslint-disable-next-line primer-react/no-unnecessary-components return }) as ForwardRefComponent<'span', PrimerToggleSwitchProps & Omit> From 3c52b3095e7536c51173a9e129a1cde842efe70e Mon Sep 17 00:00:00 2001 From: Josh Black Date: Thu, 4 Sep 2025 12:26:31 -0500 Subject: [PATCH 04/15] chore: disable Box eslint warning in styled-react --- eslint.config.mjs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/eslint.config.mjs b/eslint.config.mjs index 64252485dc5..af71cdc91a5 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -377,6 +377,14 @@ const config = defineConfig([ '@typescript-eslint/triple-slash-reference': 'off', }, }, + + // packages/styled-react overrides + { + files: ['packages/styled-react/**/*.{ts,tsx}'], + rules: { + 'primer-react/no-unnecessary-components': 'off', + }, + }, ]) export default tseslint.config(config) From 8e84f1f99781f12b3fea8c62596f1c15753a958c Mon Sep 17 00:00:00 2001 From: Josh Black Date: Thu, 4 Sep 2025 13:12:49 -0500 Subject: [PATCH 05/15] chore: remove eslint-disable --- packages/styled-react/src/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/styled-react/src/index.tsx b/packages/styled-react/src/index.tsx index e1a117d1405..1ece780ede2 100644 --- a/packages/styled-react/src/index.tsx +++ b/packages/styled-react/src/index.tsx @@ -36,7 +36,6 @@ type StyledProps = SxProp & const ToggleSwitch = forwardRef(function ToggleSwitch(props, ref) { // @ts-expect-error there is an issue with polymorphic `as` with this // component - // eslint-disable-next-line primer-react/no-unnecessary-components return }) as ForwardRefComponent<'span', PrimerToggleSwitchProps & Omit> From ab6d98ce57d04608f43815af2924fc2b768eb923 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Fri, 5 Sep 2025 12:38:59 -0500 Subject: [PATCH 06/15] refactor: add tests for styled-react entrypoint --- .../react/src/ProgressBar/ProgressBar.tsx | 4 +- packages/react/src/ProgressBar/index.ts | 2 +- .../react/src/ToggleSwitch/ToggleSwitch.tsx | 244 +++++----- .../__snapshots__/exports.test.ts.snap | 1 + packages/react/src/index.ts | 2 +- .../__snapshots__/exports.test.ts.snap | 5 - .../src/__tests__/styled.browser.test.tsx | 444 ++++++++++++++++++ packages/styled-react/src/index.tsx | 65 ++- .../styled-react/vitest.config.browser.ts | 12 + 9 files changed, 633 insertions(+), 146 deletions(-) create mode 100644 packages/styled-react/src/__tests__/styled.browser.test.tsx diff --git a/packages/react/src/ProgressBar/ProgressBar.tsx b/packages/react/src/ProgressBar/ProgressBar.tsx index 324aed4a078..fded3074397 100644 --- a/packages/react/src/ProgressBar/ProgressBar.tsx +++ b/packages/react/src/ProgressBar/ProgressBar.tsx @@ -15,12 +15,12 @@ type StyledProgressContainerProps = { animated?: boolean } -export type ProgressBarItems = React.HTMLAttributes & { +export type ProgressBarItemProps = React.HTMLAttributes & { 'aria-label'?: string className?: string } & ProgressProp -export const Item = forwardRef( +export const Item = forwardRef( ( { progress, diff --git a/packages/react/src/ProgressBar/index.ts b/packages/react/src/ProgressBar/index.ts index 6c0452114df..2a6a4bee81e 100644 --- a/packages/react/src/ProgressBar/index.ts +++ b/packages/react/src/ProgressBar/index.ts @@ -1,6 +1,6 @@ import {ProgressBar as Bar, Item} from './ProgressBar' -export type {ProgressBarProps} from './ProgressBar' +export type {ProgressBarProps, ProgressBarItemProps} from './ProgressBar' /** * Collection of ProgressBar related components. diff --git a/packages/react/src/ToggleSwitch/ToggleSwitch.tsx b/packages/react/src/ToggleSwitch/ToggleSwitch.tsx index 488bd625371..107b11d8c06 100644 --- a/packages/react/src/ToggleSwitch/ToggleSwitch.tsx +++ b/packages/react/src/ToggleSwitch/ToggleSwitch.tsx @@ -72,135 +72,133 @@ const LineIcon: React.FC> = ({size}) => ) -const ToggleSwitch = React.forwardRef>( - function ToggleSwitch(props, ref) { - const { - 'aria-labelledby': ariaLabelledby, - 'aria-describedby': ariaDescribedby, - defaultChecked, - disabled, - loading, - checked, - onChange, - onClick, - buttonType = 'button', - size = 'medium', - statusLabelPosition = 'start', - loadingLabelDelay = 2000, - loadingLabel = 'Loading', - className, - ...rest - } = props - const isControlled = typeof checked !== 'undefined' - const [isOn, setIsOn] = useProvidedStateOrCreate(checked, onChange, Boolean(defaultChecked)) - const acceptsInteraction = !disabled && !loading - - const [isLoadingLabelVisible, setIsLoadingLabelVisible] = React.useState(false) - const loadingLabelId = useId('loadingLabel') - - const {safeSetTimeout} = useSafeTimeout() - - const handleToggleClick: MouseEventHandler = useCallback( - e => { - if (disabled || loading) return - - if (!isControlled) { - setIsOn(!isOn) - } - onClick && onClick(e) - }, - [disabled, isControlled, loading, onClick, setIsOn, isOn], - ) - - useEffect(() => { - if (onChange && isControlled && !disabled) { - onChange(Boolean(checked)) +const ToggleSwitch = React.forwardRef(function ToggleSwitch(props, ref) { + const { + 'aria-labelledby': ariaLabelledby, + 'aria-describedby': ariaDescribedby, + defaultChecked, + disabled, + loading, + checked, + onChange, + onClick, + buttonType = 'button', + size = 'medium', + statusLabelPosition = 'start', + loadingLabelDelay = 2000, + loadingLabel = 'Loading', + className, + ...rest + } = props + const isControlled = typeof checked !== 'undefined' + const [isOn, setIsOn] = useProvidedStateOrCreate(checked, onChange, Boolean(defaultChecked)) + const acceptsInteraction = !disabled && !loading + + const [isLoadingLabelVisible, setIsLoadingLabelVisible] = React.useState(false) + const loadingLabelId = useId('loadingLabel') + + const {safeSetTimeout} = useSafeTimeout() + + const handleToggleClick: MouseEventHandler = useCallback( + e => { + if (disabled || loading) return + + if (!isControlled) { + setIsOn(!isOn) } - }, [onChange, checked, isControlled, disabled]) - - useEffect(() => { - if (!loading && isLoadingLabelVisible) { - setIsLoadingLabelVisible(false) - } else if (loading && !isLoadingLabelVisible) { - safeSetTimeout(() => { - setIsLoadingLabelVisible(true) - }, loadingLabelDelay) - } - }, [loading, isLoadingLabelVisible, loadingLabelDelay, safeSetTimeout]) - - let switchButtonDescribedBy = loadingLabelId - if (ariaDescribedby) switchButtonDescribedBy = `${switchButtonDescribedBy} ${ariaDescribedby}` - - return ( -

- - - {isLoadingLabelVisible && loadingLabel} - - - - {loading ? ( -
- -
- ) : null} - -
+
+ + ) +}) if (__DEV__) { ToggleSwitch.displayName = 'ToggleSwitch' diff --git a/packages/react/src/__tests__/__snapshots__/exports.test.ts.snap b/packages/react/src/__tests__/__snapshots__/exports.test.ts.snap index 8c70316ab29..3250e135574 100644 --- a/packages/react/src/__tests__/__snapshots__/exports.test.ts.snap +++ b/packages/react/src/__tests__/__snapshots__/exports.test.ts.snap @@ -118,6 +118,7 @@ exports[`@primer/react > should not update exports without a semver change 1`] = "Portal", "type PortalProps", "ProgressBar", + "type ProgressBarItemProps", "type ProgressBarProps", "Radio", "RadioGroup", diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 7ff72480dc0..a811e24534f 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -127,7 +127,7 @@ export type {PopoverProps, PopoverContentProps} from './Popover' export {default as Portal, registerPortalRoot} from './Portal' export type {PortalProps} from './Portal' export {ProgressBar} from './ProgressBar' -export type {ProgressBarProps} from './ProgressBar' +export type {ProgressBarProps, ProgressBarItemProps} from './ProgressBar' export {default as RadioGroup} from './RadioGroup' export type {RelativeTimeProps} from './RelativeTime' export {default as RelativeTime} from './RelativeTime' diff --git a/packages/styled-react/src/__tests__/__snapshots__/exports.test.ts.snap b/packages/styled-react/src/__tests__/__snapshots__/exports.test.ts.snap index c8204ea3c48..7a90a77e924 100644 --- a/packages/styled-react/src/__tests__/__snapshots__/exports.test.ts.snap +++ b/packages/styled-react/src/__tests__/__snapshots__/exports.test.ts.snap @@ -22,7 +22,6 @@ exports[`@primer/styled-react exports 1`] = ` "Heading", "IconButton", "Label", - "LabelGroup", "Link", "LinkButton", "merge", @@ -36,17 +35,13 @@ exports[`@primer/styled-react exports 1`] = ` "RelativeTime", "SegmentedControl", "Select", - "SelectPanel", - "SideNav", "Spinner", - "Stack", "StateLabel", "SubNav", "sx", "Text", "Textarea", "TextInput", - "TextInputWithTokens", "theme", "themeGet", "ThemeProvider", diff --git a/packages/styled-react/src/__tests__/styled.browser.test.tsx b/packages/styled-react/src/__tests__/styled.browser.test.tsx new file mode 100644 index 00000000000..17b721dec22 --- /dev/null +++ b/packages/styled-react/src/__tests__/styled.browser.test.tsx @@ -0,0 +1,444 @@ +import {userEvent} from '@testing-library/user-event' +import {render, screen} from '@testing-library/react' +import {createRef} from 'react' +import {describe, expect, test} from 'vitest' +import { + ActionList, + ActionMenu, + Autocomplete, + Avatar, + Box, + BranchName, + Breadcrumbs, + Button, + Checkbox, + CheckboxGroup, + CircleBadge, + CounterLabel, + Details, + Dialog, + Flash, + FormControl, + Header, + Heading, + IconButton, + Label, + Link, + LinkButton, + NavList, + Overlay, + PageHeader, + PageLayout, + Popover, + ProgressBar, + RadioGroup, + RelativeTime, + SegmentedControl, + Select, + Spinner, + StateLabel, + SubNav, + Text, + TextInput, + Textarea, + ThemeProvider, + Timeline, + Token, + Tooltip, + Truncate, + UnderlineNav, +} from '../' + +describe('@primer/react', () => { + test('ActionList supports `sx` prop', () => { + render() + expect(window.getComputedStyle(screen.getByTestId('component')).backgroundColor).toBe('rgb(255, 0, 0)') + }) + + test('ActionMenu.Button supports `sx` prop', () => { + const {container} = render(test) + expect(window.getComputedStyle(container.firstElementChild!).backgroundColor).toBe('rgb(255, 0, 0)') + }) + + test('ActionMenu.Overlay supports `sx` prop', async () => { + const user = userEvent.setup() + render( + + + test + + test + + + , + ) + + await user.click(screen.getByText('test')) + + expect(window.getComputedStyle(screen.getByTestId('component')).backgroundColor).toBe('rgb(255, 0, 0)') + }) + + test('Autocomplete.Input supports `sx` prop', () => { + const {container} = render( + + + , + ) + expect(window.getComputedStyle(container.firstElementChild!).backgroundColor).toBe('rgb(255, 0, 0)') + }) + + test('Autocomplete.Overlay supports `sx` prop', async () => { + const user = userEvent.setup() + + render( + + + + + test + + + , + ) + + await user.click(screen.getByRole('combobox')) + await user.keyboard('a') + + expect(window.getComputedStyle(screen.getByTestId('component')).backgroundColor).toBe('rgb(255, 0, 0)') + }) + + test('Avatar supports `sx` prop', () => { + render() + expect(window.getComputedStyle(screen.getByTestId('component')).backgroundColor).toBe('rgb(255, 0, 0)') + }) + + test('Box supports `sx` prop', () => { + render() + expect(window.getComputedStyle(screen.getByTestId('component')).backgroundColor).toBe('rgb(255, 0, 0)') + }) + + test('BranchName supports `sx` prop', () => { + render() + expect(window.getComputedStyle(screen.getByTestId('component')).backgroundColor).toBe('rgb(255, 0, 0)') + }) + + test('Breadcrumbs supports `sx` prop', () => { + render() + expect(window.getComputedStyle(screen.getByLabelText('Breadcrumbs')).backgroundColor).toBe('rgb(255, 0, 0)') + }) + + test('Breadcrumbs.Item supports `sx` prop', () => { + render() + expect(window.getComputedStyle(screen.getByTestId('component')).backgroundColor).toBe('rgb(255, 0, 0)') + }) + + test('Button supports `sx` prop', () => { + render(