Use J and K keys to move focus. @@ -258,7 +286,11 @@ export const OverlayPropsOverrides = () => { overlayProps={{ overflow: 'auto', maxHeight: 'xsmall', + role: 'dialog', + 'aria-modal': true, + 'aria-label': 'User Card Overlay', }} + focusZoneSettings={{disabled: true}} >
diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.stories.tsx b/packages/react/src/AnchoredOverlay/AnchoredOverlay.stories.tsx index 5a7dcbf50da..84ec0727c24 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.stories.tsx +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.stories.tsx @@ -2,7 +2,7 @@ import React, {useState} from 'react' import type {Args, Meta} from '@storybook/react' import {LocationIcon, RepoIcon} from '@primer/octicons-react' -import {Avatar, Text} from '..' +import {Avatar, Link, Text} from '..' import {AnchoredOverlay} from '../AnchoredOverlay' import {Button} from '../Button' import Octicon from '../Octicon' @@ -23,7 +23,9 @@ const hoverCard = (monalisa - Monalisa Octocat + + Monalisa Octocat + Former beach cat and champion swimmer. Now your friendly octapus with a normal face. @@ -51,6 +53,8 @@ export const Default = () => { onOpen={() => setOpen(true)} onClose={() => setOpen(false)} renderAnchor={props => } + overlayProps={{role: 'dialog', 'aria-modal': true, 'aria-label': 'User Card Overlay'}} + focusZoneSettings={{disabled: true}} > {hoverCard} @@ -74,8 +78,14 @@ export const Playground = (args: Args) => { width={args.width} height={args.height} renderAnchor={props => } - overlayProps={args.portalContainerName} + overlayProps={{ + ...args.portalContainerName, + role: 'dialog', + 'aria-modal': true, + 'aria-label': 'User Card Overlay', + }} side={args.side} + focusZoneSettings={{disabled: true}} > {hoverCard} diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx index 88260c2c8a2..bfc08cf041a 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx @@ -198,6 +198,7 @@ export const AnchoredOverlay: React.FC{children} diff --git a/packages/react/src/Banner/Banner.tsx b/packages/react/src/Banner/Banner.tsx index f33021d67d7..c61f130fd2e 100644 --- a/packages/react/src/Banner/Banner.tsx +++ b/packages/react/src/Banner/Banner.tsx @@ -88,7 +88,7 @@ const labels: Record = { warning: 'Warning', } -const CSS_MODULES_FEATURE_FLAG = 'primer_react_css_modules_staff' +const CSS_MODULES_FEATURE_FLAG = 'primer_react_css_modules_ga' export const Banner = React.forwardRef (function Banner( { diff --git a/packages/react/src/Blankslate/Blankslate.module.css b/packages/react/src/Blankslate/Blankslate.module.css index 05fa2c0a442..5f0029cbcfd 100644 --- a/packages/react/src/Blankslate/Blankslate.module.css +++ b/packages/react/src/Blankslate/Blankslate.module.css @@ -31,6 +31,7 @@ .Description { margin: 0; margin-bottom: var(--base-size-8); + text-align: center; } .Heading { diff --git a/packages/react/src/BranchName/BranchName.module.css b/packages/react/src/BranchName/BranchName.module.css new file mode 100644 index 00000000000..66e9abc2d50 --- /dev/null +++ b/packages/react/src/BranchName/BranchName.module.css @@ -0,0 +1,14 @@ +.BranchName { + display: inline-block; + padding: var(--base-size-2) var(--base-size-6); + font-family: var(--fontStack-monospace); + font-size: var(--text-body-size-small); + color: var(--fgColor-link); + text-decoration: none; + background-color: var(--bgColor-accent-muted); + border-radius: var(--borderRadius-medium); + + &:is(:not(a)) { + color: var(--fgColor-muted); + } +} diff --git a/packages/react/src/BranchName/BranchName.tsx b/packages/react/src/BranchName/BranchName.tsx index 010777c4a2a..4b6d7697c9f 100644 --- a/packages/react/src/BranchName/BranchName.tsx +++ b/packages/react/src/BranchName/BranchName.tsx @@ -1,10 +1,14 @@ +import React, {type ForwardedRef} from 'react' +import {clsx} from 'clsx' import styled from 'styled-components' import {get} from '../constants' import type {SxProp} from '../sx' import sx from '../sx' -import type {ComponentProps} from '../utils/types' +import {useFeatureFlag} from '../FeatureFlags' +import Box from '../Box' +import classes from './BranchName.module.css' -const BranchName = styled.a ` +const StyledBranchName = styled.a ` display: inline-block; padding: 2px 6px; font-size: var(--text-body-size-small, ${get('fontSizes.0')}); @@ -19,5 +23,50 @@ const BranchName = styled.a ` ${sx}; ` -export type BranchNameProps = ComponentProps -export default BranchName +type BranchNameProps = { + as?: As +} & DistributiveOmit , 'as'> & + SxProp + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function BranchName (props: BranchNameProps , ref: ForwardedRef ) { + const {as: BaseComponent = 'a', className, children, sx, ...rest} = props + const enabled = useFeatureFlag('primer_react_css_modules_team') + + if (enabled) { + if (sx) { + return ( + + {children} + + ) + } + + return ( ++ {children} + + ) + } + + return ( ++ {children} + + ) +} + +// eslint-disable-next-line @typescript-eslint/ban-types +type FixedForwardRef =( + render: (props: P, ref: React.Ref ) => React.ReactNode, +) => (props: P & React.RefAttributes ) => React.ReactNode + +const fixedForwardRef = React.forwardRef as FixedForwardRef + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type DistributiveOmit = T extends any ? Omit : never + +BranchName.displayName = 'BranchName' + +export type {BranchNameProps} +export default fixedForwardRef(BranchName) diff --git a/packages/react/src/BranchName/__tests__/BranchName.test.tsx b/packages/react/src/BranchName/__tests__/BranchName.test.tsx index 9b533e2ed34..4a9445e1421 100644 --- a/packages/react/src/BranchName/__tests__/BranchName.test.tsx +++ b/packages/react/src/BranchName/__tests__/BranchName.test.tsx @@ -3,9 +3,15 @@ import BranchName from '../BranchName' import {render, behavesAsComponent, checkExports} from '../../utils/testing' import {render as HTMLRender} from '@testing-library/react' import axe from 'axe-core' +import {FeatureFlags} from '../../FeatureFlags' describe('BranchName', () => { - behavesAsComponent({Component: BranchName}) + behavesAsComponent({ + Component: BranchName, + options: { + skipDisplayName: true, + }, + }) checkExports('BranchName', { default: BranchName, @@ -20,4 +26,23 @@ describe('BranchName', () => { it('renders an by default', () => { expect(render( ).type).toEqual('a') }) + + it('should support `className` on the outermost element', () => { + const Element = () => + const FeatureFlagElement = () => { + return ( + + + ) + } + expect(HTMLRender(+ ).container.firstChild).toHaveClass('test-class-name') + expect(HTMLRender( ).container.firstChild).toHaveClass('test-class-name') + }) }) diff --git a/packages/react/src/BranchName/__tests__/BranchName.types.test.tsx b/packages/react/src/BranchName/__tests__/BranchName.types.test.tsx index 35330c2da72..4b7db3d4ba7 100644 --- a/packages/react/src/BranchName/__tests__/BranchName.types.test.tsx +++ b/packages/react/src/BranchName/__tests__/BranchName.types.test.tsx @@ -9,3 +9,43 @@ export function shouldNotAcceptSystemProps() { // @ts-expect-error system props should not be accepted return } + +export function shouldAcceptAs() { + return ( + { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + type test = Expect >> + }} + /> + ) +} + +export function defaultAsIsAnchor() { + return ( + { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + type test = Expect >> + }} + /> + ) +} + +export function ShouldAcceptRef() { + const ref = React.useRef (null) + return ( + { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + type test = Expect >> + }} + /> + ) +} + +type Expect = T +type Equal = ( () => T extends X ? 1 : 2) extends () => T extends Y ? 1 : 2 ? true : false diff --git a/packages/react/src/Button/ButtonBase.tsx b/packages/react/src/Button/ButtonBase.tsx index dc4ffdcfad1..752525b1ad9 100644 --- a/packages/react/src/Button/ButtonBase.tsx +++ b/packages/react/src/Button/ButtonBase.tsx @@ -118,7 +118,7 @@ const ButtonBase = forwardRef( > { @@ -314,8 +314,13 @@ const ButtonBase = forwardRef( true, ) : TrailingVisual - ? renderModuleVisual(TrailingVisual, Boolean(loading) && !LeadingVisual, 'trailingVisual', false) - : null + ? renderModuleVisual( + TrailingVisual, + Boolean(loading) && !LeadingVisual, + 'trailingVisual', + false, + ) + : null } { @@ -420,8 +425,8 @@ const ButtonBase = forwardRef( 'trailingVisual', ) : TrailingVisual - ? renderVisual(TrailingVisual, Boolean(loading) && !LeadingVisual, 'trailingVisual') - : null + ? renderVisual(TrailingVisual, Boolean(loading) && !LeadingVisual, 'trailingVisual') + : null } { diff --git a/packages/react/src/ButtonGroup/ButtonGroup.tsx b/packages/react/src/ButtonGroup/ButtonGroup.tsx index e3398885b3f..74f9ff32e15 100644 --- a/packages/react/src/ButtonGroup/ButtonGroup.tsx +++ b/packages/react/src/ButtonGroup/ButtonGroup.tsx @@ -9,7 +9,7 @@ import {clsx} from 'clsx' import {useFeatureFlag} from '../FeatureFlags' const StyledButtonGroup = toggleStyledComponent( - 'primer_react_css_modules_team', + 'primer_react_css_modules_staff', 'div', styled.div` display: inline-flex; @@ -78,7 +78,7 @@ const ButtonGroup = React.forwardRef(function But {children, className, ...rest}, forwardRef, ) { - const enabled = useFeatureFlag('primer_react_css_modules_team') + const enabled = useFeatureFlag('primer_react_css_modules_staff') return ( ( ) -export const Playground = (args: DataTableProps & ColWidthArgTypes) => { - const getColWidth = (colIndex: number) => { - return args[`colWidth${colIndex}`] !== 'explicit width' - ? args[`colWidth${colIndex}`] - : args[`explicitColWidth${colIndex}`] - ? args[`explicitColWidth${colIndex}`] - : 'grow' - } +export const Playground: StoryObj & ColWidthArgTypes> = { + render: (args: DataTableProps & ColWidthArgTypes) => { + const getColWidth = (colIndex: number) => { + return args[`colWidth${colIndex}`] !== 'explicit width' + ? args[`colWidth${colIndex}`] + : args[`explicitColWidth${colIndex}`] + ? args[`explicitColWidth${colIndex}`] + : 'grow' + } - const align = args.align as CellAlignment + const align = args.align as CellAlignment - const [pageIndex, setPageIndex] = React.useState(0) - const start = pageIndex * parseInt(args.pageSize, 10) - const end = start + parseInt(args.pageSize, 10) - const rows = data.slice(start, end) + const [pageIndex, setPageIndex] = React.useState(0) + const start = pageIndex * parseInt(args.pageSize, 10) + const end = start + parseInt(args.pageSize, 10) + const rows = data.slice(start, end) - return ( - - + ) }, - 'aria-describedby': { - control: false, - table: { - disable: true, - }, + args: { + cellPadding: 'normal', + // @ts-expect-error it seems like args is not being correctly inferred + pageSize: 5, }, - 'aria-labelledby': { - control: false, - table: { - disable: true, + // @ts-expect-error it seems like arg types with column helpers are not working as intended + argTypes: { + align: { + control: { + type: 'radio', + }, + type: { + name: 'enum', + value: ['start', 'end'], + }, }, - }, - columns: { - control: false, - table: { - disable: true, + 'aria-describedby': { + control: false, + table: { + disable: true, + }, }, - }, - data: { - control: false, - table: { - disable: true, + 'aria-labelledby': { + control: false, + table: { + disable: true, + }, }, - }, - pageSize: { - control: { - defaultValue: 5, - type: 'number', - min: 1, + columns: { + control: false, + table: { + disable: true, + }, }, - }, - defaultPageIndex: { - control: { - type: 'number', + data: { + control: false, + table: { + disable: true, + }, }, - }, - cellPadding: { - control: { - type: 'radio', + pageSize: { + control: { + defaultValue: 5, + type: 'number', + min: 1, + }, + }, + defaultPageIndex: { + control: { + type: 'number', + }, }, - type: { - name: 'enum', - value: ['condensed', 'normal', 'spacious'], + cellPadding: { + control: { + type: 'radio', + }, + type: { + name: 'enum', + value: ['condensed', 'normal', 'spacious'], + }, }, + ...getColumnWidthArgTypes(5), }, - ...getColumnWidthArgTypes(5), } diff --git a/packages/react/src/DataTable/Pagination.tsx b/packages/react/src/DataTable/Pagination.tsx index 7a3bfdfea70..42ee2a1fccb 100644 --- a/packages/react/src/DataTable/Pagination.tsx +++ b/packages/react/src/DataTable/Pagination.tsx @@ -373,7 +373,7 @@ function Range({pageStart, pageEnd, totalCount}: RangeProps) {- Repositories - -- A subtitle could appear here to give extra context to the data. - -{ - return + return ( + + - ) -} - -Playground.args = { - cellPadding: 'normal', - pageSize: 5, -} - -Playground.argTypes = { - align: { - control: { - type: 'radio', - }, - type: { - name: 'enum', - value: ['start', 'end'], - }, + { + header: 'Code scanning', + field: 'securityFeatures.codeScanning', + renderCell: row => { + return row.securityFeatures.codeScanning.length > 0 ? ( ++ Repositories + ++ A subtitle could appear here to give extra context to the data. + +{ - return + { + header: 'Type', + field: 'type', + renderCell: row => { + return + }, + width: getColWidth(1), + minWidth: args.minColWidth1, + maxWidth: args.maxColWidth1, + align, }, - width: getColWidth(2), - minWidth: args.minColWidth2, - maxWidth: args.maxColWidth2, - align, - }, - { - header: 'Dependabot', - field: 'securityFeatures.dependabot', - renderCell: row => { - return row.securityFeatures.dependabot.length > 0 ? ( - - {row.securityFeatures.dependabot.map(feature => { - return - })} - - ) : null + { + header: 'Updated', + field: 'updatedAt', + renderCell: row => { + return+ }, + width: getColWidth(2), + minWidth: args.minColWidth2, + maxWidth: args.maxColWidth2, + align, }, - width: getColWidth(3), - minWidth: args.minColWidth3, - maxWidth: args.maxColWidth3, - align, - }, - { - header: 'Code scanning', - field: 'securityFeatures.codeScanning', - renderCell: row => { - return row.securityFeatures.codeScanning.length > 0 ? ( - - {row.securityFeatures.codeScanning.map(feature => { - return - })} - - ) : null + { + header: 'Dependabot', + field: 'securityFeatures.dependabot', + renderCell: row => { + return row.securityFeatures.dependabot.length > 0 ? ( ++ {row.securityFeatures.dependabot.map(feature => { + return + })} + + ) : null + }, + width: getColWidth(3), + minWidth: args.minColWidth3, + maxWidth: args.maxColWidth3, + align, }, - width: getColWidth(4), - minWidth: args.minColWidth4, - maxWidth: args.maxColWidth4, - align, - }, - ]} - /> -{ - setPageIndex(pageIndex) - }} - defaultPageIndex={parseInt(args.defaultPageIndex, 10)} - /> - + {row.securityFeatures.codeScanning.map(feature => { + return + })} + + ) : null + }, + width: getColWidth(4), + minWidth: args.minColWidth4, + maxWidth: args.maxColWidth4, + align, + }, + ]} + /> +{ + setPageIndex(pageIndex) + }} + defaultPageIndex={parseInt(args.defaultPageIndex, 10)} + /> + {start} -
diff --git a/packages/react/src/DataTable/useTable.ts b/packages/react/src/DataTable/useTable.ts index c5a9140744c..28fd042b2a4 100644 --- a/packages/react/src/DataTable/useTable.ts +++ b/packages/react/src/DataTable/useTable.ts @@ -135,8 +135,8 @@ export function useTable({ header.column.sortBy === true ? strategies.basic : typeof header.column.sortBy === 'string' - ? strategies[header.column.sortBy] - : header.column.sortBy + ? strategies[header.column.sortBy] + : header.column.sortBy setRowOrder(rowOrder => { return rowOrder.slice().sort((a, b) => { diff --git a/packages/react/src/DataTable/utils.ts b/packages/react/src/DataTable/utils.ts index e28c1d362aa..779a4598468 100644 --- a/packages/react/src/DataTable/utils.ts +++ b/packages/react/src/DataTable/utils.ts @@ -12,9 +12,9 @@ type MaxLength = ArrayOfLength<10>[number] type ArrayIndex, Keys extends number = never> = A extends readonly [] ? Keys : // eslint-disable-next-line @typescript-eslint/no-unused-vars - A extends readonly [infer _, ...infer Tail] - ? ArrayIndexthrough +through {end} of {totalCount}- : Keys + A extends readonly [infer _, ...infer Tail] + ? ArrayIndex + : Keys // Check if the given type is within the bounds set by `MaxLength` // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -30,25 +30,25 @@ type ArrayWithinBounds = T extends ReadonlyArray & {length: infer Length export type ObjectPaths = T extends readonly any[] & ArrayWithinBounds ? `${ArrayIndex }` | PrefixPath > : // eslint-disable-next-line @typescript-eslint/no-explicit-any - T extends any[] - ? never & 'Unable to determine keys of potentially boundless array' - : T extends Date - ? never - : T extends object - ? Extract | PrefixPath > - : never + T extends any[] + ? never & 'Unable to determine keys of potentially boundless array' + : T extends Date + ? never + : T extends object + ? Extract | PrefixPath > + : never -type PrefixPath = Prefix extends Extract - ? `${Prefix}.${ObjectPaths }` - : never +type PrefixPath = + Prefix extends Extract ? `${Prefix}.${ObjectPaths }` : never // Get the value of a given path within an object -export type ObjectPathValue = ObjectType extends Record< - string | number, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - any -> - ? Path extends `${infer Key}.${infer NestedPath}` - ? ObjectPathValue - : ObjectType[Path] - : never +export type ObjectPathValue = + ObjectType extends Record< + string | number, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + any + > + ? Path extends `${infer Key}.${infer NestedPath}` + ? ObjectPathValue + : ObjectType[Path] + : never diff --git a/packages/react/src/Details/Details.docs.json b/packages/react/src/Details/Details.docs.json index 51e5275b585..0dff5c8d6da 100644 --- a/packages/react/src/Details/Details.docs.json +++ b/packages/react/src/Details/Details.docs.json @@ -11,5 +11,26 @@ "type": "SystemStyleObject" } ], - "subcomponents": [] + "subcomponents": [ + { + "name": "Details.Summary", + "props": [ + { + "name": "as", + "type": "React.ElementType >", + "defaultValue": "summary", + "required": false, + "description": "HTML element to render summary as." + }, + { + "name": "children", + "type": "React.ReactNode" + }, + { + "name": "sx", + "type": "SystemStyleObject" + } + ] + } + ] } diff --git a/packages/react/src/Details/Details.stories.tsx b/packages/react/src/Details/Details.stories.tsx index cdebdccce97..799fee5ac5e 100644 --- a/packages/react/src/Details/Details.stories.tsx +++ b/packages/react/src/Details/Details.stories.tsx @@ -12,7 +12,7 @@ export const Default: StoryFn = () => { const {getDetailsProps} = useDetails({closeOnOutsideClick: true}) return ( - +) diff --git a/packages/react/src/Details/Details.tsx b/packages/react/src/Details/Details.tsx index d90f76a7e2a..927edb6091f 100644 --- a/packages/react/src/Details/Details.tsx +++ b/packages/react/src/Details/Details.tsx @@ -1,4 +1,4 @@ -import React, {type ComponentPropsWithoutRef, type ReactElement} from 'react' +import React, {useEffect, useState, type ComponentPropsWithoutRef, type ReactElement} from 'react' import styled from 'styled-components' import type {SxProp} from '../sx' import sx from '../sx' @@ -6,6 +6,7 @@ import {toggleStyledComponent} from '../internal/utils/toggleStyledComponent' import {useFeatureFlag} from '../FeatureFlags' import {clsx} from 'clsx' import classes from './Details.module.css' +import {useMergedRefs} from '../internal/hooks/useMergedRefs' const CSS_MODULES_FEATURE_FLAG = 'primer_react_css_modules_team' @@ -24,18 +25,76 @@ const StyledDetails = toggleStyledComponent( `, ) -const Details = React.forwardRefSee Details This is some content( - ({className, children, ...rest}, ref): ReactElement => { +const Root = React.forwardRef ( + ({className, children, ...rest}, forwardRef): ReactElement => { const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) + const detailsRef = React.useRef (null) + const ref = useMergedRefs(forwardRef, detailsRef) + const [hasSummary, setHasSummary] = useState(false) + + useEffect(() => { + const {current: details} = detailsRef + if (!details) { + return + } + + const updateSummary = () => { + const summary = details.querySelector('summary:not([data-default-summary])') + setHasSummary(!!summary) + } + + // Update summary on mount + updateSummary() + + const observer = new MutationObserver(() => { + updateSummary() + }) + + observer.observe(details, { + childList: true, + subtree: true, + }) + + return () => { + observer.disconnect() + } + }, []) + return ( + {/* Include default summary if summary is not provided */} + {!hasSummary && ) }, ) -Details.displayName = 'Details' +Root.displayName = 'Details' + +export type SummaryProps{'See Details'} } {children}= { + /** + * HTML element to render summary as. + */ + as?: As + children?: React.ReactNode +} & React.ComponentPropsWithoutRef + +function Summary ({as, children, ...props}: SummaryProps ) { + const Component = as ?? 'summary' + return ( + + {children} + + ) +} +Summary.displayName = 'Summary' + +export {Summary} + +const Details = Object.assign(Root, { + Summary, +}) export type DetailsProps = ComponentPropsWithoutRef<'details'> & SxProp export default Details diff --git a/packages/react/src/Details/__tests__/Details.test.tsx b/packages/react/src/Details/__tests__/Details.test.tsx index bac07624624..28be85e0945 100644 --- a/packages/react/src/Details/__tests__/Details.test.tsx +++ b/packages/react/src/Details/__tests__/Details.test.tsx @@ -1,6 +1,6 @@ -import {render} from '@testing-library/react' +import {render, screen} from '@testing-library/react' import userEvent from '@testing-library/user-event' -import React from 'react' +import React, {act} from 'react' import {Details, useDetails, Box, Button} from '../..' import type {ButtonProps} from '../../Button' import {behavesAsComponent, checkExports} from '../../utils/testing' @@ -14,29 +14,36 @@ describe('Details', () => { }) it('should have no axe violations', async () => { - const {container} = render() - const results = await axe.run(container) + const {container} = render( ++, + ) + let results + await act(async () => { + results = await axe.run(container) + }) expect(results).toHaveNoViolations() }) - it('Toggles when you click outside', () => { + it('Toggles when you click outside', async () => { const Component = () => { const {getDetailsProps} = useDetails({closeOnOutsideClick: true}) return (Summary Content +-) } - const {getByTestId} = render(hi
+hi ) + const {findByTestId} = render( ) document.body.click() - expect(getByTestId('details')).not.toHaveAttribute('open') + expect(await findByTestId('details')).not.toHaveAttribute('open') }) - it('Accurately passes down open state', () => { + it('Accurately passes down open state', async () => { const Component = () => { const {getDetailsProps, open} = useDetails({closeOnOutsideClick: true}) return ( @@ -46,12 +53,12 @@ describe('Details', () => { ) } - const {getByTestId} = render( ) + const {findByTestId} = render( ) document.body.click() - expect(getByTestId('summary')).toHaveTextContent('Closed') - expect(getByTestId('details')).not.toHaveAttribute('open') + expect(await findByTestId('summary')).toHaveTextContent('Closed') + expect(await findByTestId('details')).not.toHaveAttribute('open') }) it('Can manipulate state with setOpen', async () => { @@ -95,4 +102,53 @@ describe('Details', () => { expect(getByTestId('summary')).toHaveTextContent('Open') }) + + it('Adds default summary if no summary supplied', async () => { + const {getByText} = render( content) + + expect(getByText('See Details')).toBeInTheDocument() + expect(getByText('See Details').tagName).toBe('SUMMARY') + }) + + it('Does not add default summary if summary supplied', async () => { + const {findByTestId, findByText} = render( ++, + ) + + await expect(findByText('See Details')).rejects.toThrow() + expect(await findByTestId('summary')).toBeInTheDocument() + expect((await findByTestId('summary')).tagName).toBe('SUMMARY') + }) + + it('Does not add default summary if supplied as different element', async () => { + const {findByTestId, findByText} = render( +summary + content ++, + ) + + await expect(findByText('See Details')).rejects.toThrow() + expect(await findByTestId('summary')).toBeInTheDocument() + expect((await findByTestId('summary')).tagName).toBe('SUMMARY') + }) + + describe('Details.Summary', () => { + behavesAsComponent({Component: Details.Summary, options: {skipSx: true}}) + + it('should support a custom `className` on the container element', () => { + render(+ custom summary + + content +test summary ) + expect(screen.getByText('test summary')).toHaveClass('custom-class') + }) + + it('should pass extra props onto the container element', () => { + render(test summary ) + expect(screen.getByText('test summary')).toHaveAttribute('data-testid', 'test') + }) + }) }) diff --git a/packages/react/src/FeatureFlags/DefaultFeatureFlags.ts b/packages/react/src/FeatureFlags/DefaultFeatureFlags.ts index 3ab756ea60f..b10d4e35447 100644 --- a/packages/react/src/FeatureFlags/DefaultFeatureFlags.ts +++ b/packages/react/src/FeatureFlags/DefaultFeatureFlags.ts @@ -6,4 +6,5 @@ export const DefaultFeatureFlags = FeatureFlagScope.create({ primer_react_css_modules_ga: false, primer_react_action_list_item_as_button: false, primer_react_select_panel_with_modern_action_list: false, + primer_react_overlay_overflow: false, }) diff --git a/packages/react/src/FormControl/FormControl.features.stories.tsx b/packages/react/src/FormControl/FormControl.features.stories.tsx index ac040fb057c..d1e7a284ef4 100644 --- a/packages/react/src/FormControl/FormControl.features.stories.tsx +++ b/packages/react/src/FormControl/FormControl.features.stories.tsx @@ -4,12 +4,14 @@ import { Autocomplete, BaseStyles, Box, + Button, Checkbox, CheckboxGroup, FormControl, Radio, RadioGroup, Select, + SelectPanel, Text, TextInput, TextInputWithTokens, @@ -17,7 +19,8 @@ import { ThemeProvider, theme, } from '..' -import {MarkGithubIcon} from '@primer/octicons-react' +import {MarkGithubIcon, TriangleDownIcon} from '@primer/octicons-react' +import type {ItemInput} from '../deprecated/ActionList/List' export default { title: 'Components/FormControl/Features', @@ -273,6 +276,68 @@ export const ValidationExample = () => { ) } +function getColorCircle(color: string) { + return function () { + return ( ++ ) + } +} + +const items: ItemInput[] = [ + {leadingVisual: getColorCircle('#a2eeef'), text: 'enhancement', description: 'New feature or request', id: 1}, + {leadingVisual: getColorCircle('#d73a4a'), text: 'bug', description: "Something isn't working", id: 2}, + {leadingVisual: getColorCircle('#0cf478'), text: 'good first issue', description: 'Good for newcomers', id: 3}, + {leadingVisual: getColorCircle('#ffd78e'), text: 'design', id: 4}, + {leadingVisual: getColorCircle('#ff0000'), text: 'blocker', id: 5}, + {leadingVisual: getColorCircle('#a4f287'), text: 'backend', id: 6}, + {leadingVisual: getColorCircle('#8dc6fc'), text: 'frontend', id: 7}, +].map(item => ({...item, descriptionVariant: 'block'})) + +export const WithSelectPanel = () => { + const [selected, setSelected] = React.useState ([items[0], items[1]]) + const [filter, setFilter] = React.useState('') + const filteredItems = items.filter(item => item.text?.toLowerCase().startsWith(filter.toLowerCase())) + const [open, setOpen] = useState(false) + + return ( + + + ) +} + export const WithLeadingVisual = () => (Select Labels +( + + )} + open={open} + onOpenChange={setOpen} + items={filteredItems} + selected={selected} + onSelectedChange={setSelected} + onFilterChange={setFilter} + /> + ) + +export const WithCaption = () => ( + @@ -342,3 +407,49 @@ export const CustomRequired = () => ( + +) + +export const WithCaptionAndDisabled = () => ( +Example label ++ Example caption ++ +) + +export const WithHiddenLabel = () => ( +Example label ++ Example caption ++ +) + +export const WithRequiredIndicator = () => ( +Example label ++ + +) + +export const WithSuccessValidation = () => ( +Example label ++ + +) + +export const WithErrorValidation = () => ( +Example label ++ Example success validation message ++ +) diff --git a/packages/react/src/FormControl/FormControl.stories.tsx b/packages/react/src/FormControl/FormControl.stories.tsx index a8d30f8aa16..602d581fae4 100644 --- a/packages/react/src/FormControl/FormControl.stories.tsx +++ b/packages/react/src/FormControl/FormControl.stories.tsx @@ -1,21 +1,10 @@ import React, {useState} from 'react' import type {Meta} from '@storybook/react' -import {BaseStyles, Box, Checkbox, FormControl, TextInput, TextInputWithTokens, ThemeProvider, theme} from '..' +import {Box, Checkbox, FormControl, TextInput, TextInputWithTokens} from '..' import type {FormValidationStatus} from '../utils/types/FormValidationStatus' export default { title: 'Components/FormControl', - decorators: [ - Story => { - return ( -Example label ++ Example error validation message +- - ) - }, - ], argTypes: { disabled: { type: 'boolean', diff --git a/packages/react/src/FormControl/FormControl.tsx b/packages/react/src/FormControl/FormControl.tsx index 257163c753a..f2eb061b594 100644 --- a/packages/react/src/FormControl/FormControl.tsx +++ b/packages/react/src/FormControl/FormControl.tsx @@ -3,7 +3,8 @@ import Autocomplete from '../Autocomplete' import Box from '../Box' import Checkbox from '../Checkbox' import Radio from '../Radio' -import Select from '../Select' +import Select from '../Select/Select' +import {SelectPanel} from '../SelectPanel' import TextInput from '../TextInput' import TextInputWithTokens from '../TextInputWithTokens' import Textarea from '../Textarea' @@ -50,7 +51,16 @@ const FormControl = React.forwardRef- -- ( leadingVisual: FormControlLeadingVisual, validation: FormControlValidation, }) - const expectedInputComponents = [Autocomplete, Checkbox, Radio, Select, TextInput, TextInputWithTokens, Textarea] + const expectedInputComponents = [ + Autocomplete, + Checkbox, + Radio, + Select, + TextInput, + TextInputWithTokens, + Textarea, + SelectPanel, + ] const choiceGroupContext = useContext(CheckboxOrRadioGroupContext) const disabled = choiceGroupContext.disabled || disabledProp const id = useId(idProp) diff --git a/packages/react/src/Header/Header.dev.module.css b/packages/react/src/Header/Header.dev.module.css new file mode 100644 index 00000000000..8d90de7725a --- /dev/null +++ b/packages/react/src/Header/Header.dev.module.css @@ -0,0 +1,11 @@ +.HeaderDev { + background-color: var(--label-olive-bgColor-active); +} + +.HeaderDevItem { + padding-left: var(--base-size-24); +} + +.HeaderDevLink { + color: var(--color-prettylights-syntax-markup-inserted-text); +} diff --git a/packages/react/src/Header/Header.dev.stories.tsx b/packages/react/src/Header/Header.dev.stories.tsx new file mode 100644 index 00000000000..66756f36e93 --- /dev/null +++ b/packages/react/src/Header/Header.dev.stories.tsx @@ -0,0 +1,76 @@ +import React from 'react' +import type {Meta} from '@storybook/react' +import {MarkGithubIcon} from '@primer/octicons-react' + +import Header from './Header' +import Avatar from '../Avatar' +import Octicon from '../Octicon' + +import classes from './Header.dev.module.css' +import {FeatureFlags} from '../FeatureFlags' + +export default { + title: 'Components/Header/Dev', + component: Header, +} as Meta + +export const WithCss = () => ( + + +) + +export const WithSx = () => ( ++ ++ ++ ++ GitHub + Menu ++ ++ + +) + +export const WithSxAndCSS = () => ( ++ ++ ++ GitHub + Menu ++ ++ + +) diff --git a/packages/react/src/Header/Header.module.css b/packages/react/src/Header/Header.module.css new file mode 100644 index 00000000000..0d8a055fea9 --- /dev/null +++ b/packages/react/src/Header/Header.module.css @@ -0,0 +1,39 @@ +.Header { + z-index: 32; + display: flex; + padding: var(--base-size-16); + overflow: auto; + font-size: var(--text-body-size-medium); + line-height: var(--text-title-lineHeight-large); + color: var(--header-fgColor-default); + background-color: var(--header-bgColor); + align-items: center; + flex-wrap: nowrap; +} + +.HeaderItem { + display: flex; + margin-right: var(--base-size-16); + align-self: stretch; + align-items: center; + flex-wrap: nowrap; + + &:where([data-full]) { + flex: auto; + } +} + +.HeaderLink { + display: flex; + font-weight: var(--text-title-weight-large); + color: var(--header-fgColor-logo); + text-decoration: none; + white-space: nowrap; + cursor: pointer; + align-items: center; + + &:hover, + &:focus { + color: var(--header-fgColor-default); + } +} diff --git a/packages/react/src/Header/Header.tsx b/packages/react/src/Header/Header.tsx index c676ccd2d4c..3c7c194ca43 100644 --- a/packages/react/src/Header/Header.tsx +++ b/packages/react/src/Header/Header.tsx @@ -4,68 +4,132 @@ import {get} from '../constants' import type {SxProp} from '../sx' import sx from '../sx' import type {ComponentProps} from '../utils/types' +import {toggleStyledComponent} from '../internal/utils/toggleStyledComponent' +import {useFeatureFlag} from '../FeatureFlags' +import React from 'react' +import {clsx} from 'clsx' +import classes from './Header.module.css' +import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic' -type StyledHeaderItemProps = {full?: boolean} & SxProp -type StyledHeaderProps = SxProp -type StyledHeaderLinkProps = {to?: Location | Pathname} & SxProp - -const Header = styled.header+ ++ ++ ++ GitHub + Menu ++ ++ ` - z-index: 32; - display: flex; - padding: ${get('space.3')}; - font-size: ${get('fontSizes.1')}; - line-height: ${get('lineHeights.default')}; - color: ${get('colors.header.text')}; - background-color: ${get('colors.header.bg')}; - align-items: center; - flex-wrap: nowrap; - overflow: auto; - - ${sx}; -` -const HeaderItem = styled.div ` - display: flex; - margin-right: ${get('space.3')}; - align-self: stretch; - align-items: center; - flex-wrap: nowrap; - - ${({full}) => - full && - css` - flex: auto; - `}; - - ${sx}; -` +type StyledHeaderProps = React.ComponentProps<'header'> & SxProp +type StyledHeaderItemProps = React.ComponentProps<'div'> & SxProp & {full?: boolean} +type StyledHeaderLinkProps = React.ComponentProps<'a'> & SxProp & {to?: Location | Pathname} -HeaderItem.displayName = 'Header.Item' +const CSS_MODULES_FEATURE_FLAG = 'primer_react_css_modules_team' -const HeaderLink = styled.a.attrs (({to}) => { - const isReactRouter = typeof to === 'string' - if (isReactRouter) { - // according to their docs, NavLink supports aria-current: - // https://reacttraining.com/react-router/web/api/NavLink/aria-current-string - return {'aria-current': 'page'} - } else { - return {} - } -}) ` - font-weight: ${get('fontWeights.bold')}; - color: ${get('colors.header.logo')}; - white-space: nowrap; - cursor: pointer; - text-decoration: none; - display: flex; - align-items: center; - - &:hover, - &:focus { +const StyledHeader = toggleStyledComponent( + CSS_MODULES_FEATURE_FLAG, + 'header', + styled.header ` + z-index: 32; + display: flex; + padding: ${get('space.3')}; + font-size: ${get('fontSizes.1')}; + line-height: ${get('lineHeights.default')}; color: ${get('colors.header.text')}; - } + background-color: ${get('colors.header.bg')}; + align-items: center; + flex-wrap: nowrap; + overflow: auto; + + ${sx}; + `, +) + +const Header = React.forwardRef (function Header( + {children, className, ...rest}, + forwardRef, +) { + const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) + return ( + + {children} + + ) +}) as PolymorphicForwardRefComponent<'header', StyledHeaderProps> + +Header.displayName = 'Header' + +const StyledHeaderItem = toggleStyledComponent( + CSS_MODULES_FEATURE_FLAG, + 'div', + styled.div` + display: flex; + margin-right: ${get('space.3')}; + align-self: stretch; + align-items: center; + flex-wrap: nowrap; + + ${({full}) => + full && + css` + flex: auto; + `}; + + ${sx}; + `, +) + +const HeaderItem = React.forwardRef (function HeaderItem( + {children, className, ...rest}, + forwardRef, +) { + const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) + return ( + + {children} + + ) +}) + +HeaderItem.displayName = 'Header.Item' + +const StyledHeaderLink = toggleStyledComponent( + CSS_MODULES_FEATURE_FLAG, + 'a', + styled.a.attrs(({to}) => { + const isReactRouter = typeof to === 'string' + if (isReactRouter) { + // according to their docs, NavLink supports aria-current: + // https://reacttraining.com/react-router/web/api/NavLink/aria-current-string + return {'aria-current': 'page'} + } else { + return {} + } + }) ` + font-weight: ${get('fontWeights.bold')}; + color: ${get('colors.header.logo')}; + white-space: nowrap; + cursor: pointer; + text-decoration: none; + display: flex; + align-items: center; + + &:hover, + &:focus { + color: ${get('colors.header.text')}; + } + + ${sx}; + `, +) - ${sx}; -` +const HeaderLink = React.forwardRef (function HeaderLink( + {children, className, ...rest}, + forwardRef, +) { + const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) + return ( + + {children} + + ) +}) HeaderLink.displayName = 'Header.Link' diff --git a/packages/react/src/InlineMessage/InlineMessage.dev.stories.tsx b/packages/react/src/InlineMessage/InlineMessage.dev.stories.tsx new file mode 100644 index 00000000000..31428524b11 --- /dev/null +++ b/packages/react/src/InlineMessage/InlineMessage.dev.stories.tsx @@ -0,0 +1,14 @@ +import type {Meta} from '@storybook/react' +import React from 'react' +import {InlineMessage} from '.' + +const meta = { + title: 'Experimental/Components/InlineMessage/Dev', + component: InlineMessage, +} satisfies Meta+ +export default meta + +export const DevDefault = () => { + return An example inline message +} diff --git a/packages/react/src/LabelGroup/LabelGroup.docs.json b/packages/react/src/LabelGroup/LabelGroup.docs.json index 34fafdde94f..51e64eef176 100644 --- a/packages/react/src/LabelGroup/LabelGroup.docs.json +++ b/packages/react/src/LabelGroup/LabelGroup.docs.json @@ -17,6 +17,12 @@ "description": "How many tokens to show. `'auto'` truncates the tokens to fit in the parent container. Passing a number will truncate after that number tokens. If this is undefined, tokens will never be truncated.", "defaultValue": "", "type": "'auto' | number" + }, + { + "name": "as", + "description": "Customize the element type of the rendered container.", + "defaultValue": "ul", + "type": "React.ElementType" } ], "subcomponents": [] diff --git a/packages/react/src/LabelGroup/LabelGroup.stories.tsx b/packages/react/src/LabelGroup/LabelGroup.stories.tsx index 639446335d2..d123757c6ed 100644 --- a/packages/react/src/LabelGroup/LabelGroup.stories.tsx +++ b/packages/react/src/LabelGroup/LabelGroup.stories.tsx @@ -69,6 +69,10 @@ export const Playground: StoryFn = ({ ) } +Playground.args = { + as: 'ul', +} + Playground.argTypes = { overflowStyle: { control: { diff --git a/packages/react/src/LabelGroup/LabelGroup.tsx b/packages/react/src/LabelGroup/LabelGroup.tsx index e39d389ddd1..5aaf6f8edd8 100644 --- a/packages/react/src/LabelGroup/LabelGroup.tsx +++ b/packages/react/src/LabelGroup/LabelGroup.tsx @@ -12,6 +12,8 @@ import type {SxProp} from '../sx' import sx from '../sx' export type LabelGroupProps = { + /** Customize the element type of the rendered container */ + as?: React.ElementType /** How hidden tokens should be shown. `'inline'` shows the hidden tokens after the visible tokens. `'overlay'` shows all tokens in an overlay that appears on top of the visible tokens. */ overflowStyle?: 'inline' | 'overlay' /** How many tokens to show. `'auto'` truncates the tokens to fit in the parent container. Passing a number will truncate after that number tokens. If this is undefined, tokens will never be truncated. */ @@ -30,6 +32,13 @@ const StyledLabelGroupContainer = styled.div` flex-wrap: wrap; } + &[data-list] { + padding-inline-start: 0; + margin-block-start: 0; + margin-block-end: 0; + list-style-type: none; + } + ${sx}; ` @@ -54,7 +63,7 @@ const ItemWrapper = styled.div` // Calculates the width of the overlay to cover the labels/tokens and the expand button. const getOverlayWidth = ( buttonClientRect: DOMRect, - containerRef: React.RefObject , + containerRef: React.RefObject , overlayPaddingPx: number, ) => overlayPaddingPx + buttonClientRect.right - (containerRef.current?.getBoundingClientRect().left || 0) @@ -148,8 +157,9 @@ const LabelGroup: React.FC > = ({ visibleChildCount, overflowStyle = 'overlay', sx: sxProp, + as = 'ul', }) => { - const containerRef = React.useRef (null) + const containerRef = React.useRef (null) const collapseButtonRef = React.useRef (null) const firstHiddenIndexRef = React.useRef (undefined) const [visibilityMap, setVisibilityMap] = React.useState >({}) @@ -317,50 +327,63 @@ const LabelGroup: React.FC > = ({ } }, [overflowStyle, isOverflowShown]) + const isList = as === 'ul' || as === 'ol' + const ToggleWrapper = isList ? 'li' : React.Fragment + // If truncation is enabled, we need to render based on truncation logic. return visibleChildCount ? ( {React.Children.map(children, (child, index) => ( ) : ( -{child} ))} - {overflowStyle === 'inline' ? ( -- ) : ( - - {children} - - )} ++ {overflowStyle === 'inline' ? ( + + ) : ( + + {children} + + )} +- {children} + + {isList + ? React.Children.map(children, (child, index) => { + return ) } diff --git a/packages/react/src/Link/Link.tsx b/packages/react/src/Link/Link.tsx index 608ca582edf..9c27320a776 100644 --- a/packages/react/src/Link/Link.tsx +++ b/packages/react/src/Link/Link.tsx @@ -1,18 +1,14 @@ import {clsx} from 'clsx' import React, {forwardRef, useEffect} from 'react' -import styled from 'styled-components' -import {system} from 'styled-system' -import {get} from '../constants' import {useRefObjectAsForwardedRef} from '../hooks' import type {SxProp} from '../sx' -import sx from '../sx' import classes from './Link.module.css' -import {useFeatureFlag} from '../FeatureFlags' import Box from '../Box' import type {ComponentProps} from '../utils/types' import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic' type StyledLinkProps = { + /** @deprecated use CSS modules to style hover color */ hoverColor?: string muted?: boolean /** @deprecated use `inline` to specify the type of link instead */ @@ -21,49 +17,7 @@ type StyledLinkProps = { inline?: boolean } & SxProp -const hoverColor = system({ - hoverColor: { - property: 'color', - scale: 'colors', - }, -}) - -const StyledLink = styled.a{child} + }) + : children}` - color: ${props => (props.muted ? get('colors.fg.muted')(props) : get('colors.accent.fg')(props))}; - - /* By default, Link does not have underline */ - text-decoration: none; - - /* You can add one by setting underline={true} */ - text-decoration: ${props => (props.underline ? 'underline' : undefined)}; - - /* Inline links (inside a text block), however, should have underline based on accessibility setting set in data-attribute */ - /* Note: setting underline={false} does not override this */ - [data-a11y-link-underlines='true'] &[data-inline='true'] { - text-decoration: underline; - } - - &:hover { - text-decoration: ${props => (props.muted ? 'none' : 'underline')}; - ${props => (props.hoverColor ? hoverColor : props.muted ? `color: ${get('colors.accent.fg')(props)}` : '')}; - } - &:is(button) { - display: inline-block; - padding: 0; - font-size: inherit; - white-space: nowrap; - cursor: pointer; - user-select: none; - background-color: transparent; - border: 0; - appearance: none; - } - ${sx}; -` - const Link = forwardRef(({as: Component = 'a', className, inline, underline, ...props}, forwardedRef) => { - const enabled = useFeatureFlag('primer_react_css_modules_ga') - const innerRef = React.useRef (null) useRefObjectAsForwardedRef(forwardedRef, innerRef) @@ -91,24 +45,10 @@ const Link = forwardRef(({as: Component = 'a', className, inline, underline, ... }, [innerRef]) } - if (enabled) { - if (props.sx) { - return ( - - ) - } - + if (props.sx) { return ( - `; exports[`Link passes href down to link element 1`] = ` -.c1 { - color: var(--fgColor-accent,var(--color-accent-fg,#0969da)); - -webkit-text-decoration: none; - text-decoration: none; -} - -[data-a11y-link-underlines='true'] .c0[data-inline='true'] { - -webkit-text-decoration: underline; - text-decoration: underline; -} - -.c1:hover { - -webkit-text-decoration: underline; - text-decoration: underline; -} - -.c1:is(button) { - display: inline-block; - padding: 0; - font-size: inherit; - white-space: nowrap; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - background-color: transparent; - border: 0; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; -} - `; exports[`Link respects hoverColor prop 1`] = ` -.c1 { - color: var(--fgColor-accent,var(--color-accent-fg,#0969da)); - -webkit-text-decoration: none; - text-decoration: none; -} - -[data-a11y-link-underlines='true'] .c0[data-inline='true'] { - -webkit-text-decoration: underline; - text-decoration: underline; -} - -.c1:hover { - -webkit-text-decoration: underline; - text-decoration: underline; - color: var(--fgColor-accent,var(--color-accent-fg,#0969da)); -} - -.c1:is(button) { - display: inline-block; - padding: 0; - font-size: inherit; - white-space: nowrap; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - background-color: transparent; - border: 0; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; -} - `; exports[`Link respects the "sx" prop when "muted" prop is also passed 1`] = ` -.c1 { - color: var(--fgColor-muted,var(--color-fg-muted,#656d76)); - -webkit-text-decoration: none; - text-decoration: none; +.c0 { color: var(--fgColor-onEmphasis,var(--color-fg-on-emphasis,#ffffff)); } -[data-a11y-link-underlines='true'] .c0[data-inline='true'] { - -webkit-text-decoration: underline; - text-decoration: underline; -} - -.c1:hover { - -webkit-text-decoration: none; - text-decoration: none; - color: var(--fgColor-accent,var(--color-accent-fg,#0969da)); -} - -.c1:is(button) { - display: inline-block; - padding: 0; - font-size: inherit; - white-space: nowrap; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - background-color: transparent; - border: 0; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; -} - `; exports[`Link respects the "muted" prop 1`] = ` -.c1 { - color: var(--fgColor-muted,var(--color-fg-muted,#656d76)); - -webkit-text-decoration: none; - text-decoration: none; -} - -[data-a11y-link-underlines='true'] .c0[data-inline='true'] { - -webkit-text-decoration: underline; - text-decoration: underline; -} - -.c1:hover { - -webkit-text-decoration: none; - text-decoration: none; - color: var(--fgColor-accent,var(--color-accent-fg,#0969da)); -} - -.c1:is(button) { - display: inline-block; - padding: 0; - font-size: inherit; - white-space: nowrap; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - background-color: transparent; - border: 0; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; -} - `; diff --git a/packages/react/src/NavList/__snapshots__/NavList.test.tsx.snap b/packages/react/src/NavList/__snapshots__/NavList.test.tsx.snap index c425395d008..1d4bccc1f5a 100644 --- a/packages/react/src/NavList/__snapshots__/NavList.test.tsx.snap +++ b/packages/react/src/NavList/__snapshots__/NavList.test.tsx.snap @@ -1,7 +1,30 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`NavList renders a simple list 1`] = ` -.c5 { +.c3 { + padding-left: 8px; + padding-right: 8px; + padding-top: 6px; + padding-bottom: 6px; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + -ms-flex-positive: 1; + flex-grow: 1; + border-radius: 6px; + color: inherit; +} + +.c3:hover { + color: inherit; + -webkit-text-decoration: none; + text-decoration: none; +} + +.c4 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -16,7 +39,7 @@ exports[`NavList renders a simple list 1`] = ` min-width: 0; } -.c6 { +.c5 { -webkit-box-flex: 1; -webkit-flex-grow: 1; -ms-flex-positive: 1; @@ -25,7 +48,7 @@ exports[`NavList renders a simple list 1`] = ` word-break: break-word; } -.c8 { +.c7 { -webkit-box-flex: 1; -webkit-flex-grow: 1; -ms-flex-positive: 1; @@ -151,7 +174,7 @@ exports[`NavList renders a simple list 1`] = ` border-radius: 6px; } -.c7 { +.c6 { position: relative; display: -webkit-box; display: -webkit-flex; @@ -183,27 +206,27 @@ exports[`NavList renders a simple list 1`] = ` margin-bottom: unset; } -.c7[data-loading] { +.c6[data-loading] { cursor: default; } -.c7[aria-disabled], -.c7[data-inactive] { +.c6[aria-disabled], +.c6[data-inactive] { cursor: not-allowed; } -.c7[aria-disabled] [data-component="ActionList.Checkbox"], -.c7[data-inactive] [data-component="ActionList.Checkbox"] { +.c6[aria-disabled] [data-component="ActionList.Checkbox"], +.c6[data-inactive] [data-component="ActionList.Checkbox"] { cursor: not-allowed; background-color: var(--control-bgColor-disabled,rgba(175,184,193,0.2)); border-color: var(--color-input-disabled-bg,rgba(175,184,193,0.2)); } -.c7 [data-component="ActionList.Item--DividerContainer"] { +.c6 [data-component="ActionList.Item--DividerContainer"] { position: relative; } -.c7 [data-component="ActionList.Item--DividerContainer"]::before { +.c6 [data-component="ActionList.Item--DividerContainer"]::before { content: " "; display: block; position: absolute; @@ -214,7 +237,7 @@ exports[`NavList renders a simple list 1`] = ` border-color: var(--divider-color,transparent); } -.c7:not(:first-of-type) { +.c6:not(:first-of-type) { --divider-color: var(--borderColor-muted,var(--color-action-list-item-inline-divider,rgba(208,215,222,0.48))); } @@ -222,22 +245,22 @@ exports[`NavList renders a simple list 1`] = ` --divider-color: transparent !important; } -.c7:hover:not([aria-disabled]):not([data-inactive]):not([data-loading]), -.c7[data-focus-visible-added]:not([aria-disabled]):not([data-inactive]) { +.c6:hover:not([aria-disabled]):not([data-inactive]):not([data-loading]), +.c6[data-focus-visible-added]:not([aria-disabled]):not([data-inactive]) { --divider-color: transparent; } -.c7:hover:not([aria-disabled]):not([data-inactive]):not([data-loading]) + .c1, -.c7[data-focus-visible-added] + li { +.c6:hover:not([aria-disabled]):not([data-inactive]):not([data-loading]) + .c1, +.c6[data-focus-visible-added] + li { --divider-color: transparent; } -.c7[data-is-active-descendant] { +.c6[data-is-active-descendant] { font-weight: 400; background-color: var(--control-transparent-bgColor-selected,var(--color-action-list-item-default-selected-bg,rgba(208,215,222,0.24))); } -.c7[data-is-active-descendant]::after { +.c6[data-is-active-descendant]::after { position: absolute; top: calc(50% - 12px); left: -8px; @@ -248,59 +271,6 @@ exports[`NavList renders a simple list 1`] = ` border-radius: 6px; } -.c4 { - color: var(--fgColor-accent,var(--color-accent-fg,#0969da)); - -webkit-text-decoration: none; - text-decoration: none; - padding-left: 8px; - padding-right: 8px; - padding-top: 6px; - padding-bottom: 6px; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-box-flex: 1; - -webkit-flex-grow: 1; - -ms-flex-positive: 1; - flex-grow: 1; - border-radius: 6px; - color: inherit; -} - -[data-a11y-link-underlines='true'] .c3[data-inline='true'] { - -webkit-text-decoration: underline; - text-decoration: underline; -} - -.c4:hover { - -webkit-text-decoration: underline; - text-decoration: underline; -} - -.c4:is(button) { - display: inline-block; - padding: 0; - font-size: inherit; - white-space: nowrap; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - background-color: transparent; - border: 0; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; -} - -.c4:hover { - color: inherit; - -webkit-text-decoration: none; - text-decoration: none; -} - @media (forced-colors:active) { .c2:focus, .c2:focus-visible, @@ -332,30 +302,30 @@ exports[`NavList renders a simple list 1`] = ` } @media (forced-colors:active) { - .c7:focus, - .c7:focus-visible, - .c7 > a.focus-visible, - .c7[data-is-active-descendant] { + .c6:focus, + .c6:focus-visible, + .c6 > a.focus-visible, + .c6[data-is-active-descendant] { outline: solid 1px transparent !important; } } @media (hover:hover) and (pointer:fine) { - .c7:hover:not([aria-disabled]):not([data-inactive]) { + .c6:hover:not([aria-disabled]):not([data-inactive]) { background-color: var(--control-transparent-bgColor-hover,var(--color-action-list-item-default-hover-bg,rgba(208,215,222,0.32))); color: var(--fgColor-default,var(--color-fg-default,#1F2328)); box-shadow: inset 0 0 0 max(1px,0.0625rem) var(--control-transparent-borderColor-active,var(--color-action-list-item-default-active-border,transparent)); } - .c7:focus-visible, - .c7 > a.focus-visible, - .c7:focus.focus-visible { + .c6:focus-visible, + .c6 > a.focus-visible, + .c6:focus.focus-visible { outline: none; border: 2 solid; box-shadow: 0 0 0 2px var(--bgColor-accent-emphasis,var(--color-accent-emphasis,#0969da)); } - .c7:active:not([aria-disabled]):not([data-inactive]) { + .c6:active:not([aria-disabled]):not([data-inactive]) { background-color: var(--control-transparent-bgColor-active,var(--color-action-list-item-default-active-bg,rgba(208,215,222,0.48))); color: var(--fgColor-default,var(--color-fg-default,#1F2328)); } @@ -374,17 +344,17 @@ exports[`NavList renders a simple list 1`] = ` Home @@ -393,21 +363,21 @@ exports[`NavList renders a simple list 1`] = ` About @@ -416,21 +386,21 @@ exports[`NavList renders a simple list 1`] = ` Contact @@ -496,7 +466,30 @@ exports[`NavList renders with groups 1`] = ` padding-inline-start: 0; } -.c9 { +.c7 { + padding-left: 8px; + padding-right: 8px; + padding-top: 6px; + padding-bottom: 6px; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + -ms-flex-positive: 1; + flex-grow: 1; + border-radius: 6px; + color: inherit; +} + +.c7:hover { + color: inherit; + -webkit-text-decoration: none; + text-decoration: none; +} + +.c8 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -511,7 +504,7 @@ exports[`NavList renders with groups 1`] = ` min-width: 0; } -.c10 { +.c9 { -webkit-box-flex: 1; -webkit-flex-grow: 1; -ms-flex-positive: 1; @@ -520,7 +513,7 @@ exports[`NavList renders with groups 1`] = ` word-break: break-word; } -.c12 { +.c11 { -webkit-box-flex: 1; -webkit-flex-grow: 1; -ms-flex-positive: 1; @@ -646,7 +639,7 @@ exports[`NavList renders with groups 1`] = ` border-radius: 6px; } -.c11 { +.c10 { position: relative; display: -webkit-box; display: -webkit-flex; @@ -678,27 +671,27 @@ exports[`NavList renders with groups 1`] = ` margin-bottom: unset; } -.c11[data-loading] { +.c10[data-loading] { cursor: default; } -.c11[aria-disabled], -.c11[data-inactive] { +.c10[aria-disabled], +.c10[data-inactive] { cursor: not-allowed; } -.c11[aria-disabled] [data-component="ActionList.Checkbox"], -.c11[data-inactive] [data-component="ActionList.Checkbox"] { +.c10[aria-disabled] [data-component="ActionList.Checkbox"], +.c10[data-inactive] [data-component="ActionList.Checkbox"] { cursor: not-allowed; background-color: var(--control-bgColor-disabled,rgba(175,184,193,0.2)); border-color: var(--color-input-disabled-bg,rgba(175,184,193,0.2)); } -.c11 [data-component="ActionList.Item--DividerContainer"] { +.c10 [data-component="ActionList.Item--DividerContainer"] { position: relative; } -.c11 [data-component="ActionList.Item--DividerContainer"]::before { +.c10 [data-component="ActionList.Item--DividerContainer"]::before { content: " "; display: block; position: absolute; @@ -709,7 +702,7 @@ exports[`NavList renders with groups 1`] = ` border-color: var(--divider-color,transparent); } -.c11:not(:first-of-type) { +.c10:not(:first-of-type) { --divider-color: var(--borderColor-muted,var(--color-action-list-item-inline-divider,rgba(208,215,222,0.48))); } @@ -717,22 +710,22 @@ exports[`NavList renders with groups 1`] = ` --divider-color: transparent !important; } -.c11:hover:not([aria-disabled]):not([data-inactive]):not([data-loading]), -.c11[data-focus-visible-added]:not([aria-disabled]):not([data-inactive]) { +.c10:hover:not([aria-disabled]):not([data-inactive]):not([data-loading]), +.c10[data-focus-visible-added]:not([aria-disabled]):not([data-inactive]) { --divider-color: transparent; } -.c11:hover:not([aria-disabled]):not([data-inactive]):not([data-loading]) + .c5, -.c11[data-focus-visible-added] + li { +.c10:hover:not([aria-disabled]):not([data-inactive]):not([data-loading]) + .c5, +.c10[data-focus-visible-added] + li { --divider-color: transparent; } -.c11[data-is-active-descendant] { +.c10[data-is-active-descendant] { font-weight: 400; background-color: var(--control-transparent-bgColor-selected,var(--color-action-list-item-default-selected-bg,rgba(208,215,222,0.24))); } -.c11[data-is-active-descendant]::after { +.c10[data-is-active-descendant]::after { position: absolute; top: calc(50% - 12px); left: -8px; @@ -743,59 +736,6 @@ exports[`NavList renders with groups 1`] = ` border-radius: 6px; } -.c8 { - color: var(--fgColor-accent,var(--color-accent-fg,#0969da)); - -webkit-text-decoration: none; - text-decoration: none; - padding-left: 8px; - padding-right: 8px; - padding-top: 6px; - padding-bottom: 6px; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-box-flex: 1; - -webkit-flex-grow: 1; - -ms-flex-positive: 1; - flex-grow: 1; - border-radius: 6px; - color: inherit; -} - -[data-a11y-link-underlines='true'] .c7[data-inline='true'] { - -webkit-text-decoration: underline; - text-decoration: underline; -} - -.c8:hover { - -webkit-text-decoration: underline; - text-decoration: underline; -} - -.c8:is(button) { - display: inline-block; - padding: 0; - font-size: inherit; - white-space: nowrap; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - background-color: transparent; - border: 0; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; -} - -.c8:hover { - color: inherit; - -webkit-text-decoration: none; - text-decoration: none; -} - @media (forced-colors:active) { .c6:focus, .c6:focus-visible, @@ -827,30 +767,30 @@ exports[`NavList renders with groups 1`] = ` } @media (forced-colors:active) { - .c11:focus, - .c11:focus-visible, - .c11 > a.focus-visible, - .c11[data-is-active-descendant] { + .c10:focus, + .c10:focus-visible, + .c10 > a.focus-visible, + .c10[data-is-active-descendant] { outline: solid 1px transparent !important; } } @media (hover:hover) and (pointer:fine) { - .c11:hover:not([aria-disabled]):not([data-inactive]) { + .c10:hover:not([aria-disabled]):not([data-inactive]) { background-color: var(--control-transparent-bgColor-hover,var(--color-action-list-item-default-hover-bg,rgba(208,215,222,0.32))); color: var(--fgColor-default,var(--color-fg-default,#1F2328)); box-shadow: inset 0 0 0 max(1px,0.0625rem) var(--control-transparent-borderColor-active,var(--color-action-list-item-default-active-border,transparent)); } - .c11:focus-visible, - .c11 > a.focus-visible, - .c11:focus.focus-visible { + .c10:focus-visible, + .c10 > a.focus-visible, + .c10:focus.focus-visible { outline: none; border: 2 solid; box-shadow: 0 0 0 2px var(--bgColor-accent-emphasis,var(--color-accent-emphasis,#0969da)); } - .c11:active:not([aria-disabled]):not([data-inactive]) { + .c10:active:not([aria-disabled]):not([data-inactive]) { background-color: var(--control-transparent-bgColor-active,var(--color-action-list-item-default-active-bg,rgba(208,215,222,0.48))); color: var(--fgColor-default,var(--color-fg-default,#1F2328)); } @@ -892,17 +832,17 @@ exports[`NavList renders with groups 1`] = `Getting started @@ -936,21 +876,21 @@ exports[`NavList renders with groups 1`] = ` class="c4" > Avatar @@ -1020,7 +960,32 @@ exports[`NavList.Item with NavList.SubNav does not have active styles if SubNav color: var(--fgColor-default,var(--color-fg-default,#1F2328)); } -.c14 { +.c12 { + padding-left: 16px; + padding-right: 8px; + padding-top: 6px; + padding-bottom: 6px; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + -ms-flex-positive: 1; + flex-grow: 1; + border-radius: 6px; + color: inherit; + font-size: 12px; + font-weight: 400; +} + +.c12:hover { + color: inherit; + -webkit-text-decoration: none; + text-decoration: none; +} + +.c13 { -webkit-box-flex: 1; -webkit-flex-grow: 1; -ms-flex-positive: 1; @@ -1046,8 +1011,8 @@ exports[`NavList.Item with NavList.SubNav does not have active styles if SubNav --divider-color: transparent !important; } -.c15:hover:not([aria-disabled]):not([data-inactive]):not([data-loading]) + .c3, -.c15[data-focus-visible-added] + li { +.c14:hover:not([aria-disabled]):not([data-inactive]):not([data-loading]) + .c3, +.c14[data-focus-visible-added] + li { --divider-color: transparent; } @@ -1258,61 +1223,6 @@ exports[`NavList.Item with NavList.SubNav does not have active styles if SubNav border-radius: 6px; } -.c13 { - color: var(--fgColor-accent,var(--color-accent-fg,#0969da)); - -webkit-text-decoration: none; - text-decoration: none; - padding-left: 16px; - padding-right: 8px; - padding-top: 6px; - padding-bottom: 6px; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-box-flex: 1; - -webkit-flex-grow: 1; - -ms-flex-positive: 1; - flex-grow: 1; - border-radius: 6px; - color: inherit; - font-size: 12px; - font-weight: 400; -} - -[data-a11y-link-underlines='true'] .c12[data-inline='true'] { - -webkit-text-decoration: underline; - text-decoration: underline; -} - -.c13:hover { - -webkit-text-decoration: underline; - text-decoration: underline; -} - -.c13:is(button) { - display: inline-block; - padding: 0; - font-size: inherit; - white-space: nowrap; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - background-color: transparent; - border: 0; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; -} - -.c13:hover { - color: inherit; - -webkit-text-decoration: none; - text-decoration: none; -} - .c9 { -webkit-transform: rotate(180deg); -ms-transform: rotate(180deg); @@ -1453,7 +1363,7 @@ exports[`NavList.Item with NavList.SubNav does not have active styles if SubNav Sub Item @@ -1531,6 +1441,31 @@ exports[`NavList.Item with NavList.SubNav has active styles if SubNav contains t display: none; } +.c12 { + padding-left: 16px; + padding-right: 8px; + padding-top: 6px; + padding-bottom: 6px; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + -ms-flex-positive: 1; + flex-grow: 1; + border-radius: 6px; + color: inherit; + font-size: 12px; + font-weight: 400; +} + +.c12:hover { + color: inherit; + -webkit-text-decoration: none; + text-decoration: none; +} + .c7 { -webkit-box-flex: 1; -webkit-flex-grow: 1; @@ -1551,8 +1486,8 @@ exports[`NavList.Item with NavList.SubNav has active styles if SubNav contains t --divider-color: transparent !important; } -.c14:hover:not([aria-disabled]):not([data-inactive]):not([data-loading]) + .c3, -.c14[data-focus-visible-added] + li { +.c13:hover:not([aria-disabled]):not([data-inactive]):not([data-loading]) + .c3, +.c13[data-focus-visible-added] + li { --divider-color: transparent; } @@ -1670,8 +1605,8 @@ exports[`NavList.Item with NavList.SubNav has active styles if SubNav contains t --divider-color: transparent !important; } -.c15:hover:not([aria-disabled]):not([data-inactive]):not([data-loading]) + .c3, -.c15[data-focus-visible-added] + li { +.c14:hover:not([aria-disabled]):not([data-inactive]):not([data-loading]) + .c3, +.c14[data-focus-visible-added] + li { --divider-color: transparent; } @@ -1784,61 +1719,6 @@ exports[`NavList.Item with NavList.SubNav has active styles if SubNav contains t border-radius: 6px; } -.c13 { - color: var(--fgColor-accent,var(--color-accent-fg,#0969da)); - -webkit-text-decoration: none; - text-decoration: none; - padding-left: 16px; - padding-right: 8px; - padding-top: 6px; - padding-bottom: 6px; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-box-flex: 1; - -webkit-flex-grow: 1; - -ms-flex-positive: 1; - flex-grow: 1; - border-radius: 6px; - color: inherit; - font-size: 12px; - font-weight: 400; -} - -[data-a11y-link-underlines='true'] .c12[data-inline='true'] { - -webkit-text-decoration: underline; - text-decoration: underline; -} - -.c13:hover { - -webkit-text-decoration: underline; - text-decoration: underline; -} - -.c13:is(button) { - display: inline-block; - padding: 0; - font-size: inherit; - white-space: nowrap; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - background-color: transparent; - border: 0; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; -} - -.c13:hover { - color: inherit; - -webkit-text-decoration: none; - text-decoration: none; -} - .c9 { -webkit-transform: rotate(0deg); -ms-transform: rotate(0deg); @@ -1987,7 +1867,7 @@ exports[`NavList.Item with NavList.SubNav has active styles if SubNav contains t { role={role} aria-modal={role === 'dialog' ? 'true' : undefined} ref={primaryContainer} + preventOverflow={false} > ) } -const StyledSpinner = styled(Spinner)` +const StyledComponentSpinner = styled(Spinner)` @keyframes rotate-keyframes { 100% { transform: rotate(360deg); @@ -82,6 +87,19 @@ const StyledSpinner = styled(Spinner)` ${sx} ` +function StyledSpinner({sx, ...props}: SpinnerProps) { + const enabled = useFeatureFlag('primer_react_css_modules_team') + if (enabled) { + if (sx) { + return+ } + + return + } + + return +} + StyledSpinner.displayName = 'Spinner' export default StyledSpinner diff --git a/packages/react/src/Stack/Stack.module.css b/packages/react/src/Stack/Stack.module.css new file mode 100644 index 00000000000..f326ef06e9e --- /dev/null +++ b/packages/react/src/Stack/Stack.module.css @@ -0,0 +1,323 @@ +.Stack { + display: flex; + flex-flow: column; + align-items: stretch; + align-content: flex-start; + gap: var(--stack-gap, var(--stack-gap-normal)); + + &[data-padding='none'], + &[data-padding-narrow='none'] { + padding: 0; + } + + &[data-padding='condensed'], + &[data-padding-narrow='condensed'] { + /* stylelint-disable-next-line primer/spacing */ + padding: var(--stack-padding-condensed); + } + + &[data-padding='normal'], + &[data-padding-narrow='normal'] { + /* stylelint-disable-next-line primer/spacing */ + padding: var(--stack-padding-normal); + } + + &[data-padding='spacious'], + &[data-padding-narrow='spacious'] { + /* stylelint-disable-next-line primer/spacing */ + padding: var(--stack-padding-spacious); + } + + &[data-direction='horizontal'], + &[data-direction-narrow='horizontal'] { + flex-flow: row; + } + + &[data-direction='vertical'], + &[data-direction-narrow='vertical'] { + flex-flow: column; + } + + &[data-gap='none'], + &[data-gap-narrow='none'] { + --stack-gap: 0; + } + + &[data-gap='condensed'], + &[data-gap-narrow='condensed'] { + --stack-gap: var(--stack-gap-condensed); + } + + &[data-gap='normal'], + &[data-gap-narrow='normal'] { + --stack-gap: var(--stack-gap-normal); + } + + &[data-gap='spacious'], + &[data-gap-narrow='spacious'] { + --stack-gap: var(--stack-gap-spacious); + } + + &[data-align='start'], + &[data-align-narrow='start'] { + align-items: flex-start; + } + + &[data-align='center'], + &[data-align-narrow='center'] { + align-items: center; + } + + &[data-align='end'], + &[data-align-narrow='end'] { + align-items: flex-end; + } + + &[data-align='baseline'], + &[data-align-narrow='baseline'] { + align-items: baseline; + } + + &[data-justify='start'], + &[data-justify-narrow='start'] { + justify-content: flex-start; + } + + &[data-justify='center'], + &[data-justify-narrow='center'] { + justify-content: center; + } + + &[data-justify='end'], + &[data-justify-narrow='end'] { + justify-content: flex-end; + } + + &[data-justify='space-between'], + &[data-justify-narrow='space-between'] { + justify-content: space-between; + } + + &[data-justify='space-evenly'], + &[data-justify-narrow='space-evenly'] { + justify-content: space-evenly; + } + + &[data-wrap='wrap'], + &[data-wrap-narrow='wrap'] { + flex-wrap: wrap; + } + + &[data-wrap='nowrap'], + &[data-wrap-narrow='nowrap'] { + flex-wrap: nowrap; + } + + @media (--veiwportRange-regular) { + &[data-padding-regular='none'] { + padding: 0; + } + + &[data-padding-regular='condensed'] { + /* stylelint-disable-next-line primer/spacing */ + padding: var(--stack-padding-condensed); + } + + &[data-padding-regular='normal'] { + /* stylelint-disable-next-line primer/spacing */ + padding: var(--stack-padding-normal); + } + + &[data-padding-regular='spacious'] { + /* stylelint-disable-next-line primer/spacing */ + padding: var(--stack-padding-spacious); + } + + &[data-direction-regular='horizontal'] { + flex-flow: row; + } + + &[data-direction-regular='vertical'] { + flex-flow: column; + } + + &[data-gap-regular='none'] { + --stack-gap: 0; + } + + &[data-gap-regular='condensed'] { + --stack-gap: var(--stack-gap-condensed); + } + + &[data-gap-regular='normal'] { + --stack-gap: var(--stack-gap-normal); + } + + &[data-gap-regular='spacious'] { + --stack-gap: var(--stack-gap-spacious); + } + + &[data-align-regular='start'] { + align-items: flex-start; + } + + &[data-align-regular='center'] { + align-items: center; + } + + &[data-align-regular='end'] { + align-items: flex-end; + } + + &[data-align-regular='baseline'] { + align-items: baseline; + } + + &[data-justify-regular='start'] { + justify-content: flex-start; + } + + &[data-justify-regular='center'] { + justify-content: center; + } + + &[data-justify-regular='end'] { + justify-content: flex-end; + } + + &[data-justify-regular='space-between'] { + justify-content: space-between; + } + + &[data-justify-regular='space-evenly'] { + justify-content: space-evenly; + } + + &[data-wrap-regular='wrap'] { + flex-wrap: wrap; + } + + &[data-wrap-regular='nowrap'] { + flex-wrap: nowrap; + } + } + + @media (--viewportRange-wide) { + &[data-padding-wide='none'] { + padding: 0; + } + + &[data-padding-wide='condensed'] { + /* stylelint-disable-next-line primer/spacing */ + padding: var(--stack-padding-condensed); + } + + &[data-padding-wide='normal'] { + /* stylelint-disable-next-line primer/spacing */ + padding: var(--stack-padding-normal); + } + + &[data-padding-wide='spacious'] { + /* stylelint-disable-next-line primer/spacing */ + padding: var(--stack-padding-spacious); + } + + &[data-direction-wide='horizontal'] { + flex-flow: row; + } + + &[data-direction-wide='vertical'] { + flex-flow: column; + } + + &[data-gap-wide='none'] { + --stack-gap: 0; + } + + &[data-gap-wide='condensed'] { + --stack-gap: var(--stack-gap-condensed); + } + + &[data-gap-wide='normal'] { + --stack-gap: var(--stack-gap-normal); + } + + &[data-gap-wide='spacious'] { + --stack-gap: var(--stack-gap-spacious); + } + + &[data-align-wide='start'] { + align-items: flex-start; + } + + &[data-align-wide='center'] { + align-items: center; + } + + &[data-align-wide='end'] { + align-items: flex-end; + } + + &[data-align-wide='baseline'] { + align-items: baseline; + } + + &[data-justify-wide='start'] { + justify-content: flex-start; + } + + &[data-justify-wide='center'] { + justify-content: center; + } + + &[data-justify-wide='end'] { + justify-content: flex-end; + } + + &[data-justify-wide='space-between'] { + justify-content: space-between; + } + + &[data-justify-wide='space-evenly'] { + justify-content: space-evenly; + } + + &[data-wrap-wide='wrap'] { + flex-wrap: wrap; + } + + &[data-wrap-wide='nowrap'] { + flex-wrap: nowrap; + } + } +} + +.StackItem { + flex: 0 1 auto; + min-inline-size: 0; + + &[data-grow='true'], + &[data-grow-narrow='true'] { + flex-grow: 1; + } + + @media (--veiwportRange-regular) { + &[data-grow-regular='true'] { + flex-grow: 1; + } + + &[data-grow-regular='false'] { + flex-grow: 0; + } + } + + @media (--viewportRange-wide) { + &[data-grow-wide='true'] { + flex-grow: 1; + } + + &[data-grow-wide='false'] { + flex-grow: 0; + } + } +} diff --git a/packages/react/src/Stack/Stack.tsx b/packages/react/src/Stack/Stack.tsx index 6b14b52a3e8..865936e9da9 100644 --- a/packages/react/src/Stack/Stack.tsx +++ b/packages/react/src/Stack/Stack.tsx @@ -2,295 +2,305 @@ import React, {type ElementType} from 'react' import styled from 'styled-components' import type {ResponsiveValue} from '../hooks/useResponsiveValue' import {getResponsiveAttributes} from '../internal/utils/getResponsiveAttributes' - -const StyledStack = styled.div` - display: flex; - flex-flow: column; - align-items: stretch; - align-content: flex-start; - gap: var(--stack-gap, var(--stack-gap-normal, 1rem)); - - // non-responsive values - - &[data-padding='none'], - &[data-padding-narrow='none'] { - padding: 0; - } - - &[data-padding='condensed'], - &[data-padding-narrow='condensed'] { - padding: var(--stack-padding-condensed, 8px); - } - - &[data-padding='normal'], - &[data-padding-narrow='normal'] { - padding: var(--stack-padding-normal, 16px); - } - - &[data-padding='spacious'], - &[data-padding-narrow='spacious'] { - padding: var(--stack-padding-spacious, 24px); - } - - &[data-direction='horizontal'], - &[data-direction-narrow='horizontal'] { - flex-flow: row; - } - - &[data-direction='vertical'], - &[data-direction-narrow='vertical'] { +import classes from './Stack.module.css' +import {toggleStyledComponent} from '../internal/utils/toggleStyledComponent' +import {useFeatureFlag} from '../FeatureFlags' +import {clsx} from 'clsx' + +const CSS_MODULE_FEATURE_FLAG = 'primer_react_css_modules_team' + +const StyledStack = toggleStyledComponent( + CSS_MODULE_FEATURE_FLAG, + 'div', + styled.div` + display: flex; flex-flow: column; - } - - &[data-gap='none'], - &[data-gap-narrow='none'] { - --stack-gap: var(--stack-gap-none, 0); - } - - &[data-gap='condensed'], - &[data-gap-narrow='condensed'] { - --stack-gap: var(--stack-gap-condensed, 0.5rem); - } - - &[data-gap='normal'], - &[data-gap-narrow='normal'] { - --stack-gap: var(--stack-gap-normal, 1rem); - } - - &[data-gap='spacious'], - &[data-gap-narrow='spacious'] { - --stack-gap: var(--stack-gap-spacious, 1.5rem); - } - - &[data-align='start'], - &[data-align-narrow='start'] { - align-items: flex-start; - } - - &[data-align='center'], - &[data-align-narrow='center'] { - align-items: center; - } - - &[data-align='end'], - &[data-align-narrow='end'] { - align-items: flex-end; - } - - &[data-align='baseline'], - &[data-align-narrow='baseline'] { - align-items: baseline; - } - - &[data-justify='start'], - &[data-justify-narrow='start'] { - justify-content: flex-start; - } - - &[data-justify='center'], - &[data-justify-narrow='center'] { - justify-content: center; - } - - &[data-justify='end'], - &[data-justify-narrow='end'] { - justify-content: flex-end; - } - - &[data-justify='space-between'], - &[data-justify-narrow='space-between'] { - justify-content: space-between; - } - - &[data-justify='space-evenly'], - &[data-justify-narrow='space-evenly'] { - justify-content: space-evenly; - } - - &[data-wrap='wrap'], - &[data-wrap-narrow='wrap'] { - flex-wrap: wrap; - } - - &[data-wrap='nowrap'], - &[data-wrap-narrow='nowrap'] { - flex-wrap: nowrap; - } - - // @custom-media --veiwportRange-regular - @media (min-width: 48rem) { - &[data-padding-regular='none'] { + align-items: stretch; + align-content: flex-start; + gap: var(--stack-gap, var(--stack-gap-normal, 1rem)); + + // non-responsive values + + &[data-padding='none'], + &[data-padding-narrow='none'] { padding: 0; } - &[data-padding-regular='condensed'] { + &[data-padding='condensed'], + &[data-padding-narrow='condensed'] { padding: var(--stack-padding-condensed, 8px); } - &[data-padding-regular='normal'] { + &[data-padding='normal'], + &[data-padding-narrow='normal'] { padding: var(--stack-padding-normal, 16px); } - &[data-padding-regular='spacious'] { + &[data-padding='spacious'], + &[data-padding-narrow='spacious'] { padding: var(--stack-padding-spacious, 24px); } - &[data-direction-regular='horizontal'] { + &[data-direction='horizontal'], + &[data-direction-narrow='horizontal'] { flex-flow: row; } - &[data-direction-regular='vertical'] { + &[data-direction='vertical'], + &[data-direction-narrow='vertical'] { flex-flow: column; } - &[data-gap-regular='none'] { + &[data-gap='none'], + &[data-gap-narrow='none'] { --stack-gap: var(--stack-gap-none, 0); } - &[data-gap-regular='condensed'] { + &[data-gap='condensed'], + &[data-gap-narrow='condensed'] { --stack-gap: var(--stack-gap-condensed, 0.5rem); } - &[data-gap-regular='normal'] { + &[data-gap='normal'], + &[data-gap-narrow='normal'] { --stack-gap: var(--stack-gap-normal, 1rem); } - &[data-gap-regular='spacious'] { + &[data-gap='spacious'], + &[data-gap-narrow='spacious'] { --stack-gap: var(--stack-gap-spacious, 1.5rem); } - &[data-align-regular='start'] { + &[data-align='start'], + &[data-align-narrow='start'] { align-items: flex-start; } - &[data-align-regular='center'] { + &[data-align='center'], + &[data-align-narrow='center'] { align-items: center; } - &[data-align-regular='end'] { + &[data-align='end'], + &[data-align-narrow='end'] { align-items: flex-end; } - &[data-align-regular='baseline'] { + &[data-align='baseline'], + &[data-align-narrow='baseline'] { align-items: baseline; } - &[data-justify-regular='start'] { + &[data-justify='start'], + &[data-justify-narrow='start'] { justify-content: flex-start; } - &[data-justify-regular='center'] { + &[data-justify='center'], + &[data-justify-narrow='center'] { justify-content: center; } - &[data-justify-regular='end'] { + &[data-justify='end'], + &[data-justify-narrow='end'] { justify-content: flex-end; } - &[data-justify-regular='space-between'] { + &[data-justify='space-between'], + &[data-justify-narrow='space-between'] { justify-content: space-between; } - &[data-justify-regular='space-evenly'] { + &[data-justify='space-evenly'], + &[data-justify-narrow='space-evenly'] { justify-content: space-evenly; } - &[data-wrap-regular='wrap'] { + &[data-wrap='wrap'], + &[data-wrap-narrow='wrap'] { flex-wrap: wrap; } - &[data-wrap-regular='nowrap'] { + &[data-wrap='nowrap'], + &[data-wrap-narrow='nowrap'] { flex-wrap: nowrap; } - } - // @custom-media --viewportRange-wide - @media (min-width: 87.5rem) { - &[data-padding-wide='none'] { - padding: 0; - } + // @custom-media --veiwportRange-regular + @media (min-width: 48rem) { + &[data-padding-regular='none'] { + padding: 0; + } - &[data-padding-wide='condensed'] { - padding: var(--stack-padding-condensed, 8px); - } + &[data-padding-regular='condensed'] { + padding: var(--stack-padding-condensed, 8px); + } - &[data-padding-wide='normal'] { - padding: var(--stack-padding-normal, 16px); - } + &[data-padding-regular='normal'] { + padding: var(--stack-padding-normal, 16px); + } - &[data-padding-wide='spacious'] { - padding: var(--stack-padding-spacious, 24px); - } + &[data-padding-regular='spacious'] { + padding: var(--stack-padding-spacious, 24px); + } - &[data-direction-wide='horizontal'] { - flex-flow: row; - } + &[data-direction-regular='horizontal'] { + flex-flow: row; + } - &[data-direction-wide='vertical'] { - flex-flow: column; - } + &[data-direction-regular='vertical'] { + flex-flow: column; + } - &[data-gap-wide='none'] { - --stack-gap: var(--stack-gap-none, 0); - } + &[data-gap-regular='none'] { + --stack-gap: var(--stack-gap-none, 0); + } - &[data-gap-wide='condensed'] { - --stack-gap: var(--stack-gap-condensed, 0.5rem); - } + &[data-gap-regular='condensed'] { + --stack-gap: var(--stack-gap-condensed, 0.5rem); + } - &[data-gap-wide='normal'] { - --stack-gap: var(--stack-gap-normal, 1rem); - } + &[data-gap-regular='normal'] { + --stack-gap: var(--stack-gap-normal, 1rem); + } - &[data-gap-wide='spacious'] { - --stack-gap: var(--stack-gap-spacious, 1.5rem); - } + &[data-gap-regular='spacious'] { + --stack-gap: var(--stack-gap-spacious, 1.5rem); + } - &[data-align-wide='start'] { - align-items: flex-start; - } + &[data-align-regular='start'] { + align-items: flex-start; + } - &[data-align-wide='center'] { - align-items: center; - } + &[data-align-regular='center'] { + align-items: center; + } - &[data-align-wide='end'] { - align-items: flex-end; - } + &[data-align-regular='end'] { + align-items: flex-end; + } - &[data-align-wide='baseline'] { - align-items: baseline; - } + &[data-align-regular='baseline'] { + align-items: baseline; + } - &[data-justify-wide='start'] { - justify-content: flex-start; - } + &[data-justify-regular='start'] { + justify-content: flex-start; + } - &[data-justify-wide='center'] { - justify-content: center; - } + &[data-justify-regular='center'] { + justify-content: center; + } - &[data-justify-wide='end'] { - justify-content: flex-end; - } + &[data-justify-regular='end'] { + justify-content: flex-end; + } - &[data-justify-wide='space-between'] { - justify-content: space-between; - } + &[data-justify-regular='space-between'] { + justify-content: space-between; + } - &[data-justify-wide='space-evenly'] { - justify-content: space-evenly; - } + &[data-justify-regular='space-evenly'] { + justify-content: space-evenly; + } - &[data-wrap-wide='wrap'] { - flex-wrap: wrap; + &[data-wrap-regular='wrap'] { + flex-wrap: wrap; + } + + &[data-wrap-regular='nowrap'] { + flex-wrap: nowrap; + } } - &[data-wrap-wide='nowrap'] { - flex-wrap: nowrap; + // @custom-media --viewportRange-wide + @media (min-width: 87.5rem) { + &[data-padding-wide='none'] { + padding: 0; + } + + &[data-padding-wide='condensed'] { + padding: var(--stack-padding-condensed, 8px); + } + + &[data-padding-wide='normal'] { + padding: var(--stack-padding-normal, 16px); + } + + &[data-padding-wide='spacious'] { + padding: var(--stack-padding-spacious, 24px); + } + + &[data-direction-wide='horizontal'] { + flex-flow: row; + } + + &[data-direction-wide='vertical'] { + flex-flow: column; + } + + &[data-gap-wide='none'] { + --stack-gap: var(--stack-gap-none, 0); + } + + &[data-gap-wide='condensed'] { + --stack-gap: var(--stack-gap-condensed, 0.5rem); + } + + &[data-gap-wide='normal'] { + --stack-gap: var(--stack-gap-normal, 1rem); + } + + &[data-gap-wide='spacious'] { + --stack-gap: var(--stack-gap-spacious, 1.5rem); + } + + &[data-align-wide='start'] { + align-items: flex-start; + } + + &[data-align-wide='center'] { + align-items: center; + } + + &[data-align-wide='end'] { + align-items: flex-end; + } + + &[data-align-wide='baseline'] { + align-items: baseline; + } + + &[data-justify-wide='start'] { + justify-content: flex-start; + } + + &[data-justify-wide='center'] { + justify-content: center; + } + + &[data-justify-wide='end'] { + justify-content: flex-end; + } + + &[data-justify-wide='space-between'] { + justify-content: space-between; + } + + &[data-justify-wide='space-evenly'] { + justify-content: space-evenly; + } + + &[data-wrap-wide='wrap'] { + flex-wrap: wrap; + } + + &[data-wrap-wide='nowrap'] { + flex-wrap: nowrap; + } } - } -` + `, +) type GapScale = 'none' | 'condensed' | 'normal' | 'spacious' type Gap = GapScale | ResponsiveValue @@ -366,12 +376,12 @@ function Stack ({ ...rest }: StackProps & React.ComponentPropsWithoutRef ) { const BaseComponent = as ?? 'div' - + const enabled = useFeatureFlag(CSS_MODULE_FEATURE_FLAG) return ( ({ ) } -const StyledStackItem = styled.div` - flex: 0 1 auto; - min-inline-size: 0; - - &[data-grow='true'], - &[data-grow-narrow='true'] { - flex-grow: 1; - } +const StyledStackItem = toggleStyledComponent( + CSS_MODULE_FEATURE_FLAG, + 'div', + styled.div` + flex: 0 1 auto; + min-inline-size: 0; - // @custom-media --veiwportRange-regular - @media (min-width: 48rem) { - &[data-grow-regular='true'] { + &[data-grow='true'], + &[data-grow-narrow='true'] { flex-grow: 1; } - &[data-grow-regular='false'] { - flex-grow: 0; - } - } + // @custom-media --veiwportRange-regular + @media (min-width: 48rem) { + &[data-grow-regular='true'] { + flex-grow: 1; + } - // @custom-media --viewportRange-wide - @media (min-width: 87.5rem) { - &[data-grow-wide='true'] { - flex-grow: 1; + &[data-grow-regular='false'] { + flex-grow: 0; + } } - &[data-grow-wide='false'] { - flex-grow: 0; + // @custom-media --viewportRange-wide + @media (min-width: 87.5rem) { + &[data-grow-wide='true'] { + flex-grow: 1; + } + + &[data-grow-wide='false'] { + flex-grow: 0; + } } - } -` + `, +) type StackItemProps = React.PropsWithChildren<{ /** @@ -438,9 +452,15 @@ function StackItem ({ ...rest }: StackItemProps & React.ComponentPropsWithoutRef ) { const BaseComponent = as ?? 'div' + const enabled = useFeatureFlag(CSS_MODULE_FEATURE_FLAG) return ( - + {children} ) diff --git a/packages/react/src/Stack/__tests__/Stack.test.tsx b/packages/react/src/Stack/__tests__/Stack.test.tsx index e4cca6c27d0..e98402bc024 100644 --- a/packages/react/src/Stack/__tests__/Stack.test.tsx +++ b/packages/react/src/Stack/__tests__/Stack.test.tsx @@ -1,8 +1,28 @@ import {render, screen} from '@testing-library/react' import React from 'react' import {Stack} from '../Stack' +import {FeatureFlags} from '../../FeatureFlags' describe('Stack', () => { + it('should support `className` on the outermost element', () => { + const Element = () =>+ const FeatureFlagElement = () => { + return ( + + + ) + } + expect(render(+ ).container.firstChild).toHaveClass('test-class-name') + expect(render( ).container.firstChild).toHaveClass('test-class-name') + }) + it('should support rendering content through `children`', () => { render( diff --git a/packages/react/src/Stack/__tests__/StackItem.test.tsx b/packages/react/src/Stack/__tests__/StackItem.test.tsx index 4daa252d46b..56eae72c474 100644 --- a/packages/react/src/Stack/__tests__/StackItem.test.tsx +++ b/packages/react/src/Stack/__tests__/StackItem.test.tsx @@ -1,8 +1,34 @@ import {render, screen} from '@testing-library/react' import React from 'react' import {Stack, StackItem} from '../Stack' +import {FeatureFlags} from '../../FeatureFlags' describe('StackItem', () => { + it('should support `className` on the outermost element', () => { + const Element = () => ( + + + ) + const FeatureFlagElement = () => { + return ( ++ Content + ++ + ) + } + expect(render(+ ).getAllByTestId('stack-item')[0]).toHaveClass('test-class-name') + expect(render( ).getAllByTestId('stack-item')[1]).toHaveClass('test-class-name') + }) + it('should render its children', () => { render( diff --git a/packages/react/src/SubNav/SubNav.dev.module.css b/packages/react/src/SubNav/SubNav.dev.module.css new file mode 100644 index 00000000000..61807842f42 --- /dev/null +++ b/packages/react/src/SubNav/SubNav.dev.module.css @@ -0,0 +1,17 @@ +.SubNavDev { + padding: var(--base-size-4); + border: var(--borderWidth-thick) solid var(--borderColor-default); +} + +.SubNavLinksDev { + margin: var(--base-size-8); +} + +.SubNavLinkDev { + font-weight: var(--text-title-weight-large); + color: var(--fgColor-accent); + + &:is([data-selected]) { + background-color: var(--bgColor-open-emphasis); + } +} diff --git a/packages/react/src/SubNav/SubNav.dev.stories.tsx b/packages/react/src/SubNav/SubNav.dev.stories.tsx new file mode 100644 index 00000000000..69889e38ac0 --- /dev/null +++ b/packages/react/src/SubNav/SubNav.dev.stories.tsx @@ -0,0 +1,92 @@ +import React from 'react' +import type {Meta} from '@storybook/react' +import SubNav from './SubNav' +import type {ComponentProps} from '../utils/types' +import {FeatureFlags} from '../FeatureFlags' + +import styles from './SubNav.dev.module.css' + +export default { + title: 'Components/SubNav/Dev', + component: SubNav, + subcomponents: { + 'SubNav.Link': SubNav.Link, + }, +} as Meta > + +export const WithCss = () => ( + + +) + +export const WithSx = () => ( ++ ++ ++ Home + ++ Documentation + ++ Support + ++ +) + +export const WithSxAndCSS = () => ( ++ ++ Home + ++ Documentation + ++ Support + ++ +) diff --git a/packages/react/src/SubNav/SubNav.module.css b/packages/react/src/SubNav/SubNav.module.css new file mode 100644 index 00000000000..bb6a4c52c81 --- /dev/null +++ b/packages/react/src/SubNav/SubNav.module.css @@ -0,0 +1,69 @@ +.SubNav { + display: flex; + justify-content: space-between; +} + +.Body { + display: flex; + /* stylelint-disable-next-line primer/spacing */ + margin-bottom: -1px; + + & > * { + margin-left: var(--base-size-8); + } + + & > *:first-child { + margin-left: 0; + } +} + +.Actions { + align-self: center; +} + +.Links { + display: flex; +} + +.Link { + display: flex; + min-height: 34px; /* custom values for SubNav */ + padding-right: var(--base-size-16); + padding-left: var(--base-size-16); + font-size: var(--text-body-size-medium); + font-weight: var(--base-text-weight-medium); + /* stylelint-disable-next-line primer/typography */ + line-height: 20px; /* custom values for SubNav */ + color: var(--fgColor-default); + text-align: center; + text-decoration: none; + border-top: var(--borderWidth-thin) solid var(--borderColor-default); + border-right: var(--borderWidth-thin) solid var(--borderColor-default); + border-bottom: var(--borderWidth-thin) solid var(--borderColor-default); + align-items: center; + + &:first-of-type { + border-left: var(--borderWidth-thin) solid var(--borderColor-default); + border-top-left-radius: var(--borderRadius-medium); + border-bottom-left-radius: var(--borderRadius-medium); + } + + &:last-of-type { + border-top-right-radius: var(--borderRadius-medium); + border-bottom-right-radius: var(--borderRadius-medium); + } + + &:hover, + &:focus { + text-decoration: none; + background-color: var(--bgColor-muted); + transition: background-color 0.2s ease; + } + + &:is([data-selected]) { + color: var(--fgColor-onEmphasis); + background-color: var(--bgColor-accent-emphasis); + /* stylelint-disable-next-line primer/colors */ + border-color: var(--bgColor-accent-emphasis); + } +} diff --git a/packages/react/src/SubNav/SubNav.tsx b/packages/react/src/SubNav/SubNav.tsx index 48ccc8e5640..7ab40ad702d 100644 --- a/packages/react/src/SubNav/SubNav.tsx +++ b/packages/react/src/SubNav/SubNav.tsx @@ -6,118 +6,166 @@ import {get} from '../constants' import type {SxProp} from '../sx' import sx from '../sx' import type {ComponentProps} from '../utils/types' +import {toggleStyledComponent} from '../internal/utils/toggleStyledComponent' +import {useFeatureFlag} from '../FeatureFlags' -const ITEM_CLASS = 'SubNav-item' -const SELECTED_CLASS = 'selected' +import styles from './SubNav.module.css' -const SubNavBase = styled.nav+ ++ ++ Home + ++ Documentation + ++ Support + +` - display: flex; - justify-content: space-between; +const CSS_MODULES_FEATURE_FLAG = 'primer_react_css_modules_team' - .SubNav-body { +type StyledSubNavProps = React.ComponentProps<'nav'> & { + actions?: React.ReactNode + align?: 'right' + full?: boolean + label?: string +} & SxProp +type StyledSubNavLinksProps = React.ComponentProps<'div'> & SxProp +type StyledSubNavLinkProps = React.ComponentProps<'a'> & SxProp & {to?: To; selected?: boolean} + +// SubNav + +const StyledSubNav = toggleStyledComponent( + CSS_MODULES_FEATURE_FLAG, + 'nav', + styled.nav ` display: flex; - margin-bottom: -1px; + justify-content: space-between; + + .SubNav-body { + display: flex; + margin-bottom: -1px; - > * { - margin-left: ${get('space.2')}; + > * { + margin-left: ${get('space.2')}; + } + + > *:first-child { + margin-left: 0; + } } - > *:first-child { - margin-left: 0; + .SubNav-actions { + align-self: center; } - } - .SubNav-actions { - align-self: center; - } + ${sx}; + `, +) - ${sx}; -` +const SubNav = React.forwardRef (function SubNav( + {actions, className, children, label, ...rest}, + forwardRef, +) { + const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) + return ( + + + ) +}) +SubNav.displayName = 'SubNav' -export type SubNavProps = { - actions?: React.ReactNode - align?: 'right' - full?: boolean - label?: string -} & ComponentProps{children}+ {actions &&{actions}} ++// SubNav.Links -function SubNav({actions, className, children, label, ...rest}: SubNavProps) { - const classes = clsx(className, 'SubNav') +const StyledSubNavLinks = toggleStyledComponent( + CSS_MODULES_FEATURE_FLAG, + 'div', + styled.div ` + display: flex; + ${sx}; + `, +) + +const SubNavLinks = React.forwardRef (function SubNavLink( + {children, className, ...rest}, + forwardRef, +) { + const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) return ( - - +{children}- {actions &&{actions}} -+ {children} + ) -} - -export type SubNavLinksProps = SxProp +}) +SubNavLinks.displayName = 'SubNav.Links' -const SubNavLinks = styled.div` - display: flex; - ${sx}; -` +// SubNav.Link + +const StyledSubNavLink = toggleStyledComponent( + CSS_MODULES_FEATURE_FLAG, + 'a', + styled.a.attrs (props => ({ + className: clsx('SubNav-item', props.selected && 'selected', props.className), + })) ` + padding-left: ${get('space.3')}; + padding-right: ${get('space.3')}; + font-weight: ${get('fontWeights.semibold')}; + font-size: ${get('fontSizes.1')}; + line-height: 20px; //custom value for SubNav + min-height: 34px; //custom value for SubNav + color: ${get('colors.fg.default')}; + text-align: center; + text-decoration: none; + border-top: 1px solid ${get('colors.border.default')}; + border-bottom: 1px solid ${get('colors.border.default')}; + border-right: 1px solid ${get('colors.border.default')}; + display: flex; + align-items: center; -type StyledSubNavLinkProps = { - to?: To - selected?: boolean -} & SxProp + &:first-of-type { + border-top-left-radius: ${get('radii.2')}; + border-bottom-left-radius: ${get('radii.2')}; + border-left: 1px solid ${get('colors.border.default')}; + } -const SubNavLink = styled.a.attrs (props => ({ - className: clsx(ITEM_CLASS, props.selected && SELECTED_CLASS, props.className), -})) ` - padding-left: ${get('space.3')}; - padding-right: ${get('space.3')}; - font-weight: ${get('fontWeights.semibold')}; - font-size: ${get('fontSizes.1')}; - line-height: 20px; //custom value for SubNav - min-height: 34px; //custom value for SubNav - color: ${get('colors.fg.default')}; - text-align: center; - text-decoration: none; - border-top: 1px solid ${get('colors.border.default')}; - border-bottom: 1px solid ${get('colors.border.default')}; - border-right: 1px solid ${get('colors.border.default')}; - display: flex; - align-items: center; - - &:first-of-type { - border-top-left-radius: ${get('radii.2')}; - border-bottom-left-radius: ${get('radii.2')}; - border-left: 1px solid ${get('colors.border.default')}; - } - - &:last-of-type { - border-top-right-radius: ${get('radii.2')}; - border-bottom-right-radius: ${get('radii.2')}; - } - - &:hover, - &:focus { - text-decoration: none; - background-color: ${get('colors.canvas.subtle')}; - transition: background-color 0.2s ease; + &:last-of-type { + border-top-right-radius: ${get('radii.2')}; + border-bottom-right-radius: ${get('radii.2')}; + } - .SubNav-octicon { - color: ${get('colors.fg.muted')}; + &:hover, + &:focus { + text-decoration: none; + background-color: ${get('colors.canvas.subtle')}; + transition: background-color 0.2s ease; } - } - &.selected { - color: ${get('colors.fg.onEmphasis')}; - background-color: ${get('colors.accent.emphasis')}; - border-color: ${get('colors.accent.emphasis')}; - .SubNav-octicon { + &.selected { color: ${get('colors.fg.onEmphasis')}; + background-color: ${get('colors.accent.emphasis')}; + border-color: ${get('colors.accent.emphasis')}; } - } - ${sx}; -` + ${sx}; + `, +) -SubNavLink.displayName = 'SubNav.Link' +const SubNavLink = React.forwardRef (function SubNavLink( + {children, className, ...rest}, + forwardRef, +) { + const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) + return ( + + {children} + + ) +}) -SubNavLinks.displayName = 'SubNav.Links' +SubNavLink.displayName = 'SubNav.Link' +export type SubNavProps = ComponentProps+export type SubNavLinksProps = ComponentProps export type SubNavLinkProps = ComponentProps export default Object.assign(SubNav, {Link: SubNavLink, Links: SubNavLinks}) diff --git a/packages/react/src/TextInput/TextInput.dev.stories.tsx b/packages/react/src/TextInput/TextInput.dev.stories.tsx new file mode 100644 index 00000000000..2f73acfd8d3 --- /dev/null +++ b/packages/react/src/TextInput/TextInput.dev.stories.tsx @@ -0,0 +1,55 @@ +import React from 'react' +import type {Meta} from '@storybook/react' +import {Box, FormControl} from '..' +import TextInput from '.' +import {textInputExcludedControlKeys} from '../utils/story-helpers' +import {FeatureFlags} from '../FeatureFlags' + +export default { + title: 'Components/TextInput/Dev', + component: TextInput, + parameters: {controls: {exclude: textInputExcludedControlKeys}}, +} as Meta > + +export const WithCSS = () => ( + + +) + +export const WithSx = () => ( ++ ++ +Default label ++ + +) + +export const WithSxAndCSS = () => ( ++ +Default label ++ + +) diff --git a/packages/react/src/TooltipV2/Tooltip.tsx b/packages/react/src/TooltipV2/Tooltip.tsx index 4c574664b31..7a1eebf66a3 100644 --- a/packages/react/src/TooltipV2/Tooltip.tsx +++ b/packages/react/src/TooltipV2/Tooltip.tsx @@ -198,40 +198,72 @@ export const Tooltip = React.forwardRef( const [isPopoverOpen, setIsPopoverOpen] = useState(false) const openTooltip = () => { - if ( - tooltipElRef.current && - triggerRef.current && - tooltipElRef.current.hasAttribute('popover') && - !tooltipElRef.current.matches(':popover-open') - ) { - const tooltip = tooltipElRef.current - const trigger = triggerRef.current - tooltip.showPopover() - setIsPopoverOpen(true) - /* - * TOOLTIP POSITIONING - */ - const settings = { - side: directionToPosition[direction].side, - align: directionToPosition[direction].align, + try { + if ( + tooltipElRef.current && + triggerRef.current && + tooltipElRef.current.hasAttribute('popover') && + !tooltipElRef.current.matches(':popover-open') + ) { + const tooltip = tooltipElRef.current + const trigger = triggerRef.current + tooltip.showPopover() + setIsPopoverOpen(true) + /* + * TOOLTIP POSITIONING + */ + const settings = { + side: directionToPosition[direction].side, + align: directionToPosition[direction].align, + } + const {top, left, anchorAlign, anchorSide} = getAnchoredPosition(tooltip, trigger, settings) + // This is required to make sure the popover is positioned correctly i.e. when there is not enough space on the specified direction, we set a new direction to position the ::after + const calculatedDirection = positionToDirection[`${anchorSide}-${anchorAlign}` as string] + setCalculatedDirection(calculatedDirection) + tooltip.style.top = `${top}px` + tooltip.style.left = `${left}px` + } + } catch (error) { + // older browsers don't support the :popover-open selector and will throw, even though we use a polyfill + // see https://github.com/github/issues/issues/12468 + if ( + error && + typeof error === 'object' && + 'message' in error && + typeof error.message === 'string' && + error.message.includes('not a valid selector') + ) { + // fail silently + } else { + throw error } - const {top, left, anchorAlign, anchorSide} = getAnchoredPosition(tooltip, trigger, settings) - // This is required to make sure the popover is positioned correctly i.e. when there is not enough space on the specified direction, we set a new direction to position the ::after - const calculatedDirection = positionToDirection[`${anchorSide}-${anchorAlign}` as string] - setCalculatedDirection(calculatedDirection) - tooltip.style.top = `${top}px` - tooltip.style.left = `${left}px` } } const closeTooltip = () => { - if ( - tooltipElRef.current && - triggerRef.current && - tooltipElRef.current.hasAttribute('popover') && - tooltipElRef.current.matches(':popover-open') - ) { - tooltipElRef.current.hidePopover() - setIsPopoverOpen(false) + try { + if ( + tooltipElRef.current && + triggerRef.current && + tooltipElRef.current.hasAttribute('popover') && + tooltipElRef.current.matches(':popover-open') + ) { + tooltipElRef.current.hidePopover() + setIsPopoverOpen(false) + } + } catch (error) { + // older browsers don't support the :popover-open selector and will throw, even though we use a polyfill + // see https://github.com/github/issues/issues/12468 + if ( + error && + typeof error === 'object' && + 'message' in error && + typeof error.message === 'string' && + error.message.includes('not a valid selector') + ) { + // fail silently + } else { + throw error + } } } diff --git a/packages/react/src/TreeView/TreeView.features.stories.tsx b/packages/react/src/TreeView/TreeView.features.stories.tsx index b4d9a8181b4..b608bc51fb2 100644 --- a/packages/react/src/TreeView/TreeView.features.stories.tsx +++ b/packages/react/src/TreeView/TreeView.features.stories.tsx @@ -580,7 +580,7 @@ AsyncError.args = { } export const EmptyDirectories: StoryFn = () => { - const [state, setState] = React.useState+ ++ +Default label ++ ('loading') + const [state, setState] = React.useState ('initial') const timeoutId = React.useRef | null>(null) React.useEffect(() => { @@ -597,6 +597,7 @@ export const EmptyDirectories: StoryFn = () => { { + setState('loading') if (expanded) { timeoutId.current = setTimeout(() => { setState('done') diff --git a/packages/react/src/TreeView/TreeView.test.tsx b/packages/react/src/TreeView/TreeView.test.tsx index 056b6714acb..eb2329f9b22 100644 --- a/packages/react/src/TreeView/TreeView.test.tsx +++ b/packages/react/src/TreeView/TreeView.test.tsx @@ -189,6 +189,19 @@ describe('Markup', () => { expect(noDescription).toHaveAccessibleDescription(' ') }) + it('should not have aria-describedby when no leading or trailing visual', () => { + const {getByLabelText} = renderWithTheme( + + , + ) + + const noDescription = getByLabelText(/Item 1/) + expect(noDescription).not.toHaveAccessibleDescription() + expect(noDescription).not.toHaveAttribute('aria-describedby') + }) + it('should include `aria-expanded` when a SubTree contains content', async () => { const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime, @@ -220,7 +233,7 @@ describe('Markup', () => { expect(treeitem).not.toHaveAttribute('aria-expanded') await user.click(getByText(/Item 2/)) - expect(treeitem).not.toHaveAttribute('aria-expanded') + expect(treeitem).toHaveAttribute('aria-expanded', 'true') }) it('should render with containIntrinsicSize', () => { @@ -1537,7 +1550,7 @@ describe('Asyncronous loading', () => { expect(parentItem).toHaveAttribute('aria-expanded', 'true') }) - it('should remove `aria-expanded` if no content is loaded in', async () => { + it('should update `aria-expanded` if no content is loaded in', async () => { function Example() { const [state, setState] = React.useStateItem 1 +Item 2 +('loading') const timeoutId = React.useRef | null>(null) @@ -1584,6 +1597,46 @@ describe('Asyncronous loading', () => { jest.runAllTimers() }) - expect(treeitem).not.toHaveAttribute('aria-expanded') + expect(treeitem).toHaveAttribute('aria-expanded', 'true') + expect(getByLabelText('No items found')).toBeInTheDocument() + }) + + it('should have `aria-expanded` when directory is empty', async () => { + const {getByRole} = renderWithTheme( + + , + ) + + const parentItem = getByRole('treeitem', {name: 'Parent'}) + + // Parent item should be expanded + expect(parentItem).toHaveAttribute('aria-expanded', 'true') + + // Current child should not have `aria-expanded` + expect(getByRole('treeitem', {name: 'child current'})).not.toHaveAttribute('aria-expanded') + + // Empty child should not have `aria-expanded` when closed + expect(getByRole('treeitem', {name: 'empty child'})).not.toHaveAttribute('aria-expanded') + + fireEvent.click(getByRole('treeitem', {name: 'empty child'})) + + // Empty child should have `aria-expanded` when opened + expect(getByRole('treeitem', {name: 'empty child'})).toHaveAttribute('aria-expanded') }) }) diff --git a/packages/react/src/TreeView/TreeView.tsx b/packages/react/src/TreeView/TreeView.tsx index b891aa97a74..a1e4fc882b6 100644 --- a/packages/react/src/TreeView/TreeView.tsx +++ b/packages/react/src/TreeView/TreeView.tsx @@ -361,7 +361,7 @@ export type TreeViewItemProps = { containIntrinsicSize?: string current?: boolean defaultExpanded?: boolean - expanded?: boolean + expanded?: boolean | null onExpandedChange?: (expanded: boolean) => void onSelect?: (event: React.MouseEvent+ ++ + Parent ++ + +child ++ child current + ++ empty child + ++ | React.KeyboardEvent ) => void className?: string @@ -401,7 +401,7 @@ const Item = React.forwardRef ( // If defaultExpanded is not provided, we default to false unless the item // is the current item, in which case we default to true. defaultValue: () => expandedStateCache.current?.get(itemId) ?? defaultExpanded ?? isCurrentItem, - value: expanded, + value: expanded === null ? false : expanded, onChange: onExpandedChange, }) const {level} = React.useContext(ItemContext) @@ -458,6 +458,11 @@ const Item = React.forwardRef ( [onSelect, setIsExpandedWithCache, toggle], ) + const ariaDescribedByIds = [ + slots.leadingVisual ? leadingVisualId : null, + slots.trailingVisual ? trailingVisualId : null, + ].filter(Boolean) + return ( ( role="treeitem" aria-label={ariaLabel} aria-labelledby={ariaLabel ? undefined : ariaLabelledby || labelId} - aria-describedby={`${leadingVisualId} ${trailingVisualId}`} + aria-describedby={ariaDescribedByIds.length ? ariaDescribedByIds.join(' ') : undefined} aria-level={level} - aria-expanded={isSubTreeEmpty ? undefined : isExpanded} + aria-expanded={(isSubTreeEmpty && (!isExpanded || !hasSubTree)) || expanded === null ? undefined : isExpanded} aria-current={isCurrentItem ? 'true' : undefined} aria-selected={isFocused ? 'true' : 'false'} data-has-leading-action={slots.leadingAction ? true : undefined} @@ -697,6 +702,7 @@ const SubTree: React.FC = ({count, state, children}) => { ref={ref} > {state === 'loading' ? : children} + {isSubTreeEmpty && state !== 'loading' ? : null} ) } @@ -785,6 +791,14 @@ const LoadingItem = React.forwardRef (({count}, re ) }) +const EmptyItem = React.forwardRef ((props, ref) => { + return ( + - +
+ ) +}) + function useSubTree(children: React.ReactNode) { return React.useMemo(() => { const subTree = React.Children.toArray(children).find( diff --git a/packages/react/src/VisuallyHidden/VisuallyHidden.dev.stories.tsx b/packages/react/src/VisuallyHidden/VisuallyHidden.dev.stories.tsx new file mode 100644 index 00000000000..2941dcf449a --- /dev/null +++ b/packages/react/src/VisuallyHidden/VisuallyHidden.dev.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react' +import type {Meta} from '@storybook/react' +import {VisuallyHidden} from './VisuallyHidden' + +export default { + title: 'Components/VisuallyHidden/Dev', + component: VisuallyHidden, +} as MetaNo items found ++ +export const Default = () => ( + + Visible Text ++) diff --git a/packages/react/src/VisuallyHidden/VisuallyHidden.module.css b/packages/react/src/VisuallyHidden/VisuallyHidden.module.css new file mode 100644 index 00000000000..0b7ea0f8148 --- /dev/null +++ b/packages/react/src/VisuallyHidden/VisuallyHidden.module.css @@ -0,0 +1,10 @@ +.VisuallyHidden { + &:not(:focus):not(:active):not(:focus-within) { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + white-space: nowrap; + clip-path: inset(50%); + } +} diff --git a/packages/react/src/VisuallyHidden/VisuallyHidden.tsx b/packages/react/src/VisuallyHidden/VisuallyHidden.tsx index 546b6d2d812..da4e99be21b 100644 --- a/packages/react/src/VisuallyHidden/VisuallyHidden.tsx +++ b/packages/react/src/VisuallyHidden/VisuallyHidden.tsx @@ -1,6 +1,13 @@ import styled from 'styled-components' import type {SxProp} from '../sx' import sx from '../sx' +import {toggleStyledComponent} from '../internal/utils/toggleStyledComponent' +import {clsx} from 'clsx' +import {useFeatureFlag} from '../FeatureFlags' +import React, {type HTMLAttributes} from 'react' +import classes from './VisuallyHidden.module.css' + +const CSS_MODULES_FEATURE_FLAG = 'primer_react_css_modules_team' /** * Provides a component that implements the "visually hidden" technique. This is @@ -12,17 +19,34 @@ import sx from '../sx' * * @see https://www.scottohara.me/blog/2023/03/21/visually-hidden-hack.html */ -export const VisuallyHidden = styled.spanVisually Hidden Text +` - &:not(:focus):not(:active):not(:focus-within) { - clip-path: inset(50%); - height: 1px; - overflow: hidden; - position: absolute; - white-space: nowrap; - width: 1px; - } +const StyledVisuallyHidden = toggleStyledComponent( + CSS_MODULES_FEATURE_FLAG, + 'span', + styled.span ` + &:not(:focus):not(:active):not(:focus-within) { + clip-path: inset(50%); + height: 1px; + overflow: hidden; + position: absolute; + white-space: nowrap; + width: 1px; + } + + ${sx} + `, +) - ${sx} -` +export const VisuallyHidden = ({className, children, ...rest}: VisuallyHiddenProps) => { + const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG) + return ( + + {children} + + ) +} -export type VisuallyHiddenProps = React.ComponentPropsWithoutRef+export type VisuallyHiddenProps = React.PropsWithChildren< + HTMLAttributes & { + className?: string + } & SxProp +> diff --git a/packages/react/src/__tests__/Header.test.tsx b/packages/react/src/__tests__/Header.test.tsx index c4af28bc8ac..e084cc9e8c6 100644 --- a/packages/react/src/__tests__/Header.test.tsx +++ b/packages/react/src/__tests__/Header.test.tsx @@ -3,6 +3,7 @@ import {Header} from '..' import {render, behavesAsComponent, checkExports} from '../utils/testing' import {render as HTMLRender} from '@testing-library/react' import axe from 'axe-core' +import {FeatureFlags} from '../FeatureFlags' describe('Header', () => { behavesAsComponent({Component: Header}) @@ -13,10 +14,52 @@ describe('Header', () => { describe('Header.Item', () => { behavesAsComponent({Component: Header.Item}) + + it('accepts and applies className', () => { + expect(render( ).props.className).toContain('primer') + }) + + it('should support `className` on the outermost element', () => { + const Element = () => + const FeatureFlagElement = () => { + return ( + + + ) + } + expect(HTMLRender(+ ).container.firstChild).toHaveClass('test-class-name') + expect(HTMLRender( ).container.firstChild).toHaveClass('test-class-name') + }) }) describe('Header.Link', () => { behavesAsComponent({Component: Header.Link}) + + it('should support `className` on the outermost element', () => { + const Element = () => + const FeatureFlagElement = () => { + return ( + + + ) + } + expect(HTMLRender(+ ).container.firstChild).toHaveClass('test-class-name') + expect(HTMLRender( ).container.firstChild).toHaveClass('test-class-name') + }) }) it('should have no axe violations', async () => { @@ -43,4 +86,23 @@ describe('Header', () => { it('sets aria-label appropriately', () => { expect(render( ).props['aria-label']).toEqual('Test label') }) + + it('should support `className` on the outermost element', () => { + const Element = () => + const FeatureFlagElement = () => { + return ( + + + ) + } + expect(HTMLRender(+ ).container.firstChild).toHaveClass('test-class-name') + expect(HTMLRender( ).container.firstChild).toHaveClass('test-class-name') + }) }) diff --git a/packages/react/src/__tests__/LabelGroup.test.tsx b/packages/react/src/__tests__/LabelGroup.test.tsx index b2e8f6192f5..77a89f65581 100644 --- a/packages/react/src/__tests__/LabelGroup.test.tsx +++ b/packages/react/src/__tests__/LabelGroup.test.tsx @@ -177,4 +177,127 @@ describe('LabelGroup', () => { expect(document.activeElement).toEqual(getByText('+2').closest('button')) }) + + describe('should render as ul by default', () => { + it('without truncation', () => { + const {getByRole} = HTMLRender( + + , + ) + const list = getByRole('list') + expect(list).not.toBeNull() + expect(list.tagName).toBe('UL') + expect(list).toHaveAttribute('data-list', 'true') + expect(list.querySelectorAll('li')).toHaveLength(5) + }) + + it('with truncation', () => { + const {getByRole} = HTMLRender( ++ + + + + + ++ , + ) + const list = getByRole('list') + expect(list).not.toBeNull() + expect(list.tagName).toBe('UL') + expect(list).toHaveAttribute('data-list', 'true') + // account for "show more" button + expect(list.querySelectorAll('li')).toHaveLength(6) + }) + }) + + describe('should render as custom element when `as` is provided', () => { + it('without truncation', () => { + const {queryByRole, container} = HTMLRender( ++ + + + + + ++ , + ) + const list = queryByRole('list') + expect(list).toBeNull() + const labelGroupDiv = container.querySelectorAll('div')[1] + expect(labelGroupDiv.querySelectorAll('li')).toHaveLength(0) + expect(labelGroupDiv.querySelectorAll('span')).toHaveLength(5) + expect(labelGroupDiv).not.toHaveAttribute('data-list') + }) + + it('with truncation', () => { + const {queryByRole, container} = HTMLRender( ++ + + + + + ++ , + ) + const list = queryByRole('list') + expect(list).toBeNull() + const labelGroupDiv = container.querySelectorAll('div')[1] + expect(labelGroupDiv.querySelectorAll('li')).toHaveLength(0) + expect(labelGroupDiv.querySelectorAll(':scope > span')).toHaveLength(5) + expect(labelGroupDiv).not.toHaveAttribute('data-list') + }) + }) + + describe('should render children as list items when rendered as ol', () => { + it('without truncation', () => { + const {getByRole} = HTMLRender( ++ + + + + + ++ , + ) + const list = getByRole('list') + expect(list).not.toBeNull() + expect(list.tagName).toBe('OL') + expect(list).toHaveAttribute('data-list', 'true') + expect(list.querySelectorAll('li')).toHaveLength(5) + }) + it('with truncation', () => { + const {getByRole} = HTMLRender( ++ + + + + + ++ , + ) + const list = getByRole('list') + expect(list).not.toBeNull() + expect(list.tagName).toBe('OL') + expect(list).toHaveAttribute('data-list', 'true') + // account for "show more" button + expect(list.querySelectorAll('li')).toHaveLength(6) + }) + }) }) diff --git a/packages/react/src/__tests__/ProgressBar.test.tsx b/packages/react/src/__tests__/ProgressBar.test.tsx index 498154a56ea..ead938b3e18 100644 --- a/packages/react/src/__tests__/ProgressBar.test.tsx +++ b/packages/react/src/__tests__/ProgressBar.test.tsx @@ -6,7 +6,7 @@ import axe from 'axe-core' import {FeatureFlags} from '../FeatureFlags' describe('ProgressBar', () => { - behavesAsComponent({Component: ProgressBar}) + behavesAsComponent({Component: ProgressBar, toRender: () =>+ + + + + + +}) checkExports('ProgressBar', { default: undefined, @@ -72,4 +72,50 @@ describe('ProgressBar', () => { it('respects the "progress" prop', () => { expect(render( )).toMatchSnapshot() }) + + it('passed the `aria-label` down to the progress bar', () => { + const {getByRole, getByLabelText} = HTMLRender( ) + expect(getByRole('progressbar')).toHaveAttribute('aria-label', 'Upload test.png') + expect(getByLabelText('Upload test.png')).toBeInTheDocument() + }) + + it('passed the `aria-valuenow` down to the progress bar', () => { + const {getByRole} = HTMLRender( ) + expect(getByRole('progressbar')).toHaveAttribute('aria-valuenow', '80') + }) + + it('passed the `aria-valuetext` down to the progress bar', () => { + const {getByRole} = HTMLRender( ) + expect(getByRole('progressbar')).toHaveAttribute('aria-valuetext', '80 percent') + }) + + it('does not pass the `aria-label` down to the progress bar if there are multiple items', () => { + const {getByRole} = HTMLRender( + + , + ) + expect(getByRole('progressbar')).not.toHaveAttribute('aria-label') + }) + + it('passes aria attributes to the progress bar item', () => { + const {getByRole} = HTMLRender( ++ + , + ) + expect(getByRole('progressbar')).toHaveAttribute('aria-valuenow', '50') + expect(getByRole('progressbar')).toHaveAttribute('aria-label', 'Progress') + }) + + it('provides `aria-valuenow` to the progress bar item if it is not already provided', () => { + const {getByRole} = HTMLRender(+ ) + expect(getByRole('progressbar')).toHaveAttribute('aria-valuenow', '50') + }) + + it('applies `0` as a value for `aria-valuenow`', () => { + const {getByRole} = HTMLRender( ) + + expect(getByRole('progressbar')).toHaveAttribute('aria-valuenow', '0') + }) }) diff --git a/packages/react/src/__tests__/__snapshots__/AnchoredOverlay.test.tsx.snap b/packages/react/src/__tests__/__snapshots__/AnchoredOverlay.test.tsx.snap index 786ea165f9b..38a2450ee5b 100644 --- a/packages/react/src/__tests__/__snapshots__/AnchoredOverlay.test.tsx.snap +++ b/packages/react/src/__tests__/__snapshots__/AnchoredOverlay.test.tsx.snap @@ -313,6 +313,10 @@ exports[`AnchoredOverlay should render consistently when open 1`] = ` outline: none; } +.c3[data-reflow-container='true'] { + max-width: calc(100vw - 2rem); +} + @media (forced-colors:active) { .c1:focus { outline: solid 1px transparent; diff --git a/packages/react/src/__tests__/__snapshots__/Autocomplete.test.tsx.snap b/packages/react/src/__tests__/__snapshots__/Autocomplete.test.tsx.snap index 9637ecc100e..c3dd83b8058 100644 --- a/packages/react/src/__tests__/__snapshots__/Autocomplete.test.tsx.snap +++ b/packages/react/src/__tests__/__snapshots__/Autocomplete.test.tsx.snap @@ -327,14 +327,7 @@ exports[`snapshots renders a loading state 1`] = ` justify-content: center; } -.c2 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; -} - -.c4:not(:focus):not(:active):not(:focus-within) { +.c3:not(:focus):not(:active):not(:focus-within) { -webkit-clip-path: inset(50%); clip-path: inset(50%); height: 1px; @@ -344,7 +337,7 @@ exports[`snapshots renders a loading state 1`] = ` width: 1px; } -.c3 { +.c2 { -webkit-animation: rotate-keyframes 1s linear infinite; animation: rotate-keyframes 1s linear infinite; } @@ -374,12 +367,12 @@ exports[`snapshots renders a loading state 1`] = ` display="flex" > Loading diff --git a/packages/react/src/__tests__/__snapshots__/ProgressBar.test.tsx.snap b/packages/react/src/__tests__/__snapshots__/ProgressBar.test.tsx.snap index cd0422b606b..f47852ca0bc 100644 --- a/packages/react/src/__tests__/__snapshots__/ProgressBar.test.tsx.snap +++ b/packages/react/src/__tests__/__snapshots__/ProgressBar.test.tsx.snap @@ -34,16 +34,15 @@ exports[`ProgressBar respects the "progress" prop 1`] = ` } `; diff --git a/packages/react/src/__tests__/__snapshots__/SubNavLink.test.tsx.snap b/packages/react/src/__tests__/__snapshots__/SubNavLink.test.tsx.snap index 53f3a831a46..b9018c8172a 100644 --- a/packages/react/src/__tests__/__snapshots__/SubNavLink.test.tsx.snap +++ b/packages/react/src/__tests__/__snapshots__/SubNavLink.test.tsx.snap @@ -45,23 +45,15 @@ exports[`SubNav.Link respects the "selected" prop 1`] = ` transition: background-color 0.2s ease; } -.c0:hover .SubNav-octicon, -.c0:focus .SubNav-octicon { - color: var(--fgColor-muted,var(--color-fg-muted,#656d76)); -} - .c0.selected { color: var(--fgColor-onEmphasis,var(--color-fg-on-emphasis,#ffffff)); background-color: var(--bgColor-accent-emphasis,var(--color-accent-emphasis,#0969da)); border-color: var(--bgColor-accent-emphasis,var(--color-accent-emphasis,#0969da)); } -.c0.selected .SubNav-octicon { - color: var(--fgColor-onEmphasis,var(--color-fg-on-emphasis,#ffffff)); -} - `; diff --git a/packages/react/src/__tests__/__snapshots__/TextInput.test.tsx.snap b/packages/react/src/__tests__/__snapshots__/TextInput.test.tsx.snap index 2c8c4fe8878..2c67fb764da 100644 --- a/packages/react/src/__tests__/__snapshots__/TextInput.test.tsx.snap +++ b/packages/react/src/__tests__/__snapshots__/TextInput.test.tsx.snap @@ -4038,13 +4038,6 @@ exports[`TextInput renders with a loading indicator 1`] = ` } .c5 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; -} - -.c6 { -webkit-animation: rotate-keyframes 1s linear infinite; animation: rotate-keyframes 1s linear infinite; visibility: visible; @@ -4212,11 +4205,11 @@ exports[`TextInput renders with a loading indicator 1`] = ` id=":r2n:" > Loading @@ -5008,11 +4980,11 @@ exports[`TextInput renders with a loading indicator 1`] = ` id=":r34:" > Loading @@ -5316,11 +5281,11 @@ exports[`TextInput renders with a loading indicator 1`] = ` id=":r39:" > Loading @@ -5624,11 +5582,11 @@ exports[`TextInput renders with a loading indicator 1`] = ` id=":r3e:" > Loading @@ -6160,7 +6104,7 @@ exports[`TextInput renders with a loading indicator 1`] = ` id=":r3n:" > Loading @@ -6774,7 +6704,7 @@ exports[`TextInput renders with a loading indicator 1`] = ` id=":r40:" > Loading @@ -7121,7 +7044,7 @@ exports[`TextInput renders with a loading indicator 1`] = ` id=":r45:" > Loading @@ -7475,7 +7391,7 @@ exports[`TextInput renders with a loading indicator 1`] = ` id=":r4a:" > , - .c6 { + .c5 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -13857,7 +13794,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` flex-grow: 1; } -.c6 > * { +.c5 > * { -webkit-flex-shrink: 0; -ms-flex-negative: 0; flex-shrink: 0; @@ -13865,7 +13802,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` margin-bottom: 0.25rem; } -.c7 { +.c6 { -webkit-order: 1; -ms-flex-order: 1; order: 1; @@ -13883,22 +13820,15 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` position: relative; } -.c4 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; -} - .c3 { visibility: hidden; } -.c13 { +.c12 { visibility: visible; } -.c5 { +.c4 { -webkit-animation: rotate-keyframes 1s linear infinite; animation: rotate-keyframes 1s linear infinite; position: absolute; @@ -13909,7 +13839,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` left: 0; } -.c14 { +.c13 { -webkit-animation: rotate-keyframes 1s linear infinite; animation: rotate-keyframes 1s linear infinite; position: absolute; @@ -13920,7 +13850,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` right: 0; } -.c11 { +.c10 { position: absolute; width: 1px; height: 1px; @@ -14040,7 +13970,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` padding-right: 0; } -.c8 { +.c7 { border: 0; font-size: inherit; font-family: inherit; @@ -14051,11 +13981,11 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` height: 100%; } -.c8:focus { +.c7:focus { outline: 0; } -.c9 { +.c8 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -14086,13 +14016,13 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` padding-right: 0; } -.c9:hover { +.c8:hover { background-color: var(--bgColor-neutral-muted,var(--color-neutral-muted,rgba(175,184,193,0.2))); box-shadow: var(--shadow-resting-medium,var(--color-shadow-medium,0 3px 6px rgba(140,149,159,0.15))); color: var(--fgColor-default,var(--color-fg-default,#1F2328)); } -.c12 { +.c11 { background-color: transparent; font-family: inherit; color: currentColor; @@ -14132,16 +14062,16 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` width: 16px; } -.c12:hover, -.c12:focus { +.c11:hover, +.c11:focus { background-color: var(--bgColor-neutral-muted,var(--color-neutral-muted,rgba(175,184,193,0.2))); } -.c12:active { +.c11:active { background-color: var(--bgColor-neutral-muted,var(--color-neutral-subtle,rgba(234,238,242,0.5))); } -.c10 { +.c9 { -webkit-box-flex: 1; -webkit-flex-grow: 1; -ms-flex-positive: 1; @@ -14166,11 +14096,11 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` text-decoration: none; } -.c10:is(a,button,[tabIndex='0']) { +.c9:is(a,button,[tabIndex='0']) { cursor: pointer; } -.c10:is(a,button,[tabIndex='0']):after { +.c9:is(a,button,[tabIndex='0']):after { content: ''; position: absolute; left: 0; @@ -14227,11 +14157,11 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = `, - .c5 { + .c4 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -7066,7 +7059,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` flex-grow: 1; } -.c5 > * { +.c4 > * { -webkit-flex-shrink: 0; -ms-flex-negative: 0; flex-shrink: 0; @@ -7074,7 +7067,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` margin-bottom: 0.25rem; } -.c6 { +.c5 { -webkit-order: 1; -ms-flex-order: 1; order: 1; @@ -7093,25 +7086,18 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` } .c3 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; -} - -.c4 { -webkit-animation: rotate-keyframes 1s linear infinite; animation: rotate-keyframes 1s linear infinite; visibility: visible; } -.c12 { +.c11 { -webkit-animation: rotate-keyframes 1s linear infinite; animation: rotate-keyframes 1s linear infinite; visibility: hidden; } -.c10 { +.c9 { position: absolute; width: 1px; height: 1px; @@ -7224,7 +7210,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` padding-right: 12px; } -.c7 { +.c6 { border: 0; font-size: inherit; font-family: inherit; @@ -7235,11 +7221,11 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` height: 100%; } -.c7:focus { +.c6:focus { outline: 0; } -.c8 { +.c7 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -7272,13 +7258,13 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` padding-right: 0; } -.c8:hover { +.c7:hover { background-color: var(--bgColor-neutral-muted,var(--color-neutral-muted,rgba(175,184,193,0.2))); box-shadow: var(--shadow-resting-medium,var(--color-shadow-medium,0 3px 6px rgba(140,149,159,0.15))); color: var(--fgColor-default,var(--color-fg-default,#1F2328)); } -.c11 { +.c10 { background-color: transparent; font-family: inherit; color: currentColor; @@ -7318,16 +7304,16 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` width: 32px; } -.c11:hover, -.c11:focus { +.c10:hover, +.c10:focus { background-color: var(--bgColor-neutral-muted,var(--color-neutral-muted,rgba(175,184,193,0.2))); } -.c11:active { +.c10:active { background-color: var(--bgColor-neutral-muted,var(--color-neutral-subtle,rgba(234,238,242,0.5))); } -.c9 { +.c8 { -webkit-box-flex: 1; -webkit-flex-grow: 1; -ms-flex-positive: 1; @@ -7352,11 +7338,11 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` text-decoration: none; } -.c9:is(a,button,[tabIndex='0']) { +.c8:is(a,button,[tabIndex='0']) { cursor: pointer; } -.c9:is(a,button,[tabIndex='0']):after { +.c8:is(a,button,[tabIndex='0']):after { content: ''; position: absolute; left: 0; @@ -7388,11 +7374,11 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` display="flex" > zero (press backspace or delete to remove) one (press backspace or delete to remove) two (press backspace or delete to remove) three (press backspace or delete to remove) four (press backspace or delete to remove) five (press backspace or delete to remove) six (press backspace or delete to remove) seven (press backspace or delete to remove) zero (press backspace or delete to remove) one (press backspace or delete to remove) two (press backspace or delete to remove) three (press backspace or delete to remove) four (press backspace or delete to remove) five (press backspace or delete to remove) six (press backspace or delete to remove) seven (press backspace or delete to remove) , - .c6 { + .c5 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -9570,7 +9542,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` flex-grow: 1; } -.c6 > * { +.c5 > * { -webkit-flex-shrink: 0; -ms-flex-negative: 0; flex-shrink: 0; @@ -9578,7 +9550,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` margin-bottom: 0.25rem; } -.c7 { +.c6 { -webkit-order: 1; -ms-flex-order: 1; order: 1; @@ -9596,24 +9568,17 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` position: relative; } -.c4 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; -} - .c3 { visibility: hidden; } -.c13 { +.c12 { -webkit-animation: rotate-keyframes 1s linear infinite; animation: rotate-keyframes 1s linear infinite; visibility: hidden; } -.c5 { +.c4 { -webkit-animation: rotate-keyframes 1s linear infinite; animation: rotate-keyframes 1s linear infinite; position: absolute; @@ -9624,7 +9589,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` left: 0; } -.c11 { +.c10 { position: absolute; width: 1px; height: 1px; @@ -9737,7 +9702,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` padding-right: 12px; } -.c8 { +.c7 { border: 0; font-size: inherit; font-family: inherit; @@ -9748,11 +9713,11 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` height: 100%; } -.c8:focus { +.c7:focus { outline: 0; } -.c9 { +.c8 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -9785,13 +9750,13 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` padding-right: 0; } -.c9:hover { +.c8:hover { background-color: var(--bgColor-neutral-muted,var(--color-neutral-muted,rgba(175,184,193,0.2))); box-shadow: var(--shadow-resting-medium,var(--color-shadow-medium,0 3px 6px rgba(140,149,159,0.15))); color: var(--fgColor-default,var(--color-fg-default,#1F2328)); } -.c12 { +.c11 { background-color: transparent; font-family: inherit; color: currentColor; @@ -9831,16 +9796,16 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` width: 32px; } -.c12:hover, -.c12:focus { +.c11:hover, +.c11:focus { background-color: var(--bgColor-neutral-muted,var(--color-neutral-muted,rgba(175,184,193,0.2))); } -.c12:active { +.c11:active { background-color: var(--bgColor-neutral-muted,var(--color-neutral-subtle,rgba(234,238,242,0.5))); } -.c10 { +.c9 { -webkit-box-flex: 1; -webkit-flex-grow: 1; -ms-flex-positive: 1; @@ -9865,11 +9830,11 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` text-decoration: none; } -.c10:is(a,button,[tabIndex='0']) { +.c9:is(a,button,[tabIndex='0']) { cursor: pointer; } -.c10:is(a,button,[tabIndex='0']):after { +.c9:is(a,button,[tabIndex='0']):after { content: ''; position: absolute; left: 0; @@ -9926,11 +9891,11 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` zero (press backspace or delete to remove) one (press backspace or delete to remove) two (press backspace or delete to remove) three (press backspace or delete to remove) four (press backspace or delete to remove) five (press backspace or delete to remove) six (press backspace or delete to remove) seven (press backspace or delete to remove) , - .c6 { + .c5 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -10447,7 +10412,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` flex-grow: 1; } -.c6 > * { +.c5 > * { -webkit-flex-shrink: 0; -ms-flex-negative: 0; flex-shrink: 0; @@ -10455,7 +10420,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` margin-bottom: 0.25rem; } -.c7 { +.c6 { -webkit-order: 1; -ms-flex-order: 1; order: 1; @@ -10473,24 +10438,17 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` position: relative; } -.c4 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; -} - .c3 { visibility: visible; } -.c13 { +.c12 { -webkit-animation: rotate-keyframes 1s linear infinite; animation: rotate-keyframes 1s linear infinite; visibility: visible; } -.c5 { +.c4 { -webkit-animation: rotate-keyframes 1s linear infinite; animation: rotate-keyframes 1s linear infinite; position: absolute; @@ -10501,7 +10459,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` left: 0; } -.c11 { +.c10 { position: absolute; width: 1px; height: 1px; @@ -10614,7 +10572,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` padding-right: 0; } -.c8 { +.c7 { border: 0; font-size: inherit; font-family: inherit; @@ -10625,11 +10583,11 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` height: 100%; } -.c8:focus { +.c7:focus { outline: 0; } -.c9 { +.c8 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -10662,13 +10620,13 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` padding-right: 0; } -.c9:hover { +.c8:hover { background-color: var(--bgColor-neutral-muted,var(--color-neutral-muted,rgba(175,184,193,0.2))); box-shadow: var(--shadow-resting-medium,var(--color-shadow-medium,0 3px 6px rgba(140,149,159,0.15))); color: var(--fgColor-default,var(--color-fg-default,#1F2328)); } -.c12 { +.c11 { background-color: transparent; font-family: inherit; color: currentColor; @@ -10708,16 +10666,16 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` width: 32px; } -.c12:hover, -.c12:focus { +.c11:hover, +.c11:focus { background-color: var(--bgColor-neutral-muted,var(--color-neutral-muted,rgba(175,184,193,0.2))); } -.c12:active { +.c11:active { background-color: var(--bgColor-neutral-muted,var(--color-neutral-subtle,rgba(234,238,242,0.5))); } -.c10 { +.c9 { -webkit-box-flex: 1; -webkit-flex-grow: 1; -ms-flex-positive: 1; @@ -10742,11 +10700,11 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` text-decoration: none; } -.c10:is(a,button,[tabIndex='0']) { +.c9:is(a,button,[tabIndex='0']) { cursor: pointer; } -.c10:is(a,button,[tabIndex='0']):after { +.c9:is(a,button,[tabIndex='0']):after { content: ''; position: absolute; left: 0; @@ -10803,11 +10761,11 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` zero (press backspace or delete to remove) one (press backspace or delete to remove) two (press backspace or delete to remove) three (press backspace or delete to remove) four (press backspace or delete to remove) five (press backspace or delete to remove) six (press backspace or delete to remove) seven (press backspace or delete to remove), - .c5 { + .c4 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -12152,7 +12103,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` flex-grow: 1; } -.c5 > * { +.c4 > * { -webkit-flex-shrink: 0; -ms-flex-negative: 0; flex-shrink: 0; @@ -12160,7 +12111,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` margin-bottom: 0.25rem; } -.c6 { +.c5 { -webkit-order: 1; -ms-flex-order: 1; order: 1; @@ -12178,24 +12129,17 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` position: relative; } -.c3 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; -} - -.c12 { +.c11 { visibility: visible; } -.c4 { +.c3 { -webkit-animation: rotate-keyframes 1s linear infinite; animation: rotate-keyframes 1s linear infinite; visibility: visible; } -.c13 { +.c12 { -webkit-animation: rotate-keyframes 1s linear infinite; animation: rotate-keyframes 1s linear infinite; position: absolute; @@ -12206,7 +12150,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` right: 0; } -.c10 { +.c9 { position: absolute; width: 1px; height: 1px; @@ -12319,7 +12263,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` padding-right: 0; } -.c7 { +.c6 { border: 0; font-size: inherit; font-family: inherit; @@ -12330,11 +12274,11 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` height: 100%; } -.c7:focus { +.c6:focus { outline: 0; } -.c8 { +.c7 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -12367,13 +12311,13 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` padding-right: 0; } -.c8:hover { +.c7:hover { background-color: var(--bgColor-neutral-muted,var(--color-neutral-muted,rgba(175,184,193,0.2))); box-shadow: var(--shadow-resting-medium,var(--color-shadow-medium,0 3px 6px rgba(140,149,159,0.15))); color: var(--fgColor-default,var(--color-fg-default,#1F2328)); } -.c11 { +.c10 { background-color: transparent; font-family: inherit; color: currentColor; @@ -12413,16 +12357,16 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` width: 32px; } -.c11:hover, -.c11:focus { +.c10:hover, +.c10:focus { background-color: var(--bgColor-neutral-muted,var(--color-neutral-muted,rgba(175,184,193,0.2))); } -.c11:active { +.c10:active { background-color: var(--bgColor-neutral-muted,var(--color-neutral-subtle,rgba(234,238,242,0.5))); } -.c9 { +.c8 { -webkit-box-flex: 1; -webkit-flex-grow: 1; -ms-flex-positive: 1; @@ -12447,11 +12391,11 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` text-decoration: none; } -.c9:is(a,button,[tabIndex='0']) { +.c8:is(a,button,[tabIndex='0']) { cursor: pointer; } -.c9:is(a,button,[tabIndex='0']):after { +.c8:is(a,button,[tabIndex='0']):after { content: ''; position: absolute; left: 0; @@ -12483,11 +12427,11 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` display="flex" > zero (press backspace or delete to remove) one (press backspace or delete to remove) two (press backspace or delete to remove) three (press backspace or delete to remove) four (press backspace or delete to remove) five (press backspace or delete to remove) six (press backspace or delete to remove) seven (press backspace or delete to remove) zero (press backspace or delete to remove) one (press backspace or delete to remove) two (press backspace or delete to remove) three (press backspace or delete to remove) four (press backspace or delete to remove) five (press backspace or delete to remove) six (press backspace or delete to remove) seven (press backspace or delete to remove) , - .c6 { + .c5 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -14778,7 +14708,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` flex-grow: 1; } -.c6 > * { +.c5 > * { -webkit-flex-shrink: 0; -ms-flex-negative: 0; flex-shrink: 0; @@ -14786,7 +14716,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` margin-bottom: 0.25rem; } -.c7 { +.c6 { -webkit-order: 1; -ms-flex-order: 1; order: 1; @@ -14804,22 +14734,15 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` position: relative; } -.c4 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; -} - .c3 { visibility: hidden; } -.c13 { +.c12 { visibility: visible; } -.c5 { +.c4 { -webkit-animation: rotate-keyframes 1s linear infinite; animation: rotate-keyframes 1s linear infinite; position: absolute; @@ -14830,7 +14753,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` left: 0; } -.c14 { +.c13 { -webkit-animation: rotate-keyframes 1s linear infinite; animation: rotate-keyframes 1s linear infinite; position: absolute; @@ -14841,7 +14764,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` right: 0; } -.c11 { +.c10 { position: absolute; width: 1px; height: 1px; @@ -14954,7 +14877,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` padding-right: 0; } -.c8 { +.c7 { border: 0; font-size: inherit; font-family: inherit; @@ -14965,11 +14888,11 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` height: 100%; } -.c8:focus { +.c7:focus { outline: 0; } -.c9 { +.c8 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -15002,13 +14925,13 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` padding-right: 0; } -.c9:hover { +.c8:hover { background-color: var(--bgColor-neutral-muted,var(--color-neutral-muted,rgba(175,184,193,0.2))); box-shadow: var(--shadow-resting-medium,var(--color-shadow-medium,0 3px 6px rgba(140,149,159,0.15))); color: var(--fgColor-default,var(--color-fg-default,#1F2328)); } -.c12 { +.c11 { background-color: transparent; font-family: inherit; color: currentColor; @@ -15048,16 +14971,16 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` width: 32px; } -.c12:hover, -.c12:focus { +.c11:hover, +.c11:focus { background-color: var(--bgColor-neutral-muted,var(--color-neutral-muted,rgba(175,184,193,0.2))); } -.c12:active { +.c11:active { background-color: var(--bgColor-neutral-muted,var(--color-neutral-subtle,rgba(234,238,242,0.5))); } -.c10 { +.c9 { -webkit-box-flex: 1; -webkit-flex-grow: 1; -ms-flex-positive: 1; @@ -15082,11 +15005,11 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` text-decoration: none; } -.c10:is(a,button,[tabIndex='0']) { +.c9:is(a,button,[tabIndex='0']) { cursor: pointer; } -.c10:is(a,button,[tabIndex='0']):after { +.c9:is(a,button,[tabIndex='0']):after { content: ''; position: absolute; left: 0; @@ -15143,11 +15066,11 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` zero (press backspace or delete to remove) one (press backspace or delete to remove) two (press backspace or delete to remove) three (press backspace or delete to remove) four (press backspace or delete to remove) five (press backspace or delete to remove) six (press backspace or delete to remove) seven (press backspace or delete to remove) , - .c6 { + .c5 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -15694,7 +15617,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` flex-grow: 1; } -.c6 > * { +.c5 > * { -webkit-flex-shrink: 0; -ms-flex-negative: 0; flex-shrink: 0; @@ -15702,7 +15625,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` margin-bottom: 0.25rem; } -.c7 { +.c6 { -webkit-order: 1; -ms-flex-order: 1; order: 1; @@ -15720,14 +15643,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` position: relative; } -.c4 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; -} - -.c13 { +.c12 { visibility: hidden; } @@ -15735,7 +15651,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` visibility: visible; } -.c5 { +.c4 { -webkit-animation: rotate-keyframes 1s linear infinite; animation: rotate-keyframes 1s linear infinite; position: absolute; @@ -15746,7 +15662,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` left: 0; } -.c14 { +.c13 { -webkit-animation: rotate-keyframes 1s linear infinite; animation: rotate-keyframes 1s linear infinite; position: absolute; @@ -15757,7 +15673,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` right: 0; } -.c11 { +.c10 { position: absolute; width: 1px; height: 1px; @@ -15870,7 +15786,7 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` padding-right: 0; } -.c8 { +.c7 { border: 0; font-size: inherit; font-family: inherit; @@ -15881,11 +15797,11 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` height: 100%; } -.c8:focus { +.c7:focus { outline: 0; } -.c9 { +.c8 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -15916,13 +15832,13 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` padding-right: 0; } -.c9:hover { +.c8:hover { background-color: var(--bgColor-neutral-muted,var(--color-neutral-muted,rgba(175,184,193,0.2))); box-shadow: var(--shadow-resting-medium,var(--color-shadow-medium,0 3px 6px rgba(140,149,159,0.15))); color: var(--fgColor-default,var(--color-fg-default,#1F2328)); } -.c12 { +.c11 { background-color: transparent; font-family: inherit; color: currentColor; @@ -15962,16 +15878,16 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` width: 24px; } -.c12:hover, -.c12:focus { +.c11:hover, +.c11:focus { background-color: var(--bgColor-neutral-muted,var(--color-neutral-muted,rgba(175,184,193,0.2))); } -.c12:active { +.c11:active { background-color: var(--bgColor-neutral-muted,var(--color-neutral-subtle,rgba(234,238,242,0.5))); } -.c10 { +.c9 { -webkit-box-flex: 1; -webkit-flex-grow: 1; -ms-flex-positive: 1; @@ -15996,11 +15912,11 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` text-decoration: none; } -.c10:is(a,button,[tabIndex='0']) { +.c9:is(a,button,[tabIndex='0']) { cursor: pointer; } -.c10:is(a,button,[tabIndex='0']):after { +.c9:is(a,button,[tabIndex='0']):after { content: ''; position: absolute; left: 0; @@ -16057,11 +15973,11 @@ exports[`TextInputWithTokens renders with a loading indicator 1`] = ` zero (press backspace or delete to remove) one (press backspace or delete to remove) two (press backspace or delete to remove) three (press backspace or delete to remove) four (press backspace or delete to remove) five (press backspace or delete to remove) six (press backspace or delete to remove) seven (press backspace or delete to remove)