@patreon/studio
Version:
Patreon Studio Design System
104 lines (102 loc) • 4.31 kB
JSX
/**
* @author @Patreon/fe-core
**/
'use client';
import React, { useCallback } from 'react';
import { styled } from 'styled-components';
import { getDimensions } from '~/components/Button/theme';
import { Spacer } from '~/components/Spacer';
import { tokens } from '~/tokens';
import { mediaForBreakpoint } from '~/utilities/breakpoints';
import { cssForBodyText } from '~/utilities/type-bundles';
export const SegmentedControl = ({ segments, fluidWidth = false, onClick, selectedIndex, size = 'md', }) => {
const onSelected = useCallback((index, segment) => {
onClick(index);
if (segment.onSelect) {
segment.onSelect();
}
}, [onClick]);
const createSegment = useCallback((segment, index) => {
const isSelected = selectedIndex === index;
const SegmentIcon = 'icon' in segment ? segment.icon : undefined;
const segmentLabel = 'label' in segment ? segment.label : undefined;
return (<SegmentButton role="radio" aria-checked={isSelected} aria-label={segment['aria-label']}
// Only allow the selected item to be tabbable
tabIndex={isSelected ? 0 : -1} isSelected={isSelected} fluidWidth={fluidWidth} onClick={() => onSelected(index, segment)} data-tag={`segment-${index}`} key={`segment-${index}`} size={size}>
{SegmentIcon && <SegmentIcon size="20px"/>}
{segmentLabel && <Spacer ml={SegmentIcon && tokens.global.space.x8}>{segmentLabel}</Spacer>}
</SegmentButton>);
}, [fluidWidth, onSelected, selectedIndex, size]);
const handleKeyDown = useCallback((e) => {
let newIndex = selectedIndex;
if (e.key === 'ArrowLeft') {
// Prev item
newIndex = Math.max(0, selectedIndex - 1);
e.preventDefault();
}
else if (e.key === 'ArrowRight') {
// Next item
newIndex = Math.min(segments.length - 1, selectedIndex + 1);
e.preventDefault();
}
else if (e.key === 'Home') {
// First item
newIndex = 0;
e.preventDefault();
}
else if (e.key === 'End') {
// Last item
newIndex = segments.length - 1;
e.preventDefault();
}
// Nothing to do if new index matches selected index.
if (newIndex === selectedIndex) {
return;
}
// Select the new segment
onSelected(newIndex, segments[newIndex]);
// Focus the new element
e.currentTarget.children[newIndex].focus();
}, [segments, selectedIndex, onSelected]);
return (<SegmentContainer size={size} fluidWidth={fluidWidth} segmentCount={segments.length} role="radiogroup" onKeyDown={handleKeyDown}>
{segments.map((segment, index) => createSegment(segment, index))}
</SegmentContainer>);
};
const SegmentContainer = styled.div `
display: ${({ fluidWidth }) => (fluidWidth ? 'grid' : 'inline-grid')};
grid-template-columns: repeat(${({ segmentCount }) => segmentCount}, 1fr);
box-sizing: border-box;
height: ${({ size }) => getDimensions({ size, isMobile: true })};
border-radius: ${tokens.global.radius.md};
background-color: ${tokens.global.primary.surfaceMuted.default};
border: ${tokens.global.borderWidth.thin} solid transparent;
@media ${mediaForBreakpoint('sm')} {
height: ${({ size }) => getDimensions({ size, isMobile: false })};
}
`;
function getButtonPadding(size) {
switch (size) {
case 'sm':
return tokens.global.space.x12;
case 'lg':
return tokens.global.space.x24;
case 'md':
default:
return tokens.global.space.x16;
}
}
const SegmentButton = styled.button `
background-color: ${({ isSelected }) => (isSelected ? tokens.global.bg.base.default : 'transparent')};
flex-direction: row;
display: flex;
justify-content: center;
align-items: center;
transition: background-color 0.3s ease;
padding: ${tokens.global.space.x4} ${({ size }) => getButtonPadding(size)};
border: none;
border-radius: calc(${tokens.global.radius.md} - ${tokens.global.borderWidth.thin});
color: ${tokens.global.content.regular.default};
white-space: nowrap;
${cssForBodyText({ size: 'md', weight: 'bold' })};
`;
//# sourceMappingURL=index.jsx.map