diff --git a/src/scripts/Button.tsx b/src/scripts/Button.tsx index 240ba0840..37e663982 100644 --- a/src/scripts/Button.tsx +++ b/src/scripts/Button.tsx @@ -1,13 +1,16 @@ import React, { FC, ReactNode, ButtonHTMLAttributes, Ref, useRef } from 'react'; import classnames from 'classnames'; -import { Icon } from './Icon'; +import { SvgIcon, IconCategory } from './Icon'; import { Spinner } from './Spinner'; import { useEventCallback, useMergeRefs } from './hooks'; export type ButtonType = | 'neutral' | 'brand' + | 'outline-brand' | 'destructive' + | 'text-destructive' + | 'success' | 'inverse' | 'icon' | 'icon-bare' @@ -31,6 +34,7 @@ export type ButtonIconMoreSize = 'x-small' | 'small' | 'medium' | 'large'; */ export type ButtonIconProps = { className?: string; + category?: IconCategory; icon: string; align?: ButtonIconAlign; size?: ButtonIconSize; @@ -43,9 +47,9 @@ export type ButtonIconProps = { */ export const ButtonIcon: FC = ({ icon, + category = 'utility', align, size, - inverse, className, style, ...props @@ -56,19 +60,22 @@ export const ButtonIcon: FC = ({ : null; const sizeClassName = size && ICON_SIZES.indexOf(size) >= 0 ? `slds-button__icon_${size}` : null; - const inverseClassName = inverse ? 'slds-button__icon_inverse' : null; const iconClassNames = classnames( 'slds-button__icon', alignClassName, sizeClassName, - inverseClassName, className ); + + if (icon.indexOf(':') > 0) { + [category, icon] = icon.split(':') as [IconCategory, string]; + } + return ( - = (props) => { buttonRef: buttonRef_, iconMoreSize: iconMoreSize_, onClick: onClick_, + tabIndex, ...rprops } = props; @@ -135,19 +143,24 @@ export const Button: FC = (props) => { onClick_?.(e); }); + const content = children || label; + const isIconOnly = type && /^icon-/.test(type) && icon && !content; + const typeClassName = type ? `slds-button_${type}` : null; const btnClassNames = classnames(className, 'slds-button', typeClassName, { 'slds-is-selected': selected, + ['slds-button_icon']: /^icon-/.test(type ?? ''), [`slds-button_icon-${size ?? ''}`]: /^(x-small|small)$/.test(size ?? '') && /^icon-/.test(type ?? ''), }); - const buttonContent = ( - // eslint-disable-next-line react/button-has-type + return ( ); - - if (props.tabIndex != null) { - return ( - - {buttonContent} - - ); - } - - return buttonContent; }; diff --git a/src/scripts/Icon.tsx b/src/scripts/Icon.tsx index d2c4a9a2a..03dc71e7a 100644 --- a/src/scripts/Icon.tsx +++ b/src/scripts/Icon.tsx @@ -4,7 +4,6 @@ import React, { SVGAttributes, useContext, useRef, - useState, useEffect, useCallback, } from 'react'; @@ -12,7 +11,6 @@ import classnames from 'classnames'; import svg4everybody from 'svg4everybody'; import { registerStyle, getAssetRoot } from './util'; import { ComponentSettingsContext } from './ComponentSettings'; -import { useEventCallback } from './hooks'; import { createFC } from './common'; svg4everybody(); @@ -124,23 +122,19 @@ function useInitComponentStyle() { function getIconColor( fillColor: string | undefined, - category: string | undefined, + category: IconCategory, icon: string ) { /* eslint-disable no-unneeded-ternary */ - return category === 'doctype' - ? null - : fillColor === 'none' + return fillColor === 'none' ? null : fillColor ? fillColor : category === 'utility' ? null - : category === 'custom' - ? icon.replace(/^custom/, 'custom-') - : category === 'action' && /^new_custom/.test(icon) + : category === 'action' && /^new_custom/.test(icon) // not needed for the current SLDS2 icons ? icon.replace(/^new_custom/, 'custom-') - : `${category ?? ''}-${(icon ?? '').replace(/_/g, '-')}`; + : `${category}-${icon.replace(/_/g, '-')}`; /* eslint-enable no-unneeded-ternary */ } @@ -154,8 +148,15 @@ export type IconCategory = | 'standard' | 'utility'; export type IconSize = 'xx-small' | 'x-small' | 'small' | 'medium' | 'large'; -export type IconContainer = boolean | 'default' | 'circle'; -export type IconTextColor = 'default' | 'warning' | 'error' | null; +export type IconContainer = 'circle'; +export type IconTextColor = + | 'default' + | 'currentColor' + | 'success' + | 'warning' + | 'error' + | 'light' + | null; /** * @@ -167,55 +168,56 @@ export type IconProps = { size?: IconSize; align?: 'left' | 'right'; container?: IconContainer; - color?: string; textColor?: IconTextColor; - tabIndex?: number; fillColor?: string; + title?: string; + flip?: boolean; } & SVGAttributes; /** * */ type SvgIconProps = IconProps & { - iconColor: string | null; + iconColor?: string | null; }; /** * */ -const SvgIcon = forwardRef( +export const SvgIcon = forwardRef( (props: SvgIconProps, ref: ForwardedRef) => { const { className = '', category: category_ = 'utility', icon: icon_, - iconColor, + iconColor = null, size = '', align, - container, textColor = 'default', style, ...rprops } = props; const { assetRoot = getAssetRoot() } = useContext(ComponentSettingsContext); - const iconClassNames = classnames( - 'react-slds-icon', - { - 'slds-icon': !/slds-button__icon/.test(className), - [`slds-icon_${size}`]: /^(xx-small|x-small|small|medium|large)$/.test( - size - ), - [`slds-icon-text-${textColor ?? 'default'}`]: - /^(default|warning|error)$/.test(textColor ?? '') && !iconColor, - [`slds-icon-${iconColor ?? ''}`]: !container && iconColor, - 'slds-m-left_x-small': align === 'right', - 'slds-m-right_x-small': align === 'left', - }, - className - ); + + const inIcon = !/slds-button__icon/.test(className); + const iconOnlyClassNames = classnames('react-slds-icon', 'slds-icon', { + [`slds-icon_${size}`]: /^(xx-small|x-small|small|medium|large)$/.test( + size + ), + [`slds-icon-text-${textColor ?? 'default'}`]: + /^(default|success|warning|error|light)$/.test(textColor ?? '') && + !iconColor, + 'slds-m-left_x-small': align === 'right', + 'slds-m-right_x-small': align === 'left', + }); + + const iconClassNames = classnames(className, { + [iconOnlyClassNames]: inIcon, + }); + // icon and category prop should not include chars other than alphanumerics, underscore, and hyphen - const icon = (icon_ ?? '').replace(/[^\w-]/g, ''); // eslint-disable-line no-param-reassign - const category = (category_ ?? '').replace(/[^\w-]/g, ''); // eslint-disable-line no-param-reassign + const icon = (icon_ ?? '').replace(/[^\w-]/g, ''); + const category = (category_ ?? '').replace(/[^\w-]/g, ''); const iconUrl = `${assetRoot}/icons/${category}-sprite/svg/symbols.svg#${icon}`; return ( ( (props) => { - const { container, containerClassName, fillColor, ...rprops } = props; + const { + container, + containerClassName: containerClassName_, + fillColor, + textColor = 'default', + title, + flip, + ...rprops + } = props; let { category = 'utility', icon } = props; useInitComponentStyle(); - const iconContainerRef = useRef(null); - const svgIconRef = useRef(null); const svgIconRefCallback = useCallback( @@ -255,72 +263,44 @@ export const Icon = createFC( [props.tabIndex] ); - const [iconColor, setIconColor] = useState(null); - - const checkIconColor = useEventCallback(() => { - if ( - fillColor || - category === 'doctype' || - (!fillColor && category === 'utility') || - iconColor === 'standard-default' - ) { - return; - } - const el = container ? iconContainerRef.current : svgIconRef.current; - if (!el) { - return; - } - const bgColorStyle = getComputedStyle(el).backgroundColor; - // if no background color set to the icon - if ( - bgColorStyle && - /^(transparent|rgba\(0,\s*0,\s*0,\s*0\))$/.test(bgColorStyle) - ) { - setIconColor('standard-default'); - } - }); - useEffect(() => { svgIconRefCallback(svgIconRef.current); }, [svgIconRefCallback]); - useEffect(() => { - checkIconColor(); - }, [checkIconColor]); - if (icon.indexOf(':') > 0) { [category, icon] = icon.split(':') as [IconCategory, string]; } - const fillIconColor = - iconColor || container ? getIconColor(fillColor, category, icon) : null; + const fillIconColor = getIconColor(fillColor, category, icon); - const svgIcon = ( - + const containerClassName = classnames( + containerClassName_, + 'slds-icon_container', + container === 'circle' ? 'slds-icon_container_circle' : null, + category === 'utility' + ? `slds-icon-utility-${icon.replace(/_/g, '-')}` + : null, + fillIconColor ? `slds-icon-${fillIconColor}` : null, + { + 'slds-current-color': textColor === 'currentColor', + 'slds-icon_flip': flip, + } + ); + + return ( + + + {title ? {title} : null} + ); - if (container) { - const ccontainerClassName = classnames( - containerClassName, - 'slds-icon_container', - container === 'circle' ? 'slds-icon_container_circle' : null, - fillIconColor ? `slds-icon-${fillIconColor}` : null - ); - return ( - - {svgIcon} - - ); - } - return svgIcon; }, { ICONS } ); diff --git a/src/scripts/Lookup.tsx b/src/scripts/Lookup.tsx index 911efe112..8434c5127 100644 --- a/src/scripts/Lookup.tsx +++ b/src/scripts/Lookup.tsx @@ -144,7 +144,6 @@ const LookupSelectedState: FC = ({ > {selected.icon && ( = ({ }} > = ({ }} > = ({ {scope.icon && ( = ({
{iconAlign === 'left' && ( = ({ /> {iconAlign === 'right' && ( = ({ > {entry.icon && ( - + )} diff --git a/src/scripts/Notification.tsx b/src/scripts/Notification.tsx index 1bdfdc325..35cc573e5 100644 --- a/src/scripts/Notification.tsx +++ b/src/scripts/Notification.tsx @@ -51,7 +51,6 @@ export const Notification: FC = (props) => { const iconEl = icon ? ( = (props) => { Sort {cellContent} > = { }, }; +/** + * + */ +export const CurrentColor: StoryObj> = { + render: ({ color, ...args }) => ( +
+ +
+ ), + args: { + category: 'utility', + icon: 'announcement', + textColor: 'currentColor', + color: 'purple', + }, + parameters: { + docs: { + storyDescription: + 'Icon that inherits color from parent element using currentColor', + }, + }, +}; + /** * */ diff --git a/stories/Lookup.stories.tsx b/stories/Lookup.stories.tsx index d3e07af27..11baf069e 100644 --- a/stories/Lookup.stories.tsx +++ b/stories/Lookup.stories.tsx @@ -376,12 +376,7 @@ export const OpenedWithListHeaderFooter: ComponentStoryObj = { listHeader: ( <> - + @@ -393,7 +388,7 @@ export const OpenedWithListHeaderFooter: ComponentStoryObj = { listFooter: ( <> - + @@ -475,12 +470,7 @@ export const DefaultOpenedWithListHeaderFooter: ComponentStoryObj< listHeader: ( <> - + @@ -492,7 +482,7 @@ export const DefaultOpenedWithListHeaderFooter: ComponentStoryObj< listFooter: ( <> - +