UNPKG

@patreon/studio

Version:

Patreon Studio Design System

104 lines (102 loc) 4.31 kB
/** * @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