UNPKG

@gechiui/components

Version:
431 lines (417 loc) 10.3 kB
/** * External dependencies */ import { kebabCase } from 'lodash'; /** * GeChiUI dependencies */ import { useState, useRef, useEffect } from '@gechiui/element'; import { __, sprintf } from '@gechiui/i18n'; import { lineSolid, moreVertical, plus } from '@gechiui/icons'; import { __experimentalUseFocusOutside as useFocusOutside } from '@gechiui/compose'; /** * Internal dependencies */ import Button from '../button'; import { ColorPicker } from '../color-picker'; import { FlexItem } from '../flex'; import { HStack } from '../h-stack'; import { ItemGroup } from '../item-group'; import { VStack } from '../v-stack'; import GradientPicker from '../gradient-picker'; import ColorPalette from '../color-palette'; import DropdownMenu from '../dropdown-menu'; import Popover from '../popover'; import { PaletteActionsContainer, PaletteEditStyles, PaletteHeading, PaletteHStackHeader, IndicatorStyled, PaletteItem, NameContainer, NameInputControl, DoneButton, RemoveButton, } from './styles'; import { NavigableMenu } from '../navigable-container'; import { DEFAULT_GRADIENT } from '../custom-gradient-picker/constants'; import CustomGradientPicker from '../custom-gradient-picker'; const DEFAULT_COLOR = '#000'; function NameInput( { value, onChange, label } ) { return ( <NameInputControl label={ label } hideLabelFromVision value={ value } onChange={ onChange } /> ); } function getNameForPosition( position ) { return sprintf( /* translators: %s: is a temporary id for a custom color */ __( '颜色%s' ), position + 1 ); } function Option( { canOnlyChangeValues, element, onChange, isEditing, onStartEditing, onRemove, onStopEditing, slugPrefix, isGradient, } ) { const focusOutsideProps = useFocusOutside( onStopEditing ); const value = isGradient ? element.gradient : element.color; return ( <PaletteItem as="div" onClick={ onStartEditing } { ...( isEditing ? focusOutsideProps : {} ) } > <HStack justify="flex-start"> <FlexItem> <IndicatorStyled style={ { background: value, color: 'transparent' } } /> </FlexItem> <FlexItem> { isEditing && ! canOnlyChangeValues ? ( <NameInput label={ isGradient ? __( '梯度名称' ) : __( '颜色名称' ) } value={ element.name } onChange={ ( nextName ) => onChange( { ...element, name: nextName, slug: slugPrefix + kebabCase( nextName ), } ) } /> ) : ( <NameContainer>{ element.name }</NameContainer> ) } </FlexItem> { isEditing && ! canOnlyChangeValues && ( <FlexItem> <RemoveButton isSmall icon={ lineSolid } label={ __( '移除颜色' ) } onClick={ onRemove } /> </FlexItem> ) } </HStack> { isEditing && ( <Popover position="bottom left" className="components-palette-edit__popover" > { ! isGradient && ( <ColorPicker color={ value } onChange={ ( newColor ) => onChange( { ...element, color: newColor, } ) } /> ) } { isGradient && ( <CustomGradientPicker __experimentalIsRenderedInSidebar value={ value } onChange={ ( newGradient ) => onChange( { ...element, gradient: newGradient, } ) } /> ) } </Popover> ) } </PaletteItem> ); } function isTemporaryElement( slugPrefix, { slug, color, gradient }, index ) { return ( slug === slugPrefix + kebabCase( getNameForPosition( index ) ) && ( ( !! color && color === DEFAULT_COLOR ) || ( !! gradient && gradient === DEFAULT_GRADIENT ) ) ); } function PaletteEditListView( { elements, onChange, editingElement, setEditingElement, canOnlyChangeValues, slugPrefix, isGradient, } ) { // When unmounting the component if there are empty elements (the user did not complete the insertion) clean them. const elementsReference = useRef(); useEffect( () => { elementsReference.current = elements; }, [ elements ] ); useEffect( () => { return () => { if ( elementsReference.current.some( ( element, index ) => isTemporaryElement( slugPrefix, element, index ) ) ) { const newElements = elementsReference.current.filter( ( element, index ) => ! isTemporaryElement( slugPrefix, element, index ) ); onChange( newElements.length ? newElements : undefined ); } }; }, [] ); return ( <VStack spacing={ 3 }> <ItemGroup isBordered isSeparated> { elements.map( ( element, index ) => ( <Option isGradient={ isGradient } canOnlyChangeValues={ canOnlyChangeValues } key={ index } element={ element } onStartEditing={ () => { if ( editingElement !== index ) { setEditingElement( index ); } } } onChange={ ( newElement ) => { onChange( elements.map( ( currentElement, currentIndex ) => { if ( currentIndex === index ) { return newElement; } return currentElement; } ) ); } } onRemove={ () => { setEditingElement( null ); const newElements = elements.filter( ( _currentElement, currentIndex ) => { if ( currentIndex === index ) { return false; } return true; } ); onChange( newElements.length ? newElements : undefined ); } } isEditing={ index === editingElement } onStopEditing={ () => { if ( index === editingElement ) { setEditingElement( null ); } } } slugPrefix={ slugPrefix } /> ) ) } </ItemGroup> </VStack> ); } const EMPTY_ARRAY = []; export default function PaletteEdit( { gradients, colors = EMPTY_ARRAY, onChange, paletteLabel, emptyMessage, canOnlyChangeValues, canReset, slugPrefix = '', } ) { const isGradient = !! gradients; const elements = isGradient ? gradients : colors; const [ isEditing, setIsEditing ] = useState( false ); const [ editingElement, setEditingElement ] = useState( null ); const isAdding = isEditing && editingElement && elements[ editingElement ] && ! elements[ editingElement ].slug; const elementsLength = elements.length; const hasElements = elementsLength > 0; return ( <PaletteEditStyles> <PaletteHStackHeader> <PaletteHeading>{ paletteLabel }</PaletteHeading> <PaletteActionsContainer> { isEditing && ( <DoneButton isSmall onClick={ () => { setIsEditing( false ); setEditingElement( null ); } } > { __( '完成' ) } </DoneButton> ) } { ! canOnlyChangeValues && ( <Button isSmall isPressed={ isAdding } icon={ plus } label={ isGradient ? __( '添加梯度' ) : __( '添加颜色' ) } onClick={ () => { const tempOptionName = getNameForPosition( elementsLength ); onChange( [ ...elements, { ...( isGradient ? { gradient: DEFAULT_GRADIENT } : { color: DEFAULT_COLOR } ), name: tempOptionName, slug: slugPrefix + kebabCase( tempOptionName ), }, ] ); setIsEditing( true ); setEditingElement( elements.length ); } } /> ) } { hasElements && ( ! isEditing || ! canOnlyChangeValues || canReset ) && ( <DropdownMenu icon={ moreVertical } label={ isGradient ? __( '梯度选项' ) : __( '颜色选项' ) } toggleProps={ { isSmall: true, } } > { ( { onClose } ) => ( <> <NavigableMenu role="menu"> { ! isEditing && ( <Button variant="tertiary" onClick={ () => { setIsEditing( true ); onClose(); } } className="components-palette-edit__menu-button" > { isGradient ? __( '编辑梯度' ) : __( '编辑颜色' ) } </Button> ) } { ! canOnlyChangeValues && ( <Button variant="tertiary" onClick={ () => { setEditingElement( null ); setIsEditing( false ); onChange(); onClose(); } } className="components-palette-edit__menu-button" > { isGradient ? __( '移除所有梯度' ) : __( 'Remove all colors' ) } </Button> ) } { canReset && ( <Button variant="tertiary" onClick={ () => { setEditingElement( null ); onChange(); onClose(); } } > { isGradient ? __( '重置梯度' ) : __( '重置颜色' ) } </Button> ) } </NavigableMenu> </> ) } </DropdownMenu> ) } </PaletteActionsContainer> </PaletteHStackHeader> { hasElements && ( <> { isEditing && ( <PaletteEditListView canOnlyChangeValues={ canOnlyChangeValues } elements={ elements } onChange={ onChange } editingElement={ editingElement } setEditingElement={ setEditingElement } slugPrefix={ slugPrefix } isGradient={ isGradient } /> ) } { ! isEditing && ( isGradient ? ( <GradientPicker gradients={ gradients } onChange={ () => {} } clearable={ false } disableCustomGradients={ true } /> ) : ( <ColorPalette colors={ colors } onChange={ () => {} } clearable={ false } disableCustomColors={ true } /> ) ) } </> ) } { ! hasElements && emptyMessage } </PaletteEditStyles> ); }