Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/components/.stylelintrc.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ const [ disallowedValues, disallowedValueMessages ] =
export default {
extends: '@wordpress/stylelint-tools/config',
rules: {
// Allow this package's own private properties while keeping other private prefixes restricted.
'custom-property-pattern': '^(?:_wp-components-.+|(?!_(?:gcd|wp)-).+)',
'declaration-property-value-disallowed-list': [
{
...disallowedValues,
Expand Down
1 change: 1 addition & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Breaking Changes

- Components that compose Emotion style fragments with `cx()` should pass source-order-dependent fragments in a single `css()` call. Passing separate fragments can change override order after the following components stopped rendering styles through Emotion:
- `RangeControl` ([#84562](https://github.com/WordPress/gutenberg/pull/84562))
- `Tabs` ([#84332](https://github.com/WordPress/gutenberg/pull/84332))
- `NumberControl` ([#84330](https://github.com/WordPress/gutenberg/pull/84330))
- `TimePicker` ([#84331](https://github.com/WordPress/gutenberg/pull/84331))
Expand Down
2 changes: 2 additions & 0 deletions packages/components/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,8 @@ All new components should be styled using SCSS Modules.

Place component-local styles in a `style.module.scss` file next to the component, import the module from JavaScript or TypeScript, and compose class names with `clsx`. Preserve existing public `components-*` class names where consumers may rely on them. For dynamic values, prefer inline CSS custom properties consumed by the SCSS module. For variants and state, prefer conditional module classes composed with `clsx` object syntax, e.g. `{ [ styles.className ]: condition }`.

Use `--_wp-components-*` for new private custom properties. These are implementation details, not public theming APIs. Keep supported customization in component props and existing public styles.

Legacy components may still use Emotion while they are being migrated, but new Emotion usage should not be added.

Before migrating existing styles, read [Migrating components from Emotion to SCSS Modules](./emotion-to-scss-modules.md) for compatibility checks, cascade and custom-property pitfalls, verification, and examples from merged migrations.
Expand Down
107 changes: 69 additions & 38 deletions packages/components/src/range-control/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,17 @@ import BaseControl from '../base-control';
import Button from '../button';
import Icon from '../icon';
import { COLORS } from '../utils';
import { floatClamp, useControlledRangeValue } from './utils';
import {
floatClamp,
useControlledRangeValue,
getCSSWideKeyword,
} from './utils';
import { clamp } from '../utils/math';
import InputRange from './input-range';
import RangeRail from './rail';
import SimpleTooltip from './tooltip';
import {
ActionRightWrapper,
AfterIconWrapper,
BeforeIconWrapper,
InputNumber,
Root,
Track,
ThumbWrapper,
Thumb,
Wrapper,
} from './styles/range-control-styles';
import NumberControl from '../number-control';
import styles from './style.module.scss';
import type { RangeControlProps } from './types';
import type { WordPressComponentProps } from '../context';
import { space } from '../utils/space';
Expand Down Expand Up @@ -126,8 +121,13 @@ function UnforwardedRangeControl(

const classes = clsx( 'components-range-control', className );

const colorKeyword = getCSSWideKeyword( colorProp );
const trackColorKeyword = getCSSWideKeyword( trackColor );

const wrapperClasses = clsx(
'components-range-control__wrapper',
styles.wrapper,
colorKeyword && styles[ `color-${ colorKeyword }` ],
!! marks && 'is-marked'
);

Expand Down Expand Up @@ -226,16 +226,25 @@ function UnforwardedRangeControl(
id={ `${ id }` }
help={ help }
>
<Root className="components-range-control__root">
<div
className={ clsx(
'components-range-control__root',
styles.root,
{ [ styles[ 'is-rtl' ] ]: isRTL() }
) }
>
{ beforeIcon && (
<BeforeIconWrapper>
<span className={ styles[ 'before-icon' ] }>
<Icon icon={ beforeIcon } />
</BeforeIconWrapper>
</span>
) }
<Wrapper
<div
className={ wrapperClasses }
color={ colorProp }
marks={ !! marks }
style={
{
'--_wp-components-range-control-color': colorProp,
} as React.CSSProperties
}
>
<InputRange
{ ...otherProps }
Expand Down Expand Up @@ -265,24 +274,43 @@ function UnforwardedRangeControl(
step={ step }
value={ rangeFillValue }
/>
<Track
<span
aria-hidden
className="components-range-control__track"
disabled={ disabled }
style={ { width: fillValueOffset } }
trackColor={ trackColor }
className={ clsx(
'components-range-control__track',
styles.track,
{ [ styles[ 'is-disabled' ] ]: disabled },
! disabled &&
trackColorKeyword &&
styles[ `background-${ trackColorKeyword }` ]
) }
{ ...{ disabled } }
style={
{
width: fillValueOffset,
'--_wp-components-range-control-track-color':
trackColor || 'currentColor',
} as React.CSSProperties
}
/>
<ThumbWrapper
className="components-range-control__thumb-wrapper"
<span
className={ clsx(
'components-range-control__thumb-wrapper',
styles[ 'thumb-wrapper' ],
{ [ styles[ 'is-disabled' ] ]: disabled }
) }
style={ offsetStyle }
disabled={ disabled }
{ ...{ disabled } }
>
<Thumb
<span
aria-hidden
isFocused={ isThumbFocused }
disabled={ disabled }
className={ clsx( styles.thumb, {
[ styles[ 'is-disabled' ] ]: disabled,
[ styles[ 'is-focused' ] ]: isThumbFocused,
} ) }
{ ...{ disabled } }
/>
</ThumbWrapper>
</span>
{ enableTooltip && (
<SimpleTooltip
className="components-range-control__tooltip"
Expand All @@ -294,16 +322,19 @@ function UnforwardedRangeControl(
value={ value }
/>
) }
</Wrapper>
</div>
{ afterIcon && (
<AfterIconWrapper>
<span className={ styles[ 'after-icon' ] }>
<Icon icon={ afterIcon } />
</AfterIconWrapper>
</span>
) }
{ hasInputField && (
<InputNumber
<NumberControl
aria-label={ label }
className="components-range-control__number"
className={ clsx(
'components-range-control__number',
styles[ 'input-number' ]
) }
disabled={ disabled }
inputMode="decimal"
isShiftStepEnabled={ isShiftStepEnabled }
Expand All @@ -319,7 +350,7 @@ function UnforwardedRangeControl(
/>
) }
{ allowReset && (
<ActionRightWrapper>
<span className={ styles[ 'action-right' ] }>
<Button
className="components-range-control__reset"
// If the RangeControl itself is disabled, the reset button shouldn't be in the tab sequence.
Expand All @@ -341,9 +372,9 @@ function UnforwardedRangeControl(
>
{ __( 'Reset' ) }
</Button>
</ActionRightWrapper>
</span>
) }
</Root>
</div>
</BaseControl>
);
}
Expand Down
10 changes: 7 additions & 3 deletions packages/components/src/range-control/input-range.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { forwardRef } from '@wordpress/element';
import { InputRange as BaseInputRange } from './styles/range-control-styles';
import clsx from 'clsx';
import { PolymorphicElement } from '../utils/polymorphic-element';
import styles from './style.module.scss';
import type { InputRangeProps } from './types';
import type { WordPressComponentProps } from '../context';

function InputRange(
props: WordPressComponentProps< InputRangeProps, 'input' >,
ref: React.ForwardedRef< HTMLInputElement >
) {
const { describedBy, label, value, ...otherProps } = props;
const { describedBy, label, value, className, ...otherProps } = props;
return (
<BaseInputRange
<PolymorphicElement
as="input"
{ ...otherProps }
className={ clsx( styles.input, className ) }
aria-describedby={ describedBy }
aria-label={ label }
aria-hidden={ false }
Expand Down
18 changes: 13 additions & 5 deletions packages/components/src/range-control/mark.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import clsx from 'clsx';
import { Mark, MarkLabel } from './styles/range-control-styles';
import { isRTL } from '@wordpress/i18n';
import { PolymorphicElement } from '../utils/polymorphic-element';
import styles from './style.module.scss';
import type { RangeMarkProps } from './types';
import type { WordPressComponentProps } from '../context';

Expand All @@ -16,31 +18,37 @@ export default function RangeMark(

const classes = clsx(
'components-range-control__mark',
styles.mark,
isFilled && 'is-filled',
className
);
const labelClasses = clsx(
'components-range-control__mark-label',
styles[ 'mark-label' ],
{
[ styles[ 'is-filled' ] ]: isFilled,
[ styles[ 'is-rtl' ] ]: isRTL(),
},
isFilled && 'is-filled'
);

return (
<>
<Mark
<PolymorphicElement
as="span"
{ ...otherProps }
aria-hidden="true"
className={ classes }
style={ style }
/>
{ label && (
<MarkLabel
<span
aria-hidden="true"
className={ labelClasses }
isFilled={ isFilled }
style={ style }
>
{ label }
</MarkLabel>
</span>
) }
</>
);
Expand Down
41 changes: 36 additions & 5 deletions packages/components/src/range-control/rail.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { isRTL } from '@wordpress/i18n';
import clsx from 'clsx';
import { COLORS } from '../utils';
import { getCSSWideKeyword } from './utils';
import RangeMark from './mark';
import { MarksWrapper, Rail } from './styles/range-control-styles';
import { PolymorphicElement } from '../utils/polymorphic-element';
import styles from './style.module.scss';
import type { WordPressComponentProps } from '../context';
import type {
MarksProps,
Expand All @@ -19,12 +23,36 @@ export default function RangeRail(
max = 100,
step = 1,
value = 0,
railColor,
className,
style,
...restProps
} = props;

const railColorKeyword = getCSSWideKeyword( railColor );

return (
<>
<Rail disabled={ disabled } { ...restProps } />
<PolymorphicElement
as="span"
{ ...restProps }
{ ...{ disabled } }
className={ clsx(
styles.rail,
className,
{ [ styles[ 'is-disabled' ] ]: disabled },
! disabled &&
railColorKeyword &&
styles[ `background-${ railColorKeyword }` ]
) }
style={
{
'--_wp-components-range-control-rail-color':
railColor || COLORS.theme.gray[ 300 ],
...style,
} as React.CSSProperties
}
/>
{ marks && (
<Marks
disabled={ disabled }
Expand Down Expand Up @@ -53,9 +81,12 @@ function Marks( props: WordPressComponentProps< MarksProps, 'span' > ) {
const marksData = useMarks( { marks, min, max, step, value } );

return (
<MarksWrapper
<span
aria-hidden="true"
className="components-range-control__marks"
className={ clsx(
'components-range-control__marks',
styles.marks
) }
>
{ marksData.map( ( mark ) => (
<RangeMark
Expand All @@ -65,7 +96,7 @@ function Marks( props: WordPressComponentProps< MarksProps, 'span' > ) {
disabled={ disabled }
/>
) ) }
</MarksWrapper>
</span>
);
}

Expand Down
Loading
Loading