'use client' import { type ForwardedRef, type JSX, forwardRef, useEffect, useMemo, useState, } from 'react' import * as React from 'react' import * as RadioGroupPrimitive from '@radix-ui/react-radio-group' import * as TabsPrimitive from '@radix-ui/react-tabs' import classNames from 'classnames' import { ariaAttr } from '~/src/utils/aria' import { createContext } from '~/src/utils/react' import { cssDimension } from '~/src/utils/style' import { isNil } from '~/src/utils/type' import { BaseButton } from '~/src/components/BaseButton' import { Divider } from '~/src/components/Divider' import dividerStyles from '~/src/components/Divider/Divider.module.scss' import { useFormFieldProps } from '~/src/components/FormControl' import { HStack } from '~/src/components/Stack' import { Text } from '~/src/components/Text' import { type SegmentedControlItemListProps, type SegmentedControlItemProps, type SegmentedControlProps, type SegmentedControlRadioGroupProps, type SegmentedControlSize, type SegmentedControlTabContentProps, type SegmentedControlTabListProps, type SegmentedControlTabsProps, type SegmentedControlType, } from './SegmentedControl.types' import styles from './SegmentedControl.module.scss' type SegmentedControlContextValue = Required< Pick< SegmentedControlProps, 'type' | 'size' | 'width' > > const [SegmentedControlContextProvider, useSegmentedControlContext] = createContext(null, 'SegmentedControl') type SegmentedControlItemListContextValue = { setSelectedItemIndex: (index: number | null) => void } const [SegmentedControlItemContextProvider, useSegmentedControlItemContext] = createContext(null, 'SegmentedControlItem') const [ SegmentedControlItemListContextProvider, useSegmentedControlItemListContext, ] = createContext( null, 'SegmentedControlItemList' ) function SegmentedControlDivider({ index, selectedItemIndex, }: { index: number selectedItemIndex: number | null }) { const isAdjacentToSelectedItem = !isNil(selectedItemIndex) && (selectedItemIndex + 1 === index || selectedItemIndex === index) return ( ) } function SegmentedControlItemListImpl< Type extends SegmentedControlType, Value extends string, >( { children, style, className, ...rest }: SegmentedControlItemListProps, forwardedRef: ForwardedRef ) { const [selectedItemIndex, setSelectedItemIndex] = useState( null ) const { type, size, width } = useSegmentedControlContext( 'SegmentedControlItemList' ) const contextValue: SegmentedControlItemListContextValue = useMemo( () => ({ setSelectedItemIndex, }), [] ) const SegmentedControlItemList = type === 'radiogroup' ? RadioGroupPrimitive.Root : TabsPrimitive.List return (
{React.Children.map(children, (child, index) => ( <> {index !== 0 && ( )} {child} ))} {!isNil(selectedItemIndex) && (
)}
) } const SegmentedControlItemList = forwardRef(SegmentedControlItemListImpl) as < Type extends SegmentedControlType, Value extends string, >( props: SegmentedControlItemListProps & { ref?: React.ForwardedRef } ) => JSX.Element function SegmentedControlRadioGroupImpl( { children, size, ...rest }: SegmentedControlRadioGroupProps, forwardedRef: React.Ref ) { const { hasError, ...ownProps } = useFormFieldProps(rest) return ( {children} ) } const SegmentedControlRadioGroup = forwardRef( SegmentedControlRadioGroupImpl ) as ( props: SegmentedControlRadioGroupProps & { ref?: React.ForwardedRef } ) => JSX.Element /** * `SegmentedControlTabList` is the component that wraps `SegmentedControlItem`. * It can be used only when `SegmentedControl` component is used as the `tabs` type. * * It must be used as a child of `SegmentedControl`. */ export const SegmentedControlTabList = SegmentedControlItemList as ( props: SegmentedControlTabListProps & { ref?: React.ForwardedRef } ) => JSX.Element /** * `SegmentedControlTabContent` is the component that wraps the content that corresponds to a specific value of `SegmentedControlItem`. * It can be used only when `SegmentedControl` component is used as the `tabs` type. * * It must be used as a child of `SegmentedControl`. */ export const SegmentedControlTabContent = TabsPrimitive.Content as < Value extends string, >( props: SegmentedControlTabContentProps & { ref?: React.ForwardedRef } ) => JSX.Element const SegmentedControlTabs = TabsPrimitive.Root as ( props: SegmentedControlTabsProps & { ref?: React.ForwardedRef } ) => JSX.Element function SegmentedControlImpl< Type extends SegmentedControlType, Value extends string, >( { type = 'radiogroup' as Type, size = 'm', width = '100%', onValueChange, children, ...rest }: SegmentedControlProps, forwardedRef: React.Ref ) { const SegmentedControlRoot = type === 'radiogroup' ? SegmentedControlRadioGroup : SegmentedControlTabs const contextValue = useMemo( () => ({ type, size, width, }), [type, size, width] ) return ( {children} ) } /** * `SegmentedControl` is component that looks like a combination of a radio and a button. * They can be used in place of tabs and as input elements in modals. * If you have more than five items, use a different element, such as a dropdown. * * `SegmentedControl` can be used as a radio group, tabs element depending on its `type`. * @example * * ```tsx * // Anatomy of the SegmentedControl type="radiogroup" * * * * * // Anatomy of the SegmentedControl type="tabs" * * * * * * * * ``` */ export const SegmentedControl = forwardRef(SegmentedControlImpl) as < Type extends SegmentedControlType, Value extends string, >( props: SegmentedControlProps & { ref?: React.ForwardedRef } ) => JSX.Element /** * NOTE: (@ed) A property injected at runtime by the radix-ui lib. */ type ItemProps = (Type extends 'radiogroup' ? { 'data-state'?: 'unchecked' | 'checked' } : { 'data-state'?: 'inactive' | 'active' }) & React.HTMLAttributes & Partial> function getTypography(size: SegmentedControlSize) { return ( { xs: '13', s: '13', m: '13', l: '14', } as const )[size] } const Item = forwardRef>( function Item( { children, leftContent, rightContent, className, ...rest }, forwardedRef ) { const { type, size } = useSegmentedControlContext('SegmentedControlItem') const { setSelectedItemIndex } = useSegmentedControlItemListContext( 'SegmentedControlItem' ) const index = useSegmentedControlItemContext('SegmentedControlItem') const checked = type === 'radiogroup' ? (rest as ItemProps)?.['data-state'] === 'checked' : (rest as ItemProps)?.['data-state'] === 'active' useEffect( function setSelectedItem() { if (checked) { setSelectedItemIndex(index) } }, [checked, index, setSelectedItemIndex] ) return ( {leftContent} {children} {rightContent} ) } ) function SegmentedControlItemImpl( { children, ...rest }: SegmentedControlItemProps, forwardedRef: React.Ref ) { const { type } = useSegmentedControlContext('SegmentedControlItem') const SegmentedControlItem = type === 'radiogroup' ? RadioGroupPrimitive.Item : TabsPrimitive.Trigger return ( {children} ) } /** * `SegmentedControlItem` component is each item in `SegmentedControl`. * * If the type of `SegmentedControl` is `radiogroup`, this component acts as a radio item. * In this case, it must be used as a child of `SegmentedControl`. * * If the type of `SegmentedControl` is `tabs`, this component acts as a tab item. * In this case, it must be used as a child of `SegmentedControlTabList`. */ export const SegmentedControlItem = forwardRef(SegmentedControlItemImpl) as < Value extends string, >( props: SegmentedControlItemProps & { ref?: React.ForwardedRef } ) => JSX.Element