Skip to content
7 changes: 4 additions & 3 deletions src/components/font-awesome-icon/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ const addSVGAttributes = ( svgHTML, attributesToAdd = {}, attributesToRemove = [
}

const FontAwesomeIcon = memo( props => {
const { svgAttrsToAdd = { width: '32', height: '32' }, svgAttrsToRemove = [ 'id', 'data-name' ] } = props
const [ forceUpdateCount, setForceUpdateCount ] = useState( 0 )
const forceUpdate = () => {
setForceUpdateCount( forceUpdateCount + 1 )
Expand All @@ -91,7 +92,7 @@ const FontAwesomeIcon = memo( props => {
if ( typeof props.value === 'string' && props.value.match( /^<svg/ ) ) {
let svg = addSVGAriaLabel( props.value, props.ariaLabel )
// Add fallback SVG width and height values.
svg = addSVGAttributes( svg, { width: '32', height: '32' } )
svg = addSVGAttributes( svg, svgAttrsToAdd, svgAttrsToRemove )
return <RawHTML { ...propsToPass }>{ props.prependRenderString + svg }</RawHTML>
}

Expand All @@ -109,7 +110,7 @@ const FontAwesomeIcon = memo( props => {

let svg = addSVGAriaLabel( iconHTML, props.ariaLabel )
// Add fallback SVG width and height values.
svg = addSVGAttributes( svg, { width: '32', height: '32' } )
svg = addSVGAttributes( svg, svgAttrsToAdd, svgAttrsToRemove )
return <RawHTML { ...propsToPass }>{ props.prependRenderString + svg }</RawHTML>
}

Expand All @@ -123,7 +124,7 @@ const FontAwesomeIcon = memo( props => {

let svg = addSVGAriaLabel( iconHTML, props.ariaLabel )
// Add fallback SVG width and height values.
svg = addSVGAttributes( svg, { width: '32', height: '32' } )
svg = addSVGAttributes( svg, svgAttrsToAdd, svgAttrsToRemove )
return <RawHTML { ...propsToPass }>{ props.prependRenderString + svg }</RawHTML>
} )

Expand Down
26 changes: 22 additions & 4 deletions src/components/icon-search-popover/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
import { faGetIcon, faFetchIcon } from '~stackable/util'
import { FileDrop } from 'react-file-drop'
import classnames from 'classnames'
import { applyFilters } from '@wordpress/hooks'

let searchTimeout = null
let tempMediaUpload = null
Expand Down Expand Up @@ -82,7 +83,7 @@ export const cleanSvgString = svgString => {

const IconSearchPopover = props => {
const [ value, setValue ] = useState( '' )
const [ results, setResults ] = useState( [] )
const [ results, setResults ] = useState( { faIcons: [], iconLibrary: [] } )
const [ isBusy, setIsBusy ] = useState( false )
const [ isDropping, setIsDropping ] = useState( false )

Expand Down Expand Up @@ -150,6 +151,11 @@ const IconSearchPopover = props => {
fr.onload = function( e ) {
setIsDropping( false )
const svgString = cleanSvgString( addCustomIconClass( e.target.result ) )

if ( isPro && props.showPrompt ) {
applyFilters( 'stackable.global-settings.inspector.icon-library.prompt', null, svgString )
}

props.onChange( svgString )
props.onClose()
}
Expand Down Expand Up @@ -243,13 +249,24 @@ const IconSearchPopover = props => {
</div>
<div className="ugb-icon-popover__iconlist">
{ isBusy && <Spinner /> }
{ ! isBusy && results.map( ( { prefix, iconName }, i ) => {
{ ! isBusy && applyFilters( 'stackable.global-settings.inspector.icon-library.icons', null, {
icons: results.iconLibrary, onChange: props.onChange, onClose: props.onClose,
} ) }
{ ! isBusy && results.faIcons.map( ( { prefix, iconName }, i ) => {
const iconValue = `${ prefix }-${ iconName }`
return <button
key={ i }
className={ `components-button ugb-prefix--${ prefix } ugb-icon--${ iconName }` }
onClick={ async () => {
if ( props.returnSVGValue ) {
if ( props.returnSVGValue && props.returnIconName ) {
let svgIcon = faGetIcon( prefix, iconName )

if ( ! svgIcon ) {
await faFetchIcon( prefix, iconName )
svgIcon = faGetIcon( prefix, iconName )
}
props.onChange( cleanSvgString( svgIcon ), prefix, iconName )
} else if ( props.returnSVGValue ) {
let svgIcon = faGetIcon( prefix, iconName )

if ( ! svgIcon ) {
Expand All @@ -266,7 +283,7 @@ const IconSearchPopover = props => {
<FontAwesomeIcon prefix={ prefix } iconName={ iconName } />
</button>
} ) }
{ ! isBusy && ! results.length &&
{ ! isBusy && ! results.faIcons.length && ! results.iconLibrary.length &&
<p className="components-base-control__help">{ __( 'No matches found', i18n ) }</p>
}
</div>
Expand Down Expand Up @@ -309,6 +326,7 @@ IconSearchPopover.defaultProps = {
onClose: noop,
returnSVGValue: true, // If true, the value provided in onChange will be the SVG markup of the icon. If false, the value will be a prefix-iconName value.
allowReset: true,
showPrompt: true,
__deprecatedAnchorRef: undefined,
__deprecatedPosition: 'center',
__deprecatedOnClickOutside: noop,
Expand Down
7 changes: 6 additions & 1 deletion src/components/icon-search-popover/search.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { applyFilters } from '@wordpress/hooks'
import {
fontAwesomeSearchProIcons, iconsFaKit, iconsFaProKitVersion, iconsFaFreeKitVersion,
} from 'stackable'
Expand All @@ -23,13 +24,17 @@ export const searchFontAwesomeIconName = async ( name = 'icon', isPro = fontAwes
} )
.then( r => r.json() )

return data.data.search.reduce( ( iconResults, iconData ) => {
const faIcons = data.data.search.reduce( ( iconResults, iconData ) => {
convertFontAwesomeToIcon( iconData, isPro ).forEach( icon => {
iconResults.push( icon )
} )

return iconResults
}, [] )

const iconLibrary = applyFilters( 'stackable.global-settings.inspector.icon-library.search-icons', null, name )

return { faIcons, iconLibrary }
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,4 @@ export { default as ColumnsWidthMultiControl } from './columns-width-multi-contr
export { default as Popover } from './popover'
export { default as HelpTooltip } from './help-tooltip'
export { default as RichText } from './rich-text'
export { default as SortablePicker } from './sortable-picker'
5 changes: 5 additions & 0 deletions src/components/pro-control/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ const LABELS = {
<li>{ __( 'Hide the current post - great for synced patterns', i18n ) }</li>
</ul>,
},
'icon-library': {
description: <ul>
<li>{ __( 'Icon Library', i18n ) }</li>
</ul>,
},
}

const ProControl = props => {
Expand Down
206 changes: 206 additions & 0 deletions src/components/sortable-picker/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
/**
* External dependencies
*/
import classnames from 'classnames'
import { Button } from '~stackable/components'
import {
sortableContainer, sortableElement, sortableHandle,
} from 'react-sortable-hoc'

/**
* WordPress dependencies
*/
import {
BaseControl,
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
__experimentalHStack as HStack,
Dashicon,
Dropdown,
} from '@wordpress/components'
import { useState } from '@wordpress/element'
import { __ } from '@wordpress/i18n'

const popoverProps = {
placement: 'left-start',
offset: 36,
shift: true,
}

// We need to define these because 13 (return key) is not included in the
// default keyCodes to initiate a drag.
const DRAG_KEYCODES = {
lift: [ 32, 13 ],
drop: [ 32, 13 ],
cancel: [ 27 ],
up: [ 38, 37 ],
down: [ 40, 39 ],
}

const SortablePicker = props => {
const {
items,
dropdownOnAdd = false,
onChangeItem,
onDeleteItem,
handleAddItem,
onSortEnd,
AddItemPopover = null,
ref,
} = props
const [ isSorting, setIsSorting ] = useState( false )

const classNames = classnames(
'ugb-global-settings-color-picker',
'components-circular-option-picker',
'editor-color-palette-control__color-palette',
props.className
)

return (
<BaseControl className={ classNames } label={ props.label }>
<Dropdown
renderToggle={ ( { onToggle, isOpen } ) => (
<Button
className="ugb-global-settings-color-picker__add-button"
onClick={ dropdownOnAdd ? onToggle : handleAddItem }
icon="plus-alt2"
aria-expanded={ isOpen }
/>
) }
renderContent={ ( { onClose } ) => (
<AddItemPopover onClose={ onClose } onChange={ handleAddItem } />
) }
/>
<div
ref={ ref }
className={ classnames(
'ugb-global-settings-color-picker__color-indicators',
{ 'is-sorting': isSorting }
) }
>
<SortableContainer
items={ items }
onSortStart={ () => setIsSorting( true ) }
onSortEnd={ ( { oldIndex, newIndex } ) => onSortEnd( {
oldIndex, newIndex, setIsSorting,
} ) }
axis="y"
useDragHandle
keyCodes={ DRAG_KEYCODES }
>
{ items.map( ( item, i ) => (
<SortableItem
key={ i }
index={ i }
item={ item }
onDelete={ () => onDeleteItem( item ) }
onChange={ item => onChangeItem( item ) }
ItemPreview={ props.ItemPreview }
ItemPicker={ props.ItemPicker }
/> ) ) }
</SortableContainer>
</div>
</BaseControl>
)
}

SortablePicker.defaultProps = {
className: '',
label: '',
onReset: () => {},
}

export default SortablePicker

const SortableItem = sortableElement( props => <LabeledItemIndicator { ...props } /> )

const SortableContainer = sortableContainer( ( { children } ) => {
return <div>{ children }</div>
} )

const DragHandle = sortableHandle( () => <Dashicon
icon="menu"
size="16"
tabIndex="0"
/> )

const LabeledItemIndicator = props => {
const {
item,
onDelete,
onChange,
ItemPreview = null,
ItemPicker = null,

} = props

const [ isFocused, setIsFocused ] = useState( false )

return (
<HStack justify="space-between" className="stk-global-settings-color-picker__color-indicator-wrapper">
<Dropdown
popoverProps={ popoverProps }
// This is so that when we click on the label to edit it, the input doesn't lose focus.
focusOnMount={ ! isFocused ? 'firstElement' : false }
renderToggle={ ( { onToggle, isOpen } ) => {
return (
<Button
className="block-editor-panel-color-gradient-settings__dropdown"
onClick={ () => {
if ( ! isFocused ) {
onToggle()
}
} }
isPressed={ isOpen }
>
<HStack justify="flex-start">
<ItemPreview item={ item } />
<input
className="components-input-control__input"
value={ item.name }
onChange={ ev => {
onChange( {
...item,
name: ev.target.value,
} )
} }
onFocus={ () => setIsFocused( true ) }
onBlur={ ev => {
setIsFocused( false )
if ( isOpen && ! ev.relatedTarget?.closest( '.components-popover' ) ) {
onToggle()
}
} }
onClick={ () => {
if ( ! isOpen ) {
onToggle()
}
} }
onKeyDown={ ev => {
// On enter, blur the input.
if ( ev.keyCode === 13 ) {
ev.target.blur()
}
} }
/>
<DragHandle />
</HStack>
</Button>
)
} }
renderContent={ () => (
<div className="stk-color-palette-control__popover-content">
<ItemPicker item={ item } onChange={ onChange } />
</div>
) }
/>
<Button
className="stk-global-settings-color-picker__delete-button"
icon="trash"
isSmall
isTertiary
onClick={ onDelete }
/>
</HStack>
)
}
29 changes: 29 additions & 0 deletions src/global-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,35 @@ public function register_global_settings() {
'default' => '',
)
);

register_setting(
'stackable_global_settings',
'stackable_icon_library',
array(
'type' => 'array',
'description' => __( 'Stackable Icon Library', STACKABLE_I18N ),
'sanitize_callback' => array( $this, 'sanitize_array_setting' ),
'show_in_rest' => array(
'schema' => array(
'items' => array(
'type' => 'object',
'properties' => array(
'name' => array(
'type' => 'string',
),
'key' => array(
'type' => 'string',
),
'icon' => array(
'type' => 'string',
),
)
)
)
),
'default' => '',
)
);
}

/**
Expand Down
Loading