@eightshift/frontend-libs
Version:
A collection of useful frontend utility modules. powered by Eightshift
322 lines (283 loc) • 9.5 kB
JavaScript
import React from 'react';
import { Responsive } from '@eightshift/frontend-libs/scripts';
import { icons } from '@eightshift/ui-components/icons';
import { __ } from '@wordpress/i18n';
import {
BaseControl,
HStack,
ToggleButton,
ColumnConfigSlider,
} from '@eightshift/ui-components';
import { clsx } from '@eightshift/ui-components/utilities';
/**
* A modern and customizable custom slider.
*
* @typedef {null | 'dots' | true | {Number: string} | {Number: {style, label}}} DotStyle
* @typedef {'top'|'bottom'|'hidden'} TooltipPosition
*
* @param {object} props - WidthOffsetRangeSlider options.
* @param {React.Component?} [props.icon] - Icon to show next to the label
* @param {React.Component?} [props.label] - Label to show above component.
* @param {Object} [props.value] - Value to use - keys are breakpoint names, values are `width`, `offset`, `fullWidth`.
* @param {function} [props.onChange] - Function to trigger when the value of is changing.
* @param {any} [props.inheritValue] - Value that marks something as inherited.
* @param {function} [props.inheritCheck] - Function that returns a `boolean`, used to decide whether a value is inherited or not.
* @param {boolean} [props.fullWidthToggle=false] - If `true`, the "Fullwidth" toggle is shown.
* @param {boolean} [props.autoOffsetToggle=false] - If `true`, the "Automatic offset" toggle is shown.
* @param {any} [props.autoOffsetValue] - Value that marks automatic offset.
* @param {string?} [props.additionalClasses] - If passed, the classes are appended to the base control.
* @param {boolean?} [props.numericValues=false] - If `true`, numeric values are returned instead of strings. Not compatible with `autoOffsetToggle`.
* @param {Number} [props.totalNumberOfColumns=12] - Available number of columns to show.
* @param {function} [props.onAfterChange] - Function to trigger when the value of the slider is changed.
* @param {int|string} [props.colAutoStartOverride] - If passed, overrides the auto-calculated value of the automatic column start offset.
* @param {boolean} [props.showOuterAsGutter] - If `true`, the outer columns are displayed with a special icons instead of the column numbers. Other numbers are offset by 1.
*/
export const WidthOffsetRangeSlider = (props) => {
const {
icon = icons.positioningWidthGuide,
label = __('Width & offset', 'eightshift-frontend-libs'),
value,
onChange,
inheritValue,
inheritCheck = (value) => value === inheritValue,
fullWidthToggle = false,
autoOffsetToggle = false,
autoOffsetValue = 'auto',
additionalClasses,
numericValues = false,
totalNumberOfColumns: rawTotalColumns = 12,
onAfterChange,
colAutoStartOverride,
showOuterAsGutter,
} = props;
const stringValues = !numericValues || autoOffsetToggle;
const breakpointNames = Object.keys(value);
const rawWidths = Object.entries(value).reduce(
(all, [breakpointName, { width }]) => ({
...all,
[breakpointName]: width,
}),
{}
);
const rawOffsets = Object.entries(value).reduce(
(all, [breakpointName, { offset }]) => ({
...all,
[breakpointName]: offset,
}),
{}
);
const rawFullWidths =
fullWidthToggle &&
Object.entries(value).reduce(
(all, [breakpointName, { fullWidth }]) => ({
...all,
[breakpointName]: fullWidth,
}),
{}
);
return (
<Responsive label={label} icon={icon} additionalClasses={additionalClasses}>
{breakpointNames.map((breakpoint, index) => {
const width = rawWidths[breakpoint];
const offset = rawOffsets[breakpoint];
const fullWidth = rawFullWidths?.[breakpoint];
const isWidthInherited = inheritCheck(width);
const isOffsetInherited = inheritCheck(offset);
const autoStartOffset = colAutoStartOverride ?? 1;
const getNearest = (attributeName) => {
for (let i = index - 1; i >= 0; i--) {
const breakpointName = breakpointNames[i];
const current = value[breakpointName][attributeName];
if (autoOffsetToggle && current === autoOffsetValue) {
return autoStartOffset;
}
if (current) {
return current;
}
}
return inheritValue;
};
const nearestValidFullWidth = getNearest('fullWidth');
const nearestValidOffset = getNearest('offset');
const nearestValidWidth = getNearest('width');
const offsetValue = inheritCheck(offset) ? nearestValidOffset : offset;
const parsedOffset =
autoOffsetToggle && offsetValue === autoOffsetValue
? autoStartOffset
: parseInt(offsetValue);
const parsedWidth = parseInt(
inheritCheck(width) ? nearestValidWidth : width
);
const parsedFullWidth = inheritCheck(fullWidth)
? nearestValidFullWidth
: fullWidth;
const displayedWidth = parsedWidth + parsedOffset - 1;
const totalNumberOfColumns =
rawTotalColumns + (parsedFullWidth === true ? 2 : 0);
return (
<>
<HStack
hidden={
!(
(fullWidthToggle &&
(index === 0 || !inheritCheck(fullWidth))) ||
(autoOffsetToggle && index === 0)
)
}
className='es:mb-1'
>
<ToggleButton
hidden={
!(
fullWidthToggle &&
(index === 0 || !inheritCheck(fullWidth))
)
}
selected={parsedFullWidth}
onChange={(value) => {
onChange({
...value,
[breakpoint]: {
...value[breakpoint],
fullWidth: value,
},
});
}}
size='small'
className='es:ml-auto'
>
{__('Full-width', 'eightshift-frontend-libs')}
</ToggleButton>
<ToggleButton
hidden={!(autoOffsetToggle && index === 0)}
selected={offset === autoOffsetValue}
onChange={() => {
onChange({
...value,
[breakpoint]: {
...value[breakpoint],
offset: offset === autoOffsetValue ? 1 : autoOffsetValue,
},
});
}}
size='small'
className={clsx(
!(
fullWidthToggle &&
(index === 0 || !inheritCheck(fullWidth))
) && 'es:ml-auto'
)}
>
{__('Automatic offset', 'eightshift-frontend-libs')}
</ToggleButton>
</HStack>
<ColumnConfigSlider
key={breakpoint}
aria-label={__('Width & offset', 'eightshift-frontend-libs')}
columns={totalNumberOfColumns}
value={isWidthInherited && !isOffsetInherited ? [parsedOffset, parsedOffset + 1] : [parsedOffset, displayedWidth]}
showOuterAsGutter={showOuterAsGutter ?? parsedFullWidth}
onChange={([o, w]) => {
let newValues = {};
if (isWidthInherited && !isOffsetInherited) {
newValues.offset = stringValues ? String(o) : o;
} else if (!isWidthInherited && isOffsetInherited) {
newValues.width = stringValues
? String(w - nearestValidOffset + 1)
: w - nearestValidOffset + 1;
} else if (!isWidthInherited && offset === autoOffsetValue) {
const newWidth = w - autoStartOffset + 1;
if (newWidth > 0) {
newValues.width = stringValues
? String(newWidth)
: newWidth;
}
} else if (!isWidthInherited && !isOffsetInherited) {
newValues.width = stringValues
? String(w - o + 1)
: w - o + 1;
newValues.offset = stringValues ? String(o) : o;
}
onChange({
...value,
[breakpoint]: {
...value[breakpoint],
...newValues,
},
});
}}
disableWidth={inheritCheck(width)}
disableOffset={
inheritCheck(offset) ||
(index === 0 && offset === autoOffsetValue)
}
onChangeEnd={onAfterChange}
/>
<BaseControl
hidden={index === 0}
icon={icons.inherit}
label={__('Inherit', 'eightshift-frontend-libs')}
className='es:mt-2 es:mb-2 es:ml-auto es:w-fit'
inline
>
<HStack className='es:ml-1'>
<ToggleButton
selected={inheritCheck(offset)}
onChange={() => {
onChange({
...value,
[breakpoint]: {
...value[breakpoint],
offset: inheritCheck(offset)
? parsedOffset
: inheritValue,
},
});
}}
size='small'
>
{__('Offset', 'eightshift-frontend-libs')}
</ToggleButton>
<ToggleButton
selected={inheritCheck(width)}
onChange={() => {
onChange({
...value,
[breakpoint]: {
...value[breakpoint],
width: inheritCheck(width)
? displayedWidth
: inheritValue,
},
});
}}
size='small'
>
{__('Width', 'eightshift-frontend-libs')}
</ToggleButton>
<ToggleButton
hidden={!fullWidthToggle}
selected={inheritCheck(fullWidth)}
onChange={() => {
onChange({
...value,
[breakpoint]: {
...value[breakpoint],
fullWidth: inheritCheck(fullWidth)
? false
: inheritValue,
},
});
}}
size='small'
>
{__('Fullwidth', 'eightshift-frontend-libs')}
</ToggleButton>
</HStack>
</BaseControl>
</>
);
})}
</Responsive>
);
};