diff --git a/components/actionbutton/stories/template.js b/components/actionbutton/stories/template.js index 9c1fd980055..3b3cb8930c2 100644 --- a/components/actionbutton/stories/template.js +++ b/components/actionbutton/stories/template.js @@ -109,7 +109,13 @@ export const Template = ({ ${when(hasPopup && hasPopup !== "false", () => Icon({ size, - iconName: "CornerTriangle", + iconName: "CornerTriangle" + ({ + xs: "75", + s: "75", + m: "100", + l: "200", + xl: "300", + }[size] || "100"), setName: "ui", customClasses: [`${rootClass}-hold`], }, context) diff --git a/components/button/stories/button.test.js b/components/button/stories/button.test.js index bdc6ee37996..ad83defdf1e 100644 --- a/components/button/stories/button.test.js +++ b/components/button/stories/button.test.js @@ -60,8 +60,8 @@ const ButtonIconGroup = (args, context) => Container({ testHeading: "UI icon (larger)", content: Template({ ...args, - // UI icon that is larger than workflow sizing: - iconName: "ArrowDown600", + // Largest UI icon from UI icon set: + iconName: "Cross600", iconSet: "ui", }, context), }, diff --git a/components/icon/stories/icon.mdx b/components/icon/stories/icon.mdx index de5d3711be7..8aa57f0b338 100644 --- a/components/icon/stories/icon.mdx +++ b/components/icon/stories/icon.mdx @@ -67,6 +67,14 @@ with: +## Missing workflow icon placeholder + +In Storybook documentation, if a workflow icon name does not exist in the set, the placeholder "Circle" icon +will be shown. Missing ui icons will render nothing. The following example purposefully uses an +icon name that does not exist to demonstrate this behavior. + + + ## Repositories for the icon SVG files The UI icon SVGs are within the Spectrum CSS repository, which has its own package published to NPM: diff --git a/components/icon/stories/icon.stories.js b/components/icon/stories/icon.stories.js index d418250a69d..8fefeb451e1 100644 --- a/components/icon/stories/icon.stories.js +++ b/components/icon/stories/icon.stories.js @@ -193,3 +193,20 @@ UIArrows.tags = ["!dev"]; UIArrows.parameters = { chromatic: { disableSnapshot: true }, }; + +/** + * In Storybook documentation, if a workflow icon name does not exist in the set, the + * placeholder "Circle" icon will be shown. Missing ui icons will render + * nothing. The following example purposefully uses an icon name that does + * not exist to demonstrate this behavior. + */ +export const MissingWorkflowIcon = Default.bind({}); +MissingWorkflowIcon.storyName = "Missing workflow icon placeholder"; +MissingWorkflowIcon.tags = ["!dev"]; +MissingWorkflowIcon.args = { + setName: "workflow", + iconName: "ThisIconNameDoesNotExist", +}; +MissingWorkflowIcon.parameters = { + chromatic: { disableSnapshot: true }, +}; diff --git a/components/icon/stories/template.js b/components/icon/stories/template.js index b6deef70de5..75826ee6d40 100644 --- a/components/icon/stories/template.js +++ b/components/icon/stories/template.js @@ -6,7 +6,7 @@ import { ifDefined } from "lit/directives/if-defined.js"; import { styleMap } from "lit/directives/style-map.js"; import { unsafeSVG } from "lit/directives/unsafe-svg.js"; import { when } from "lit/directives/when.js"; -import { getSpriteSheetName, uiIconsCleaned, uiIconsWithDirections, workflowIconsCleaned } from "./utilities.js"; +import { appendUiIconDefaultSizing, getSpriteSheetName, uiIconsWithDirections, workflowIconsCleaned } from "./utilities.js"; import "../index.css"; @@ -33,7 +33,7 @@ import "../index.css"; export const Template = ({ rootClass = "spectrum-Icon", size = "m", - setName, + setName = "workflow", iconName, uiIconName, fill, @@ -50,113 +50,53 @@ export const Template = ({ iconName = uiIconName; } - // Make sure icon name is provided. - if (!iconName) { + // Make sure icon set is provided. + if (!["ui","workflow"].includes(setName)) { console.warn( - "Icon: Could not render a result because no icon name was provided to the icon template." + `Icon "${iconName}" is missing its icon set. Make sure you are explicitly setting either the workflow or ui icon set.` ); return html``; } - // Name of icon that corresponds with SVG file. This may differ from the icon name, such as with - // directional icons that use a single icon. - let idKey = iconName; - - // If a descriptor like "Right", "Left", "Down", or "Up" is present for the UI icons Chevron or - // Arrow, use that only for the class name and not the icon fetch. This is because these use a - // single icon file that is rotated in CSS. - if ( - ["Right", "Left", "Down", "Up"].some((c) => idKey.includes(c)) && - setName === "ui" - ) { - idKey = idKey.replace(/(Right|Left|Down|Up)/, ""); - } - - // Make sure icon set is provided. - if (!setName) { - console.warn( - `Icon "${idKey}" is missing its icon set. Make sure you are explicitly setting either the workflow or ui icon set.` - ); + // Make sure icon name is provided. + if (!iconName) { + console.warn("Icon: Could not render a result because no icon name was provided to the icon template."); return html``; } /** - * Fallback UI Icon sizing number. - * - * If the icon name includes its scale, we want to leave that scale. This is preferred, - * as UI icons do not use workflow icon sizing. + * Append approximate sizing number to UI icons passed in without a sizing number. * - * If the UI icon name does not include scale, or the scale does not exist in the current - * list of UI icons, reformat it to approximate the provided sizing for the component. + * Note: It's preferred for components to provide the specific UI sizing numbers in the UI icon + * name, rather than relying on this approximation, as UI icons do not use t-shirt sizing. */ - if ( - setName === "ui" && - ( - // Does not already have size number at the end. - !idKey.match(/\d{2,3}$/) || - // If the provided icon name includes the sizing number, make sure it's a supported sizing number; - // if not, strip it from the key. - ( - idKey.match(/\d{2,3}$/) && - !uiIconsCleaned.includes(idKey) - ) - ) - ) { - let sizeVal; - switch (size) { - case "xs": - if (["CornerTriangle", "Cross"].some(c => idKey.startsWith(c))) { - sizeVal = "75"; - } - else if (["Arrow", "Asterisk", "LinkOut"].some(c => idKey.startsWith(c))) { - sizeVal = "100"; - } - else { - sizeVal = "50"; - } - break; - case "s": - if (["Arrow", "Asterisk", "LinkOut"].some(c => idKey.startsWith(c))) { - sizeVal = "100"; - } - else { - sizeVal = "75"; - } - break; - case "l": - if (["Arrow"].some(c => idKey.startsWith(c))) { - sizeVal = "400"; - } - else { - sizeVal = "200"; - } - break; - case "xl": - case "xxl": - if (["Arrow"].some(c => idKey.startsWith(c))) { - sizeVal = "400"; - } - else { - sizeVal = "300"; - } - break; - default: - sizeVal = "100"; - break; - } + if (setName === "ui") { + iconName = appendUiIconDefaultSizing(iconName, size); + } - console.warn(`Using fallback UI Icon sizing number "${sizeVal}" for "${idKey}". UI icon size was not provided or does not exist in the list of available UI icons.`); + // Make sure icon exists in the set. + if (setName == "ui" && !uiIconsWithDirections.includes(iconName)) { + console.warn(`Icon: Could not render an icon with the name "${iconName}" because it does not exist in the "ui" icon set.`); + return html``; + } - // Replace sizing number on idKey and iconName with new fallback size. - idKey = idKey.replace(/\d{2,3}$/, ""); - idKey += sizeVal; + if (setName == "workflow" && !workflowIconsCleaned.includes(iconName)) { + console.warn(`Icon: Could not render the correct icon with the name "${iconName}" because it does not exist in the "workflow" icon set. Rendering the placeholder icon instead.`); + iconName = "Circle"; + } - iconName = iconName.replace(/\d{2,3}$/, ""); - iconName += sizeVal; + // Name of icon that corresponds with SVG file. This may differ from the icon name, such as with + // directional icons that use a single icon. + let iconNameToLoad = iconName; - if (!uiIconsCleaned.includes(idKey)) { - console.error(`The UI icon "${idKey}" does not exist in the list of available UI icons.`); - } + // If a descriptor like "Right", "Left", "Down", or "Up" is present for the UI icons Chevron or + // Arrow, use that only for the class name and not the icon fetch. This is because these use a + // single icon file that is rotated in CSS. + if ( + ["Right", "Left", "Down", "Up"].some((c) => iconNameToLoad.includes(c)) && + setName === "ui" + ) { + iconNameToLoad = iconNameToLoad.replace(/(Right|Left|Down|Up)/, ""); } /** @@ -178,8 +118,8 @@ export const Template = ({ */ if (!useRef) { let svgString; - if (loaded?.icons && loaded?.icons[setName]?.[idKey]) { - svgString = loaded.icons[setName][idKey]; + if (loaded?.icons && loaded?.icons[setName]?.[iconNameToLoad]) { + svgString = loaded.icons[setName][iconNameToLoad]; } // Return the individual SVG's entire markup. @@ -194,12 +134,12 @@ export const Template = ({ )}`; } else { - console.warn(`Could not find SVG markup for "${idKey}" in context.loaded.icons. Did you pass through context? Falling back to using the sprite sheet reference instead.`); + console.warn(`Could not find SVG markup for "${iconNameToLoad}" in context.loaded.icons. Was context passed through in the template? Falling back to using the sprite sheet reference instead.`); } } // ID of the icon within the sprite sheet for its icon set. - const iconID = getSpriteSheetName(idKey, setName); + const iconID = getSpriteSheetName(iconNameToLoad, setName); // Return SVG markup with a reference to the icon ID within the sprite sheet. return html` - ${idKey.replace(/([A-Z])/g, " $1").trim()} + ${iconNameToLoad.replace(/([A-Z])/g, " $1").trim()} `; }; @@ -304,7 +244,7 @@ export const WorkflowDefaultTemplate = (args, context) => html` }, context, [ - "AlertCircle", + "AlertTriangle", "Bell", "Camera", "Color", @@ -315,7 +255,7 @@ export const WorkflowDefaultTemplate = (args, context) => html` "Files", "Hand", "Lightbulb", - "Paragraph", + "InfoCircle", ] )} `; diff --git a/components/icon/stories/utilities.js b/components/icon/stories/utilities.js index 2ba609a3416..16faf30eaa3 100644 --- a/components/icon/stories/utilities.js +++ b/components/icon/stories/utilities.js @@ -190,3 +190,27 @@ export const getUiIconSizes = (uiIcons) => { }; export const uiIconSizes = getUiIconSizes(uiIconsWithDirections); + +/** + * If UI icon name does not have a sizing number appended, add one to approximate the provided + * t-shirt sizing for the component, based on the most common mapping. + * + * @param {string} uiIconName + * @param {string} size t-shirt sizing + * @returns {string} uiIconName with appended default sizing number, if one is not already present. + */ +export const appendUiIconDefaultSizing = (uiIconName, size = "m") => { + // If icon name already has a size number on the end, no change is needed. + if (uiIconName.match(/\d{2,3}$/)) { + return uiIconName; + } + + return uiIconName + ({ + xs: "50", + s: "75", + m: "100", + l: "200", + xl: "300", + xxl: "400", + }[size] || "100"); +}; diff --git a/components/menu/stories/template.js b/components/menu/stories/template.js index 79c4136fdad..98b765fc520 100644 --- a/components/menu/stories/template.js +++ b/components/menu/stories/template.js @@ -14,22 +14,6 @@ import { when } from "lit/directives/when.js"; import "../index.css"; -/** - * Get the tray submenu back arrow name with scale number (defined in design spec). - */ -const iconWithScale = (size = "m", iconName = "ArrowLeft") => { - switch (size) { - case "s": - return `${iconName}200`; - case "l": - return `${iconName}400`; - case "xl": - return `${iconName}500`; - default: - return `${iconName}300`; - } -}; - export const MenuItem = ( { rootClass = "spectrum-Menu-item", @@ -84,10 +68,7 @@ export const MenuItem = ( ${when(isCollapsible || (selectionMode == "single" && isSelected), () => Icon( { - iconName: iconWithScale( - size, - isCollapsible ? "ChevronRight" : "Checkmark", - ), + iconName: isCollapsible ? "ChevronRight" : "Checkmark", setName: "ui", useRef: false, size, @@ -196,7 +177,7 @@ export const MenuItem = ( ${when(isDrillIn, () => Icon( { - iconName: iconWithScale(size, "ChevronRight"), + iconName: "ChevronRight", setName: "ui", useRef: false, size, @@ -266,7 +247,12 @@ export const MenuGroup = ( > ${Icon( { - iconName: iconWithScale(size), + iconName: "ArrowRight" + ({ + s: "100", + m: "100", + l: "400", + xl: "400", + }[size] || "100"), setName: "ui", size, customClasses: ["spectrum-Menu-backIcon"], @@ -473,7 +459,7 @@ export const DisabledItemGroup = (args, context) => { context, shouldTruncate: group.shouldTruncate || false, items: group.items, - })} + }, context)} ` }, context)} `) @@ -582,11 +568,11 @@ export const OverflowGroup = (args, context) => { context, shouldTruncate: group.shouldTruncate || false, items: group.items, - })} + }, context)} ` - })} + }, context)} `) - }); + }, context); }; export const SelectionGroup = (args, context) => { @@ -700,12 +686,12 @@ export const SelectionGroup = (args, context) => { selectionMode: group.selectionMode || "none", hasActions: group.hasActions || false, items: group.items, - }) + }, context) }, context)) }); }; -export const SubmenuInPopover = (context) => Popover({ +export const SubmenuInPopover = (args, context) => Popover({ isOpen: true, position: "end-top", customStyles: { @@ -717,7 +703,8 @@ export const SubmenuInPopover = (context) => Popover({ ...args, }, context), content: [ - (args, context) => Template({ + Template({ + ...args, items: [ { label: "Language", @@ -732,9 +719,8 @@ export const SubmenuInPopover = (context) => Popover({ label: "Show grid", } ], - ...args }, context), - (args, context) => Popover({ + Popover({ isOpen: true, position: "end-top", customStyles: { @@ -742,7 +728,8 @@ export const SubmenuInPopover = (context) => Popover({ "inline-size": "120px", }, content: [ - (args, context) => Template({ + Template({ + ...args, selectionMode: "single", items: [ { @@ -765,10 +752,8 @@ export const SubmenuInPopover = (context) => Popover({ label: "日本語", } ], - ...args, }, context) ], - ...args, }, context) ], }, context); diff --git a/components/search/stories/template.js b/components/search/stories/template.js index 09570d33581..d017e28981f 100644 --- a/components/search/stories/template.js +++ b/components/search/stories/template.js @@ -34,6 +34,7 @@ export const Template = ({ size, customClasses: [`${rootClass}-textfield`], iconName: "Magnify", + setName: "workflow", type: "search", placeholder: "Search", name: "search", diff --git a/components/swatch/stories/template.js b/components/swatch/stories/template.js index c1ff884c4c6..bcf81178f6d 100644 --- a/components/swatch/stories/template.js +++ b/components/swatch/stories/template.js @@ -106,7 +106,12 @@ export const Template = ({ ...(isMixedValue ? [Icon({ customClasses: [`${rootClass}-mixedValueIcon`], setName: "ui", - iconName: "Dash", + iconName: "Dash" + ({ + xs: "75", + s: "75", + m: "100", + l: "200", + }[size] || "100"), useRef: false, }, context)] : []), ] diff --git a/components/textfield/stories/template.js b/components/textfield/stories/template.js index 9413a7504e3..e255eeae0ee 100644 --- a/components/textfield/stories/template.js +++ b/components/textfield/stories/template.js @@ -75,7 +75,7 @@ export const Template = ({ labelText, characterCount, iconName, - iconSet, + iconSet = "workflow", pattern, placeholder, name, diff --git a/components/treeview/stories/template.js b/components/treeview/stories/template.js index 6c15ccdacd7..7114a75a1ac 100644 --- a/components/treeview/stories/template.js +++ b/components/treeview/stories/template.js @@ -22,7 +22,7 @@ export const TreeViewItem = ({ isOpen, isDropTarget, icon, - iconSet, + iconSet = "workflow", thumbnail, items, variant,