Skip to content

Commit fbe4c83

Browse files
feat: size and spacing steps
1 parent a81cf9d commit fbe4c83

File tree

3 files changed

+115
-2
lines changed

3 files changed

+115
-2
lines changed

src/components/advanced-range-control/editor.scss

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,29 @@
4646
.components-number-control {
4747
width: unset;
4848
}
49+
50+
// For displaying marks
51+
.components-range-control__thumb-wrapper {
52+
z-index: 2;
53+
}
54+
.components-range-control__mark {
55+
background: var(--stk-skin-bg-default) !important;
56+
z-index: 1;
57+
transform: translateX(-50%);
58+
width: 3px;
59+
}
60+
.components-range-control__root {
61+
min-height: 35px;
62+
}
63+
.stk-range-control__custom-button {
64+
margin-inline-start: 8px;
65+
top: -1px;
66+
position: relative;
67+
}
68+
}
69+
.stk-range-control--with-marks.stk-range-control--mark-mode .components-range-control__wrapper {
70+
margin-inline-end: 8px;
71+
}
72+
.stk-range-control--with-marks.stk-range-control--mark-mode .components-range-control__wrapper:first-child:last-child {
73+
top: -1px;
4974
}

src/components/advanced-range-control/index.js

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
useAttributeName,
1111
useBlockAttributesContext,
1212
useBlockHoverState,
13+
useBlockSetAttributesContext,
1314
useDeviceType,
1415
} from '~stackable/hooks'
1516

@@ -21,7 +22,10 @@ import { isEqual } from 'lodash'
2122
/**
2223
* WordPress dependencies
2324
*/
24-
import { memo } from '@wordpress/element'
25+
import { memo, useState } from '@wordpress/element'
26+
import { Button } from '@wordpress/components'
27+
import { settings } from '@wordpress/icons'
28+
import { dispatch } from '@wordpress/data'
2529

2630
const AdvancedRangeControl = props => {
2731
const [ value, onChange ] = useControlHandlers( props.attribute, props.responsive, props.hover, props.valueCallback, props.changeCallback )
@@ -30,6 +34,7 @@ const AdvancedRangeControl = props => {
3034
const deviceType = useDeviceType()
3135
const [ currentHoverState ] = useBlockHoverState()
3236
const hasUnits = !! props.units?.length
37+
const setAttributes = useBlockSetAttributesContext()
3338
const unitAttrName = useAttributeName( `${ props.attribute }Unit`, props.responsive, props.hover )
3439
const {
3540
unitAttribute,
@@ -98,12 +103,28 @@ const AdvancedRangeControl = props => {
98103
placeholderRender = null
99104
}
100105

106+
// Is value at first render the same as a step value?
107+
const isMarkValue = props.marks && true
108+
const [ isMarkMode, setIsMarkMode ] = useState( isMarkValue )
109+
101110
// If this supports dynamic content, then the value should be saved as a String.
102111
// Important, the attribute type for this option should be a string.
103112
const _onChange = value => {
104113
const onChangeFunc = typeof props.onChange === 'undefined' ? onChange : props.onChange
105114
let newValue = props.isDynamic ? value.toString() : value
106115

116+
// Support for steps. For steps, the value is an index, but the actual value is in the marks.
117+
if ( newValue !== '' && isMarkMode && props.marks ) {
118+
// Extract the unit and value.
119+
const markValue = props.marks[ value ]?.value || '0'
120+
const [ _newValue, unit ] = extractNumberAndUnit( markValue )
121+
newValue = _newValue
122+
123+
// Update the unit.
124+
dispatch( 'core/block-editor' ).__unstableMarkNextChangeAsNotPersistent()
125+
setAttributes( { [ unitAttrName ]: unit } )
126+
}
127+
107128
// On reset, allow overriding the value.
108129
if ( newValue === '' ) {
109130
const overrideValue = props.onOverrideReset?.()
@@ -121,6 +142,45 @@ const AdvancedRangeControl = props => {
121142
onChange: _onChange,
122143
} )
123144

145+
// Support for steps. Modify the props to make the range control show steps.
146+
if ( props.marks && isMarkMode ) {
147+
// Steps only have 1 increment values
148+
propsToPass.min = 0
149+
propsToPass.min = 0
150+
propsToPass.max = props.marks.length - 1
151+
propsToPass.sliderMax = props.marks.length - 1
152+
propsToPass.step = 1
153+
154+
// Show the marks and labels
155+
propsToPass.marks = props.marks.reduce( ( acc, mark, index ) => {
156+
return [
157+
{
158+
value: index,
159+
label: undefined,
160+
},
161+
...acc,
162+
]
163+
}, [] )
164+
propsToPass.renderTooltipContent = value => {
165+
return props.marks[ value ]?.label || ''
166+
}
167+
168+
// Other necessary props for steps.
169+
propsToPass.withInputField = false
170+
} else {
171+
propsToPass.marks = undefined
172+
}
173+
174+
if ( props.marks ) {
175+
controlProps.className = controlProps.className || ''
176+
controlProps.className += 'stk-range-control--with-marks'
177+
controlProps.className += isMarkMode ? ' stk-range-control--mark-mode' : ''
178+
}
179+
180+
if ( isMarkMode ) {
181+
controlProps.units = false
182+
}
183+
124184
return (
125185
<AdvancedControl { ...controlProps }>
126186
<DynamicContentControl
@@ -134,7 +194,19 @@ const AdvancedRangeControl = props => {
134194
onChange={ _onChange }
135195
allowReset={ false }
136196
placeholderRender={ placeholderRender }
137-
/>
197+
__nextHasNoMarginBottom
198+
>
199+
{ props.allowCustom && props.marks && (
200+
<Button
201+
className="stk-range-control__custom-button"
202+
size="small"
203+
variant="tertiary"
204+
onClick={ () => setIsMarkMode( ! isMarkMode ) }
205+
icon={ settings }
206+
>
207+
</Button>
208+
) }
209+
</RangeControl>
138210
</DynamicContentControl>
139211
<ResetButton
140212
allowReset={ props.allowReset }
@@ -159,6 +231,20 @@ AdvancedRangeControl.defaultProps = {
159231
onChange: undefined,
160232
onOverrideReset: undefined,
161233
forcePlaceholder: false,
234+
235+
marks: undefined, // [{ value: '14px', label: 'S' }, { value: '16px', label: 'M' }]
236+
allowCustom: true,
162237
}
163238

164239
export default memo( AdvancedRangeControl, isEqual )
240+
241+
// The value can be in the format '10px' or '10.0em' or '10rem'.
242+
// Return an array with the number and the unit.
243+
const extractNumberAndUnit = value => {
244+
// Match the last characters that are not numbers.
245+
const matches = value.match( /([\d.]+)(\D*)$/ )
246+
if ( ! matches ) {
247+
return [ value, '' ]
248+
}
249+
return [ matches[ 1 ], matches[ 2 ] ]
250+
}

src/components/advanced-range-control/range-control.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const StackableRangeControl = memo( props => {
4343
isShiftStepEnabled,
4444
placeholderRender,
4545
defaultValue: _defaultValue, // Don't pass this.
46+
children: _children, // Don't pass this.
4647
...propsToPass
4748
} = props
4849

@@ -178,6 +179,7 @@ const StackableRangeControl = memo( props => {
178179
type="text"
179180
/>
180181
) }
182+
{ _children }
181183
{ allowReset &&
182184
<Button
183185
className="components-range-control__reset"

0 commit comments

Comments
 (0)