From 07e5f0d3f28723d8916fbe4660a52fb67dc909ac Mon Sep 17 00:00:00 2001 From: F745860 Date: Mon, 19 Aug 2024 10:35:34 +0530 Subject: [PATCH 1/2] Provide a mechanism to swap out built-in component icons to custom icons --- .changeset/grumpy-lies-bathe.md | 11 ++ .../src/__tests__/__e2e__/link/Link.cy.tsx | 13 +++ .../SemanticIconProvider.cy.tsx | 102 +++++++++++++++++ .../core/src/accordion/AccordionHeader.tsx | 8 +- packages/core/src/avatar/Avatar.tsx | 13 ++- packages/core/src/combo-box/ComboBox.tsx | 8 +- .../core/src/dialog/DialogCloseButton.tsx | 3 +- .../core/src/drawer/DrawerCloseButton.tsx | 3 +- packages/core/src/dropdown/Dropdown.tsx | 5 +- .../src/file-drop-zone/FileDropZoneIcon.tsx | 3 + packages/core/src/index.ts | 1 + packages/core/src/link/Link.tsx | 18 +-- packages/core/src/menu/MenuItem.tsx | 9 +- .../src/navigation-item/ExpansionIcon.tsx | 25 +++-- .../src/overlay/OverlayPanelCloseButton.tsx | 3 +- .../core/src/pagination/CompactPaginator.tsx | 8 +- packages/core/src/pagination/Paginator.tsx | 7 +- packages/core/src/pill-input/PillInput.tsx | 5 +- .../SemanticIconProvider.tsx | 103 ++++++++++++++++++ .../core/src/semantic-icon-provider/index.ts | 1 + .../src/status-indicator/StatusIndicator.tsx | 28 ++--- .../semantic-icon-provider.mdx | 100 +++++++++++++++++ .../semantic-icon-provider.stories.tsx | 50 +++++++++ .../internal/BreadcrumbsCollapsed.tsx | 5 +- packages/lab/src/carousel/Carousel.tsx | 13 +-- .../src/cascading-menu/CascadingMenuItem.tsx | 7 +- .../src/contact-details/ContactMetadata.tsx | 7 +- packages/lab/src/dropdown/DropdownButton.tsx | 15 +-- .../lab/src/menu-button/MenuButtonTrigger.tsx | 6 +- .../query-input/internal/CategoryListItem.tsx | 7 +- .../src/query-input/internal/ValueList.tsx | 6 +- .../query-input/internal/ValueSelector.tsx | 2 +- packages/lab/src/search-input/SearchInput.tsx | 6 +- .../TrackerStep/TrackerStep.tsx | 27 ++--- .../lab/src/stepper-input/StepperInput.tsx | 7 +- packages/lab/src/tabs-next/OverflowMenu.tsx | 6 +- packages/lab/src/tabs/Tab.tsx | 35 +++--- packages/lab/src/tabs/Tabstrip.tsx | 7 +- .../TokenizedInputNext.tsx | 6 +- .../internal/InputPill.tsx | 3 +- .../tokenized-input/TokenizedInputBase.tsx | 6 +- .../tokenized-input/internal/InputPill.tsx | 4 +- .../toolbar/overflow-panel/OverflowPanel.tsx | 6 +- .../semantic-icon-provider/accessibility.mdx | 11 ++ .../semantic-icon-provider/examples.mdx | 29 +++++ .../semantic-icon-provider/index.mdx | 19 ++++ .../semantic-icon-provider/usage.mdx | 21 ++++ .../semantic-icon-provider/Default.tsx | 33 ++++++ .../semantic-icon-provider/WithIconMap.tsx | 39 +++++++ .../examples/semantic-icon-provider/index.ts | 2 + .../DetailComponent/TitleWithDrawer.tsx | 6 +- 51 files changed, 706 insertions(+), 162 deletions(-) create mode 100644 .changeset/grumpy-lies-bathe.md create mode 100644 packages/core/src/__tests__/__e2e__/semantic-icon-provider/SemanticIconProvider.cy.tsx create mode 100644 packages/core/src/semantic-icon-provider/SemanticIconProvider.tsx create mode 100644 packages/core/src/semantic-icon-provider/index.ts create mode 100644 packages/core/stories/semantic-icon-provider/semantic-icon-provider.mdx create mode 100644 packages/core/stories/semantic-icon-provider/semantic-icon-provider.stories.tsx create mode 100644 site/docs/components/semantic-icon-provider/accessibility.mdx create mode 100644 site/docs/components/semantic-icon-provider/examples.mdx create mode 100644 site/docs/components/semantic-icon-provider/index.mdx create mode 100644 site/docs/components/semantic-icon-provider/usage.mdx create mode 100644 site/src/examples/semantic-icon-provider/Default.tsx create mode 100644 site/src/examples/semantic-icon-provider/WithIconMap.tsx create mode 100644 site/src/examples/semantic-icon-provider/index.ts diff --git a/.changeset/grumpy-lies-bathe.md b/.changeset/grumpy-lies-bathe.md new file mode 100644 index 00000000000..20336015a84 --- /dev/null +++ b/.changeset/grumpy-lies-bathe.md @@ -0,0 +1,11 @@ +--- +"@salt-ds/core": minor +--- + +Added SemanticIconProvider to provide a mechanism to swap out built-in component icons to custom icons. + +```tsx + + + +``` diff --git a/packages/core/src/__tests__/__e2e__/link/Link.cy.tsx b/packages/core/src/__tests__/__e2e__/link/Link.cy.tsx index d200510d44d..be6b0efd3b9 100644 --- a/packages/core/src/__tests__/__e2e__/link/Link.cy.tsx +++ b/packages/core/src/__tests__/__e2e__/link/Link.cy.tsx @@ -42,4 +42,17 @@ describe("GIVEN a link", () => { cy.findByTestId(/TearOutIcon/i).should("not.exist"); }); + + it('WHEN target is "_blank" AND IconComponent is provided, THEN should render the provided IconComponent', () => { + const CustomIcon = () =>
Custom Icon
; + + cy.mount( + + Action + , + ); + + cy.findByTestId(/CustomIcon/i).should("exist"); + cy.findByTestId(/TearOutIcon/i).should("not.exist"); + }); }); diff --git a/packages/core/src/__tests__/__e2e__/semantic-icon-provider/SemanticIconProvider.cy.tsx b/packages/core/src/__tests__/__e2e__/semantic-icon-provider/SemanticIconProvider.cy.tsx new file mode 100644 index 00000000000..99ed2125986 --- /dev/null +++ b/packages/core/src/__tests__/__e2e__/semantic-icon-provider/SemanticIconProvider.cy.tsx @@ -0,0 +1,102 @@ +import { SemanticIconProvider, useIcon } from "@salt-ds/core"; +import { + CalendarIcon, + ChevronDownIcon, + ChevronUpIcon, + DoubleChevronDownIcon, + DoubleChevronUpIcon, + SuccessTickIcon, + UserSolidIcon, +} from "@salt-ds/icons"; + +const TestComponent = () => { + const icons = useIcon(); + return ( +
+ + + + + +
+ ); +}; + +describe("SemanticIconProvider Tests", () => { + it("should use default icons when provider is not wrapped", () => { + cy.mount(); + cy.get('[data-testid="ChevronDownIcon"]').should("exist"); + cy.get('[data-testid="SuccessTickIcon"]').should("exist"); + cy.get('[data-testid="CalendarIcon"]').should("exist"); + cy.get('[data-testid="UserSolidIcon"]').should("exist"); + }); + + it("should override only specific icons when provider is partially wrapped", () => { + cy.mount( + + + , + ); + + cy.get('[data-testid="ChevronDownIcon"]').should( + "have.attr", + "aria-label", + "double chevron down", + ); + cy.get('[data-testid="ChevronUpIcon"]').should( + "have.attr", + "aria-label", + "double chevron up", + ); + + cy.get('[data-testid="SuccessTickIcon"]').should("exist"); + cy.get('[data-testid="CalendarIcon"]').should("exist"); + cy.get('[data-testid="UserSolidIcon"]').should("exist"); + }); + + it("should override all icons when provider is fully wrapped", () => { + cy.mount( + + + , + ); + cy.get('[data-testid="ChevronDownIcon"]').should( + "have.attr", + "aria-label", + "chevron up", + ); + cy.get('[data-testid="ChevronUpIcon"]').should( + "have.attr", + "aria-label", + "chevron down", + ); + cy.get('[data-testid="SuccessTickIcon"]').should( + "have.attr", + "aria-label", + "user solid", + ); + cy.get('[data-testid="CalendarIcon"]').should( + "have.attr", + "aria-label", + "success tick", + ); + cy.get('[data-testid="UserSolidIcon"]').should( + "have.attr", + "aria-label", + "calendar", + ); + }); +}); diff --git a/packages/core/src/accordion/AccordionHeader.tsx b/packages/core/src/accordion/AccordionHeader.tsx index 9ec9ffc24f9..248aeac2202 100644 --- a/packages/core/src/accordion/AccordionHeader.tsx +++ b/packages/core/src/accordion/AccordionHeader.tsx @@ -1,4 +1,3 @@ -import { ChevronDownIcon, ChevronUpIcon } from "@salt-ds/icons"; import { useComponentCssInjection } from "@salt-ds/styles"; import { useWindow } from "@salt-ds/window"; import { clsx } from "clsx"; @@ -8,8 +7,8 @@ import { type ReactNode, forwardRef, } from "react"; +import { useIcon } from "../semantic-icon-provider"; import { StatusIndicator } from "../status-indicator"; - import { makePrefixer, useIsomorphicLayoutEffect } from "../utils"; import { useAccordion } from "./AccordionContext"; @@ -26,11 +25,12 @@ export interface AccordionHeaderProps const withBaseName = makePrefixer("saltAccordionHeader"); function ExpansionIcon({ expanded }: { expanded: boolean }) { + const { CollapseIcon, ExpandIcon } = useIcon(); if (expanded) { - return ; + return ; } - return ; + return ; } export const AccordionHeader = forwardRef< diff --git a/packages/core/src/avatar/Avatar.tsx b/packages/core/src/avatar/Avatar.tsx index 824dcf59e16..5da06a0c0a6 100644 --- a/packages/core/src/avatar/Avatar.tsx +++ b/packages/core/src/avatar/Avatar.tsx @@ -1,12 +1,11 @@ -import { UserSolidIcon } from "@salt-ds/icons"; import { useComponentCssInjection } from "@salt-ds/styles"; import { useWindow } from "@salt-ds/window"; import { clsx } from "clsx"; import { type HTMLAttributes, type ReactNode, forwardRef } from "react"; -import { useAvatarImage } from "./useAvatarImage"; - +import { useIcon } from "../semantic-icon-provider"; import { makePrefixer } from "../utils"; import avatarCss from "./Avatar.css"; +import { useAvatarImage } from "./useAvatarImage"; export type NameToInitials = (name?: string) => string; @@ -54,12 +53,18 @@ export const Avatar = forwardRef(function Avatar( src, size = DEFAULT_AVATAR_SIZE, style: styleProp, - fallbackIcon = , + fallbackIcon: fallbackIconProp, ...rest }, ref, ) { const targetWindow = useWindow(); + const { UserIcon } = useIcon(); + + const fallbackIcon = fallbackIconProp || ( + + ); + useComponentCssInjection({ testId: "salt-avatar", css: avatarCss, diff --git a/packages/core/src/combo-box/ComboBox.tsx b/packages/core/src/combo-box/ComboBox.tsx index f7e91b41aaa..13efbc4edfb 100644 --- a/packages/core/src/combo-box/ComboBox.tsx +++ b/packages/core/src/combo-box/ComboBox.tsx @@ -7,7 +7,6 @@ import { useFocus, useInteractions, } from "@floating-ui/react"; -import { ChevronDownIcon, ChevronUpIcon } from "@salt-ds/icons"; import { useComponentCssInjection } from "@salt-ds/styles"; import { useWindow } from "@salt-ds/window"; import { clsx } from "clsx"; @@ -34,6 +33,7 @@ import { import { defaultValueToString } from "../list-control/ListControlState"; import { OptionList } from "../option/OptionList"; import { PillInput, type PillInputProps } from "../pill-input"; +import { useIcon } from "../semantic-icon-provider"; import { type UseFloatingUIProps, makePrefixer, @@ -97,7 +97,7 @@ export const ComboBox = forwardRef(function ComboBox( css: comboBoxCss, window: targetWindow, }); - + const { CollapseIcon, ExpandIcon } = useIcon(); const { a11yProps: { "aria-labelledby": formFieldLabelledBy } = {}, disabled: formFieldDisabled, @@ -429,9 +429,9 @@ export const ComboBox = forwardRef(function ComboBox( tabIndex={-1} > {openState ? ( - + ) : ( - + )} ) : undefined} diff --git a/packages/core/src/dialog/DialogCloseButton.tsx b/packages/core/src/dialog/DialogCloseButton.tsx index a1fc22aa5a9..6424dffc163 100644 --- a/packages/core/src/dialog/DialogCloseButton.tsx +++ b/packages/core/src/dialog/DialogCloseButton.tsx @@ -1,9 +1,9 @@ -import { CloseIcon } from "@salt-ds/icons"; import { useComponentCssInjection } from "@salt-ds/styles"; import { useWindow } from "@salt-ds/window"; import clsx from "clsx"; import { forwardRef } from "react"; import { Button, type ButtonProps } from "../button"; +import { useIcon } from "../semantic-icon-provider"; import { makePrefixer } from "../utils"; import dialogCloseButtonCss from "./DialogCloseButton.css"; @@ -18,6 +18,7 @@ export const DialogCloseButton = forwardRef( css: dialogCloseButtonCss, window: targetWindow, }); + const { CloseIcon } = useIcon(); return ( {children ? children : } @@ -65,7 +67,7 @@ export const CompactPaginator = forwardRef< disabled={isOnLastPage} className={withBaseName("arrowButton")} > - + ); diff --git a/packages/core/src/pagination/Paginator.tsx b/packages/core/src/pagination/Paginator.tsx index f318b7c9d7d..41bfce30a8c 100644 --- a/packages/core/src/pagination/Paginator.tsx +++ b/packages/core/src/pagination/Paginator.tsx @@ -1,7 +1,7 @@ -import { ChevronLeftIcon, ChevronRightIcon } from "@salt-ds/icons"; import { clsx } from "clsx"; import { type HTMLAttributes, type MouseEventHandler, forwardRef } from "react"; import { Button } from "../button"; +import { useIcon } from "../semantic-icon-provider"; import { makePrefixer } from "../utils"; import { PageRanges } from "./PageRanges"; import { usePaginationContext } from "./usePaginationContext"; @@ -37,6 +37,7 @@ export const Paginator = forwardRef( }); const { count, page, onPageChange } = usePaginationContext(); + const { NextIcon, PreviousIcon } = useIcon(); const onPreviousPage: MouseEventHandler = (event) => { onPageChange(event, Math.max(1, page - 1)); @@ -58,7 +59,7 @@ export const Paginator = forwardRef( disabled={isOnFirstPage} className={withBaseName("arrowButton-previous")} > - + ); diff --git a/packages/core/src/pill-input/PillInput.tsx b/packages/core/src/pill-input/PillInput.tsx index d2a9213935a..0e5f5336745 100644 --- a/packages/core/src/pill-input/PillInput.tsx +++ b/packages/core/src/pill-input/PillInput.tsx @@ -1,4 +1,3 @@ -import { CloseIcon, OverflowMenuIcon } from "@salt-ds/icons"; import { useComponentCssInjection } from "@salt-ds/styles"; import { useWindow } from "@salt-ds/window"; import { clsx } from "clsx"; @@ -19,6 +18,7 @@ import { } from "react"; import { useFormFieldProps } from "../form-field-context"; import { Pill } from "../pill"; +import { useIcon } from "../semantic-icon-provider"; import { StatusAdornment } from "../status-adornment"; import type { DataAttributes } from "../types"; import { makePrefixer, useControlled, useForkRef, useId } from "../utils"; @@ -115,6 +115,7 @@ export const PillInput = forwardRef(function PillInput( ref: ForwardedRef, ) { const targetWindow = useWindow(); + const { OverflowIcon, CloseIcon } = useIcon(); useComponentCssInjection({ testId: "salt-pill-input", css: pillInputCss, @@ -322,7 +323,7 @@ export const PillInput = forwardRef(function PillInput( data-overflowindicator className={withBaseName("overflowIndicator")} > - + )} diff --git a/packages/core/src/semantic-icon-provider/SemanticIconProvider.tsx b/packages/core/src/semantic-icon-provider/SemanticIconProvider.tsx new file mode 100644 index 00000000000..c8b19c46142 --- /dev/null +++ b/packages/core/src/semantic-icon-provider/SemanticIconProvider.tsx @@ -0,0 +1,103 @@ +import { + CalendarIcon, + ChevronDownIcon, + ChevronLeftIcon, + ChevronRightIcon, + ChevronUpIcon, + CloseIcon, + ErrorSolidIcon, + InfoSolidIcon, + OverflowMenuIcon, + StepActiveIcon, + StepDefaultIcon, + StepSuccessIcon, + SuccessTickIcon, + TearOutIcon, + TriangleDownIcon, + TriangleUpIcon, + UploadIcon, + UserSolidIcon, + WarningSolidIcon, +} from "@salt-ds/icons"; +import { + type ElementType, + type ReactNode, + createContext, + useContext, +} from "react"; + +export type SemanticIconMap = { + ExpandIcon: ElementType; + CollapseIcon: ElementType; + ExpandGroupIcon: ElementType; + CollapseGroupIcon: ElementType; + NextIcon: ElementType; + PreviousIcon: ElementType; + IncreaseIcon: ElementType; + DecreaseIcon: ElementType; + UploadIcon: ElementType; + ErrorIcon: ElementType; + SuccessIcon: ElementType; + InfoIcon: ElementType; + WarningIcon: ElementType; + OverflowIcon: ElementType; + UserIcon: ElementType; + CalendarIcon: ElementType; + CloseIcon: ElementType; + ExternalIcon: ElementType; + PendingIcon: ElementType; + ActiveIcon: ElementType; + CompletedIcon: ElementType; +}; + +export interface SemanticIconProviderProps { + /** + * Custom mapping of icon names to components. Overrides default icons if provided. + */ + iconMap?: Partial; + + /** + * Child elements that will use the provided icons. + */ + children: ReactNode; +} + +const defaultIconMap: SemanticIconMap = { + ExpandIcon: ChevronDownIcon, + CollapseIcon: ChevronUpIcon, + ExpandGroupIcon: ChevronRightIcon, + CollapseGroupIcon: ChevronDownIcon, + NextIcon: ChevronRightIcon, + PreviousIcon: ChevronLeftIcon, + IncreaseIcon: TriangleUpIcon, + DecreaseIcon: TriangleDownIcon, + UploadIcon, + ErrorIcon: ErrorSolidIcon, + SuccessIcon: SuccessTickIcon, + InfoIcon: InfoSolidIcon, + WarningIcon: WarningSolidIcon, + OverflowIcon: OverflowMenuIcon, + UserIcon: UserSolidIcon, + CalendarIcon: CalendarIcon, + CloseIcon: CloseIcon, + ExternalIcon: TearOutIcon, + PendingIcon: StepDefaultIcon, + ActiveIcon: StepActiveIcon, + CompletedIcon: StepSuccessIcon, +}; + +const SemanticIconContext = createContext(defaultIconMap); + +export const SemanticIconProvider = ({ + iconMap = {}, + children, +}: SemanticIconProviderProps) => ( + + {children} + +); + +export const useIcon = () => { + const context = useContext(SemanticIconContext); + return context || defaultIconMap; +}; diff --git a/packages/core/src/semantic-icon-provider/index.ts b/packages/core/src/semantic-icon-provider/index.ts new file mode 100644 index 00000000000..aa8e2d449b1 --- /dev/null +++ b/packages/core/src/semantic-icon-provider/index.ts @@ -0,0 +1 @@ +export * from "./SemanticIconProvider"; diff --git a/packages/core/src/status-indicator/StatusIndicator.tsx b/packages/core/src/status-indicator/StatusIndicator.tsx index a112e0d44d1..14e4090ae31 100644 --- a/packages/core/src/status-indicator/StatusIndicator.tsx +++ b/packages/core/src/status-indicator/StatusIndicator.tsx @@ -1,11 +1,4 @@ -import { - DEFAULT_ICON_SIZE, - ErrorSolidIcon, - type IconProps, - InfoSolidIcon, - SuccessTickIcon, - WarningSolidIcon, -} from "@salt-ds/icons"; +import { DEFAULT_ICON_SIZE, type IconProps } from "@salt-ds/icons"; import { clsx } from "clsx"; import { forwardRef } from "react"; import { makePrefixer } from "../utils"; @@ -13,15 +6,9 @@ import type { ValidationStatus } from "./ValidationStatus"; import { useComponentCssInjection } from "@salt-ds/styles"; import { useWindow } from "@salt-ds/window"; +import { useIcon } from "../semantic-icon-provider"; import statusIndicatorCss from "./StatusIndicator.css"; -const icons = { - error: ErrorSolidIcon, - success: SuccessTickIcon, - warning: WarningSolidIcon, - info: InfoSolidIcon, -}; - export interface StatusIndicatorProps extends IconProps { /** * Status indicator to be displayed. @@ -50,7 +37,16 @@ export const StatusIndicator = forwardRef( window: targetWindow, }); - const IconComponent = icons[status]; + const { ErrorIcon, WarningIcon, SuccessIcon, InfoIcon } = useIcon(); + + const iconMap = { + error: ErrorIcon, + success: SuccessIcon, + warning: WarningIcon, + info: InfoIcon, + }; + + const IconComponent = iconMap[status]; const ariaLabel = statusToAriaLabelMap[status]; return ( diff --git a/packages/core/stories/semantic-icon-provider/semantic-icon-provider.mdx b/packages/core/stories/semantic-icon-provider/semantic-icon-provider.mdx new file mode 100644 index 00000000000..0e0793b7ed0 --- /dev/null +++ b/packages/core/stories/semantic-icon-provider/semantic-icon-provider.mdx @@ -0,0 +1,100 @@ +import { Meta } from "@storybook/addon-docs"; +import { Banner, BannerContent } from "@salt-ds/core"; + + + +# Semantic Icon Provider + +The SemanticIconProvider introduces a centralized approach to managing and customizing icons in Salt’s ecosystem. This provider allows for flexible configuration of icon components and ensures consistent icon usage across your application. + +It extends the existing icon management framework by offering a context-based solution for overriding and customizing icons as needed. + +#Usage +To use the SemanticIconProvider, replace any existing icon management setup with this provider and provide any custom icon mappings as required. + +```js static +import { + ChevronDownIcon, + SuccessTickIcon, + ErrorSolidIcon, +} from "@salt-ds/icons"; +import { SemanticIconProvider } from "@salt-ds/core"; + +const customIconMap = { + ExpandIcon: ChevronDownIcon, + SuccessIcon: SuccessTickIcon, + ErrorIcon: ErrorSolidIcon, +}; + +const App = () => ( + + {/* Your application components */} + +); +``` + +## Icon Mapping + +The SemanticIconProvider allows you to specify an iconMap to manage which icon components are used for various icon names. This provides flexibility to customize the icon set used in your application. + +### Default Icon Map + +The default icon map includes the following icons: + +```tsx +const defaultIconMap = { + ExpandIcon: ChevronDownIcon; + CollapseIcon: ChevronUpIcon; + ExpandGroupIcon: ChevronRightIcon; + CollapseGroupIcon: ChevronDownIcon; + NextIcon: ChevronRightIcon; + PreviousIcon: ChevronLeftIcon; + IncreaseIcon: TriangleUpIcon; + DecreaseIcon: TriangleDownIcon; + UploadIcon; + ErrorIcon: ErrorSolidIcon; + SuccessIcon: SuccessTickIcon; + InfoIcon: InfoSolidIcon; + WarningIcon: WarningSolidIcon; + OverflowIcon: OverflowMenuIcon; + UserIcon: UserSolidIcon; + CalendarIcon: CalendarIcon; + CloseIcon: CloseIcon; + ExternalIcon: TearOutIcon; + PendingIcon: StepDefaultIcon; + ActiveIcon: StepActiveIcon; + CompletedIcon: StepSuccessIcon; +}; +``` + +### Customizing Icons + +To customize the icons, provide a partial SemanticIconMap to the SemanticIconProvider. This allows you to override specific icons while retaining others. + +```tsx +import { CustomIcon } from "@my-icons/custom-icons"; + +const customIconMap = { + ExpandIcon: CustomIcon, +}; + +const App = () => ( + + {/* Your application components */} + +); +``` + +### Using Icons + +To use an icon within your components, utilize the useIcon hook provided by the SemanticIconProvider. This hook provides access to the current icon map, allowing you to retrieve the appropriate icon component. + +```tsx +import { useIcon } from "@salt-ds/core"; + +const MyComponent = () => { + const { ExpandIcon } = useIcon(); + + return ; +}; +``` diff --git a/packages/core/stories/semantic-icon-provider/semantic-icon-provider.stories.tsx b/packages/core/stories/semantic-icon-provider/semantic-icon-provider.stories.tsx new file mode 100644 index 00000000000..1cd90a474c7 --- /dev/null +++ b/packages/core/stories/semantic-icon-provider/semantic-icon-provider.stories.tsx @@ -0,0 +1,50 @@ +import { Dropdown, Option, SemanticIconProvider } from "@salt-ds/core"; +import { DoubleChevronDownIcon, DoubleChevronUpIcon } from "@salt-ds/icons"; + +import "docs/story.css"; + +export default { + title: "Core/Semantic Icon Provider", + component: SemanticIconProvider, +}; + +const usStates = [ + "Alabama", + "Alaska", + "Arizona", + "Arkansas", + "California", + "Connecticut", + "Delaware", + "Florida", + "Georgia", +]; + +export const Default = () => { + return ( + + + {usStates.map((state) => ( + + + ); +}; + +export const WithIconMap = () => { + return ( + + + {usStates.map((state) => ( + + + ); +}; diff --git a/packages/lab/src/breadcrumbs/internal/BreadcrumbsCollapsed.tsx b/packages/lab/src/breadcrumbs/internal/BreadcrumbsCollapsed.tsx index bb90e6036f2..a90bcc5134e 100644 --- a/packages/lab/src/breadcrumbs/internal/BreadcrumbsCollapsed.tsx +++ b/packages/lab/src/breadcrumbs/internal/BreadcrumbsCollapsed.tsx @@ -1,4 +1,4 @@ -import { OverflowMenuIcon } from "@salt-ds/icons"; +import { useIcon } from "@salt-ds/core"; import { Children, type Component, @@ -34,6 +34,7 @@ export const BreadcrumbsCollapsed = ({ return ""; }); + const { OverflowIcon } = useIcon() || {}; const key = keys ? keys.join("") : ""; const { ref, shouldFocusOnMount } = useFocusMenuRemount(key); @@ -77,7 +78,7 @@ export const BreadcrumbsCollapsed = ({ {...rest} ref={ref} > - + ); }; diff --git a/packages/lab/src/carousel/Carousel.tsx b/packages/lab/src/carousel/Carousel.tsx index c46c0f34c7c..96a1ffa8281 100644 --- a/packages/lab/src/carousel/Carousel.tsx +++ b/packages/lab/src/carousel/Carousel.tsx @@ -4,9 +4,11 @@ import { RadioButton, RadioButtonGroup, makePrefixer, + useIcon, useId, } from "@salt-ds/core"; -import { ChevronLeftIcon, ChevronRightIcon } from "@salt-ds/icons"; +import { useComponentCssInjection } from "@salt-ds/styles"; +import { useWindow } from "@salt-ds/window"; import { clsx } from "clsx"; import { type ChangeEventHandler, @@ -17,9 +19,6 @@ import { useEffect, } from "react"; import { DeckLayout } from "../deck-layout"; - -import { useComponentCssInjection } from "@salt-ds/styles"; -import { useWindow } from "@salt-ds/window"; import { useSlideSelection } from "../utils"; import type { CarouselSlideProps } from "./CarouselSlide"; @@ -81,7 +80,7 @@ export const Carousel = forwardRef( css: carouselCss, window: targetWindow, }); - + const { NextIcon, PreviousIcon } = useIcon(); const id = useId(idProp); const slidesCount = Children.count(children); @@ -135,7 +134,7 @@ export const Carousel = forwardRef( className={withBaseName("prev-button")} onClick={() => moveSlide("left")} > - + ( className={withBaseName("next-button")} onClick={() => moveSlide("right")} > - +
( css: cascadingMenuItemCss, window: targetWindow, }); - + const { ExpandGroupIcon } = useIcon(); const menuTextRef = useRef(null); const [hasTooltip, setHasTooltip] = useState(false); const menuText = itemToString(sourceItem); @@ -169,7 +168,7 @@ export const DefaultMenuItem = forwardRef( [withBaseName("menuItemAdornmentHidden")]: !hasSubMenu, })} > - ( const { collapsible, children, collapseButtonId, className, ...restProps } = props; const { primaryId, variant, isStacked } = useContactDetailsContext(); - + const { CollapseIcon, ExpandIcon } = useIcon(); const [showMetadata, setShowMetadata] = useState(!collapsible); const toggleShowMetadata = () => { @@ -43,7 +42,7 @@ export const ContactMetadata = forwardRef( variant="secondary" className={withBaseName("expander")} > - {showMetadata ? : } + {showMetadata ? : } ) : null} {!collapsible || showMetadata ? ( diff --git a/packages/lab/src/dropdown/DropdownButton.tsx b/packages/lab/src/dropdown/DropdownButton.tsx index faea95e06a2..dab5e43f0a5 100644 --- a/packages/lab/src/dropdown/DropdownButton.tsx +++ b/packages/lab/src/dropdown/DropdownButton.tsx @@ -1,9 +1,5 @@ -import { Button, type ButtonProps, makePrefixer } from "@salt-ds/core"; -import { - ChevronDownIcon, - DEFAULT_ICON_SIZE, - type IconProps, -} from "@salt-ds/icons"; +import { Button, type ButtonProps, makePrefixer, useIcon } from "@salt-ds/core"; +import { DEFAULT_ICON_SIZE, type IconProps } from "@salt-ds/icons"; import { clsx } from "clsx"; import { type AriaAttributes, @@ -68,7 +64,7 @@ const withBaseName = makePrefixer("saltDropdownButton"); export const DropdownButton = forwardRef(function DropdownButton( { - IconComponent = ChevronDownIcon, + IconComponent, ariaHideOptionRole, className, disabled, @@ -90,7 +86,8 @@ export const DropdownButton = forwardRef(function DropdownButton( css: dropdownButtonCss, window: targetWindow, }); - + const { ExpandIcon } = useIcon(); + const Icon = IconComponent ?? ExpandIcon; const { inFormField } = useFormFieldLegacyProps(); // FIXME: use polymorphic button // We don't want the 'button' tag to be shown in the DOM to trigger some accessibility testing @@ -123,7 +120,7 @@ export const DropdownButton = forwardRef(function DropdownButton( > {label} - {children} {!hideCaret && ( - diff --git a/packages/lab/src/query-input/internal/CategoryListItem.tsx b/packages/lab/src/query-input/internal/CategoryListItem.tsx index a67b38b272a..ca100791bd8 100644 --- a/packages/lab/src/query-input/internal/CategoryListItem.tsx +++ b/packages/lab/src/query-input/internal/CategoryListItem.tsx @@ -1,5 +1,4 @@ -import { makePrefixer } from "@salt-ds/core"; -import { ChevronRightIcon } from "@salt-ds/icons"; +import { makePrefixer, useIcon } from "@salt-ds/core"; import { type CSSProperties, useMemo, useRef } from "react"; import { ListItem, type ListItemProps, type ListItemType } from "../../list"; import type { QueryInputCategory } from "../queryInputTypes"; @@ -16,7 +15,7 @@ export const CategoryListItem: ListItemType = function CategoryListItem({ item: category, ...props }) { const textRef = useRef(null); const context = useCategoryListContext(); - + const { ExpandGroupIcon } = useIcon(); const textStyle: CSSProperties = useMemo( () => ({ minWidth: context.width, @@ -36,7 +35,7 @@ export const CategoryListItem: ListItemType = )
- + ); }; diff --git a/packages/lab/src/query-input/internal/ValueList.tsx b/packages/lab/src/query-input/internal/ValueList.tsx index a7590f2deba..d5638f50c6b 100644 --- a/packages/lab/src/query-input/internal/ValueList.tsx +++ b/packages/lab/src/query-input/internal/ValueList.tsx @@ -1,5 +1,4 @@ -import { makePrefixer } from "@salt-ds/core"; -import { ChevronLeftIcon } from "@salt-ds/icons"; +import { makePrefixer, useIcon } from "@salt-ds/core"; import type { Dispatch, ReactElement, SetStateAction } from "react"; import type { SelectHandler } from "../../common-hooks"; import { List, ListItem } from "../../list"; @@ -27,6 +26,7 @@ export function ValueList(props: ValueListProps) { highlightedValueIndex, setHighlightedValueIndex, } = props; + const { PreviousIcon } = useIcon(); const handleSelect: SelectHandler = (_, value: string) => { onValueToggle(category!, value); @@ -57,7 +57,7 @@ export function ValueList(props: ValueListProps) { onClick={onBack} selectable={false} > - +
{category ? category.name : ""}
diff --git a/packages/lab/src/query-input/internal/ValueSelector.tsx b/packages/lab/src/query-input/internal/ValueSelector.tsx index 808d463e3b1..8bc489e0b99 100644 --- a/packages/lab/src/query-input/internal/ValueSelector.tsx +++ b/packages/lab/src/query-input/internal/ValueSelector.tsx @@ -60,7 +60,7 @@ export function ValueSelector(props: ValueSelectorProps) { setHighlightedCategoryIndex, setHighlightedIndex, } = props; - + console.log("Testtttttt"); const selectedCategoryValues = useMemo(() => { if (!selectedCategory) { return []; diff --git a/packages/lab/src/search-input/SearchInput.tsx b/packages/lab/src/search-input/SearchInput.tsx index e4224326fef..6bd01070e16 100644 --- a/packages/lab/src/search-input/SearchInput.tsx +++ b/packages/lab/src/search-input/SearchInput.tsx @@ -1,5 +1,5 @@ -import { Button, useControlled, useForkRef } from "@salt-ds/core"; -import { CloseIcon, SearchIcon } from "@salt-ds/icons"; +import { Button, useControlled, useForkRef, useIcon } from "@salt-ds/core"; +import { SearchIcon } from "@salt-ds/icons"; import { clsx } from "clsx"; import { type ChangeEvent, @@ -66,7 +66,7 @@ export const SearchInput = forwardRef( css: searchInputCss, window: targetWindow, }); - + const { CloseIcon } = useIcon(); const inputRef = useRef(null); const handleRef = useForkRef(inputRef, ref); diff --git a/packages/lab/src/stepped-tracker/TrackerStep/TrackerStep.tsx b/packages/lab/src/stepped-tracker/TrackerStep/TrackerStep.tsx index 595f52af20c..67dab321702 100644 --- a/packages/lab/src/stepped-tracker/TrackerStep/TrackerStep.tsx +++ b/packages/lab/src/stepped-tracker/TrackerStep/TrackerStep.tsx @@ -1,11 +1,4 @@ -import { type ValidationStatus, makePrefixer } from "@salt-ds/core"; -import { - ErrorSolidIcon, - StepActiveIcon, - StepDefaultIcon, - StepSuccessIcon, - WarningSolidIcon, -} from "@salt-ds/icons"; +import { type ValidationStatus, makePrefixer, useIcon } from "@salt-ds/core"; import { useComponentCssInjection } from "@salt-ds/styles"; import { useWindow } from "@salt-ds/window"; import { clsx } from "clsx"; @@ -37,14 +30,6 @@ export interface TrackerStepProps extends ComponentPropsWithoutRef<"li"> { status?: StatusOptions; } -const iconMap = { - pending: StepDefaultIcon, - active: StepActiveIcon, - completed: StepSuccessIcon, - warning: WarningSolidIcon, - error: ErrorSolidIcon, -}; - const useCheckWithinSteppedTracker = (isWithinSteppedTracker: boolean) => { useEffect(() => { if (process.env.NODE_ENV !== "production") { @@ -89,7 +74,8 @@ export const TrackerStep = forwardRef( css: trackerStepCss, window: targetWindow, }); - + const { ErrorIcon, WarningIcon, CompletedIcon, ActiveIcon, PendingIcon } = + useIcon(); const { activeStep, totalSteps, isWithinSteppedTracker } = useSteppedTrackerContext(); const stepNumber = useTrackerStepContext(); @@ -98,6 +84,13 @@ export const TrackerStep = forwardRef( const isActive = activeStep === stepNumber; const iconName = parseIconName({ stage, status, active: isActive }); + const iconMap = { + pending: PendingIcon, + active: ActiveIcon, + completed: CompletedIcon, + warning: WarningIcon, + error: ErrorIcon, + }; const Icon = iconMap[iconName]; const connectorState = activeStep > stepNumber ? "active" : "default"; diff --git a/packages/lab/src/stepper-input/StepperInput.tsx b/packages/lab/src/stepper-input/StepperInput.tsx index 33b44bacd43..17c11dbc4e9 100644 --- a/packages/lab/src/stepper-input/StepperInput.tsx +++ b/packages/lab/src/stepper-input/StepperInput.tsx @@ -7,6 +7,7 @@ import { useControlled, useForkRef, useFormFieldProps, + useIcon, } from "@salt-ds/core"; import { TriangleDownIcon, TriangleUpIcon } from "@salt-ds/icons"; import { useComponentCssInjection } from "@salt-ds/styles"; @@ -176,6 +177,8 @@ export const StepperInput = forwardRef( window: targetWindow, }); + const { IncreaseIcon, DecreaseIcon } = useIcon(); + const { a11yProps: { "aria-describedby": formFieldDescribedBy, @@ -395,7 +398,7 @@ export const StepperInput = forwardRef( )} {...incrementButtonProps} > - + )} diff --git a/packages/lab/src/tabs-next/OverflowMenu.tsx b/packages/lab/src/tabs-next/OverflowMenu.tsx index ec8eb0b5457..950b29dd006 100644 --- a/packages/lab/src/tabs-next/OverflowMenu.tsx +++ b/packages/lab/src/tabs-next/OverflowMenu.tsx @@ -1,5 +1,4 @@ -import { Button, useForkRef } from "@salt-ds/core"; -import { OverflowMenuIcon } from "@salt-ds/icons"; +import { Button, useForkRef, useIcon } from "@salt-ds/core"; import { type ReactNode, forwardRef } from "react"; import { useOverflowContext, useOverflowMenu } from "@fluentui/react-overflow"; @@ -19,6 +18,7 @@ export const OverflowMenu = forwardRef( const { tabs, ...rest } = props; const { ref, overflowCount, isOverflowing } = useOverflowMenu(); + const { OverflowIcon } = useIcon() || {}; const handleRef = useForkRef(ref, forwardedRef); const itemVisibility = useOverflowContext( (context) => context.itemVisibility, @@ -33,7 +33,7 @@ export const OverflowMenu = forwardRef( aria-label={`${overflowCount} more tabs`} triggerComponent={ } width="auto" diff --git a/packages/lab/src/tabs/Tab.tsx b/packages/lab/src/tabs/Tab.tsx index aa7c85ddb22..52916eedb9f 100644 --- a/packages/lab/src/tabs/Tab.tsx +++ b/packages/lab/src/tabs/Tab.tsx @@ -4,8 +4,8 @@ import { type ButtonProps, makePrefixer, useForkRef, + useIcon, } from "@salt-ds/core"; -import { CloseIcon } from "@salt-ds/icons"; import { useComponentCssInjection } from "@salt-ds/styles"; import { useWindow } from "@salt-ds/window"; import { clsx } from "clsx"; @@ -29,22 +29,25 @@ const noop = () => undefined; const withBaseName = makePrefixer("saltTab"); -const CloseTabButton = (props: ButtonProps) => ( - // FIXME: use polymorphic button - -); + className={withBaseName("closeButton")} + tabIndex={undefined} + title="Close Tab (Delete or Backspace)" + variant="secondary" + > + + + ); +}; export const Tab = forwardRef(function Tab( { diff --git a/packages/lab/src/tabs/Tabstrip.tsx b/packages/lab/src/tabs/Tabstrip.tsx index f45d28f9bb1..bc1570b15fb 100644 --- a/packages/lab/src/tabs/Tabstrip.tsx +++ b/packages/lab/src/tabs/Tabstrip.tsx @@ -2,10 +2,11 @@ import { Button, Tooltip, makePrefixer, + useIcon, useIdMemo, useIsomorphicLayoutEffect, } from "@salt-ds/core"; -import { AddIcon, OverflowMenuIcon } from "@salt-ds/icons"; +import { AddIcon } from "@salt-ds/icons"; import { clsx } from "clsx"; import { Children, @@ -113,7 +114,7 @@ export const Tabstrip = forwardRef(function Tabstrip( const activeRef = useRef( activeTabIndexProp || defaultActiveTabIndex || 0, ); - + const { OverflowIcon } = useIcon(); const overflowItemsRef = useRef([]); const [showOverflowMenu, _setShowOverflowMenu] = useState(false); @@ -437,7 +438,7 @@ export const Tabstrip = forwardRef(function Tabstrip( variant="secondary" tabIndex={-1} > - + } width="auto" diff --git a/packages/lab/src/tokenized-input-next/TokenizedInputNext.tsx b/packages/lab/src/tokenized-input-next/TokenizedInputNext.tsx index 891e575fc62..0629935f17d 100644 --- a/packages/lab/src/tokenized-input-next/TokenizedInputNext.tsx +++ b/packages/lab/src/tokenized-input-next/TokenizedInputNext.tsx @@ -7,9 +7,9 @@ import { type ValidationStatus, makePrefixer, useForkRef, + useIcon, useId, } from "@salt-ds/core"; -import { CloseIcon, OverflowMenuIcon } from "@salt-ds/icons"; import { useComponentCssInjection } from "@salt-ds/styles"; import { useWindow } from "@salt-ds/window"; import { clsx } from "clsx"; @@ -167,7 +167,7 @@ export const TokenizedInputNext = forwardRef(function TokenizedInputNext( "aria-describedby": ariaDescribedBy, ...restProps } = inputProps; - + const { OverflowIcon, CloseIcon } = useIcon(); const id = useId(idProp); const inputId = `${id}-input`; const expandButtonId = `${id}-expand-button`; @@ -364,7 +364,7 @@ export const TokenizedInputNext = forwardRef(function TokenizedInputNext( data-testid="expand-button" {...restExpandButtonProps} > - + )} diff --git a/packages/lab/src/tokenized-input-next/internal/InputPill.tsx b/packages/lab/src/tokenized-input-next/internal/InputPill.tsx index 950eb220da8..0473a44546d 100644 --- a/packages/lab/src/tokenized-input-next/internal/InputPill.tsx +++ b/packages/lab/src/tokenized-input-next/internal/InputPill.tsx @@ -3,9 +3,9 @@ import { type PillProps, Tooltip, makePrefixer, + useIcon, useIsomorphicLayoutEffect, } from "@salt-ds/core"; -import { CloseIcon } from "@salt-ds/icons"; import { useComponentCssInjection } from "@salt-ds/styles"; import { useWindow } from "@salt-ds/window"; import { clsx } from "clsx"; @@ -67,6 +67,7 @@ export const InputPill = memo(function InputPill(props: InputPillProps) { window: targetWindow, }); + const { CloseIcon } = useIcon(); const ref = useRef(null); const [isEllipsisActive, setEllipsisActive] = useState(false); const isRemovable = Boolean(onClose); diff --git a/packages/lab/src/tokenized-input/TokenizedInputBase.tsx b/packages/lab/src/tokenized-input/TokenizedInputBase.tsx index a31b805fed9..1ea816b756d 100644 --- a/packages/lab/src/tokenized-input/TokenizedInputBase.tsx +++ b/packages/lab/src/tokenized-input/TokenizedInputBase.tsx @@ -4,10 +4,10 @@ import { makePrefixer, useDensity, useForkRef, + useIcon, useId, useIsomorphicLayoutEffect, } from "@salt-ds/core"; -import { CloseIcon, OverflowMenuIcon } from "@salt-ds/icons"; import { useComponentCssInjection } from "@salt-ds/styles"; import { useWindow } from "@salt-ds/window"; import { clsx } from "clsx"; @@ -143,7 +143,7 @@ export const TokenizedInputBase = forwardRef(function TokenizedInputBase( }); const density = useDensity(); - + const { OverflowIcon, CloseIcon } = useIcon(); const id = useId(idProp); const inputId = `${id}-input`; const expandButtonId = `${id}-expand-button`; @@ -368,7 +368,7 @@ export const TokenizedInputBase = forwardRef(function TokenizedInputBase( variant="secondary" {...restExpandButtonProps} > - (null); const isRemovable = Boolean(onDelete); - + const { CloseIcon } = useIcon(); // useLayoutEffect to match the calcFirstHiddenIndex in TokenizedInputBase // We need to collect widths before the calculation useIsomorphicLayoutEffect(() => { diff --git a/packages/lab/src/toolbar/overflow-panel/OverflowPanel.tsx b/packages/lab/src/toolbar/overflow-panel/OverflowPanel.tsx index eedfcb049ba..989d3c318f5 100644 --- a/packages/lab/src/toolbar/overflow-panel/OverflowPanel.tsx +++ b/packages/lab/src/toolbar/overflow-panel/OverflowPanel.tsx @@ -2,9 +2,9 @@ import { Button, makePrefixer, useControlled, + useIcon, useIdMemo as useId, } from "@salt-ds/core"; -import { OverflowMenuIcon } from "@salt-ds/icons"; import { clsx } from "clsx"; import { type ForwardedRef, @@ -61,7 +61,7 @@ export const OverflowPanel = forwardRef(function DropdownPanel( css: overflowPanelCss, window: targetWindow, }); - + const { OverflowIcon } = useIcon(); const id = useId(); const collectionHook = useCollectionItems({ id, @@ -104,7 +104,7 @@ export const OverflowPanel = forwardRef(function DropdownPanel( const getTriggerButtonIcon = () => (triggerButtonIcon ?? triggerButtonLabel === undefined) ? ( - + ) : undefined; return ( diff --git a/site/docs/components/semantic-icon-provider/accessibility.mdx b/site/docs/components/semantic-icon-provider/accessibility.mdx new file mode 100644 index 00000000000..a56daf9354d --- /dev/null +++ b/site/docs/components/semantic-icon-provider/accessibility.mdx @@ -0,0 +1,11 @@ +--- +title: + $ref: ./#/title +layout: DetailComponent +sidebar: + exclude: true +data: + $ref: ./#/data +--- + +This component has no accessibility guidance. diff --git a/site/docs/components/semantic-icon-provider/examples.mdx b/site/docs/components/semantic-icon-provider/examples.mdx new file mode 100644 index 00000000000..f738163cfc0 --- /dev/null +++ b/site/docs/components/semantic-icon-provider/examples.mdx @@ -0,0 +1,29 @@ +--- +title: + $ref: ./#/title +layout: DetailComponent +sidebar: + exclude: true +data: + $ref: ./#/data +--- + + + + + +## Default + +Use the `SemanticIconProvider` to replace any existing icon management setup with this provider and provide any custom icon mappings as required. + + + + + +## iconMap + +The `SemanticIconProvider` allows you to specify an `iconMap` to manage which icon components are used for various icon names. This provides flexibility to customize the icon set used in your application. + + + + diff --git a/site/docs/components/semantic-icon-provider/index.mdx b/site/docs/components/semantic-icon-provider/index.mdx new file mode 100644 index 00000000000..38547d60dee --- /dev/null +++ b/site/docs/components/semantic-icon-provider/index.mdx @@ -0,0 +1,19 @@ +--- +title: Semantic icon provider +data: + description: "`SemanticIconProvider` introduces a optional centralized approach to managing and customizing icons in Salt’s ecosystem. This provider allows for flexible configuration of icon components and ensures consistent icon usage across your application." + + # Fill in the info from the content template's "Metadata" table below. + # To omit optional items, comment them out with # + sourceCodeUrl: "https://github.com/jpmorganchase/salt-ds/tree/main/packages/core/src/semantic-icon-provider" + package: + name: "@salt-ds/core" + initialVersion: "" + alsoKnownAs: [] + relatedComponents: [] + +# Leave this as is +layout: DetailComponent +--- + +{/* This area stays blank */} diff --git a/site/docs/components/semantic-icon-provider/usage.mdx b/site/docs/components/semantic-icon-provider/usage.mdx new file mode 100644 index 00000000000..580608b54df --- /dev/null +++ b/site/docs/components/semantic-icon-provider/usage.mdx @@ -0,0 +1,21 @@ +--- +title: + $ref: ./#/title +layout: DetailComponent +sidebar: + exclude: true +data: + $ref: ./#/data +--- + +## Import + +To import `SemanticIconProvider` from the core Salt package, use: + +``` +import { SemanticIconProvider } from "@salt-ds/core"; +``` + +## Props + + diff --git a/site/src/examples/semantic-icon-provider/Default.tsx b/site/src/examples/semantic-icon-provider/Default.tsx new file mode 100644 index 00000000000..3632fefcb52 --- /dev/null +++ b/site/src/examples/semantic-icon-provider/Default.tsx @@ -0,0 +1,33 @@ +import { + Dropdown, + FlexLayout, + Option, + SemanticIconProvider, +} from "@salt-ds/core"; +import type { ReactElement } from "react"; + +const usStates = [ + "Alabama", + "Alaska", + "Arizona", + "Arkansas", + "California", + "Connecticut", + "Delaware", + "Florida", + "Georgia", +]; + +export const Default = (): ReactElement => { + return ( + + + + {usStates.map((state) => ( + + + + ); +}; diff --git a/site/src/examples/semantic-icon-provider/WithIconMap.tsx b/site/src/examples/semantic-icon-provider/WithIconMap.tsx new file mode 100644 index 00000000000..84416171914 --- /dev/null +++ b/site/src/examples/semantic-icon-provider/WithIconMap.tsx @@ -0,0 +1,39 @@ +import { + Dropdown, + FlexLayout, + Option, + SemanticIconProvider, +} from "@salt-ds/core"; +import { DoubleChevronDownIcon, DoubleChevronUpIcon } from "@salt-ds/icons"; +import type { ReactElement } from "react"; + +const usStates = [ + "Alabama", + "Alaska", + "Arizona", + "Arkansas", + "California", + "Connecticut", + "Delaware", + "Florida", + "Georgia", +]; + +export const WithIconMap = (): ReactElement => { + return ( + + + + {usStates.map((state) => ( + + + + ); +}; diff --git a/site/src/examples/semantic-icon-provider/index.ts b/site/src/examples/semantic-icon-provider/index.ts new file mode 100644 index 00000000000..2082e4a8f12 --- /dev/null +++ b/site/src/examples/semantic-icon-provider/index.ts @@ -0,0 +1,2 @@ +export * from "./Default"; +export * from "./WithIconMap"; diff --git a/site/src/layouts/DetailComponent/TitleWithDrawer.tsx b/site/src/layouts/DetailComponent/TitleWithDrawer.tsx index 27ae4e413f4..79069f602a2 100644 --- a/site/src/layouts/DetailComponent/TitleWithDrawer.tsx +++ b/site/src/layouts/DetailComponent/TitleWithDrawer.tsx @@ -1,5 +1,4 @@ -import { Button } from "@salt-ds/core"; -import { CloseIcon, OverflowMenuIcon } from "@salt-ds/icons"; +import { Button, useIcon } from "@salt-ds/core"; import React, { type FC, type Dispatch, type SetStateAction } from "react"; import layoutStyles from "../index.module.css"; @@ -16,6 +15,7 @@ const TitleWithDrawer: FC = ({ openDrawer, setOpenDrawer, }) => { + const { OverflowIcon, CloseIcon } = useIcon(); const showDrawer = () => { setOpenDrawer(true); }; @@ -41,7 +41,7 @@ const TitleWithDrawer: FC = ({ variant="secondary" onClick={showDrawer} > - + )} From 07db4f2ca99e9ef113ee0f56789c354a3b09f037 Mon Sep 17 00:00:00 2001 From: Josh Wooding <12938082+joshwooding@users.noreply.github.com> Date: Tue, 8 Oct 2024 16:40:42 +0100 Subject: [PATCH 2/2] Fixes --- .changeset/grumpy-lies-bathe.md | 2 +- .../src/__tests__/__e2e__/link/Link.cy.tsx | 13 ------------ .../SemanticIconProvider.cy.tsx | 6 +++--- packages/core/src/avatar/Avatar.tsx | 9 +++++--- .../core/stories/avatar/avatar.qa.stories.tsx | 3 +++ .../core/stories/link/link.qa.stories.tsx | 21 +++++++++++++++++++ .../semantic-icon-provider.mdx | 3 ++- .../internal/BreadcrumbsCollapsed.tsx | 2 +- .../lab/src/calendar/CalendarNavigation.tsx | 8 ++++--- .../src/date-picker/DatePickerRangeInput.tsx | 11 ++++++++-- .../src/date-picker/DatePickerSingleInput.tsx | 11 ++++++++-- packages/lab/src/dropdown/DropdownButton.tsx | 2 +- .../query-input/internal/ValueSelector.tsx | 2 +- packages/lab/src/tabs-next/OverflowMenu.tsx | 2 +- .../semantic-icon-provider/examples.mdx | 2 +- 15 files changed, 64 insertions(+), 33 deletions(-) diff --git a/.changeset/grumpy-lies-bathe.md b/.changeset/grumpy-lies-bathe.md index 20336015a84..2a0e6c4486c 100644 --- a/.changeset/grumpy-lies-bathe.md +++ b/.changeset/grumpy-lies-bathe.md @@ -6,6 +6,6 @@ Added SemanticIconProvider to provide a mechanism to swap out built-in component ```tsx - + ``` diff --git a/packages/core/src/__tests__/__e2e__/link/Link.cy.tsx b/packages/core/src/__tests__/__e2e__/link/Link.cy.tsx index be6b0efd3b9..d200510d44d 100644 --- a/packages/core/src/__tests__/__e2e__/link/Link.cy.tsx +++ b/packages/core/src/__tests__/__e2e__/link/Link.cy.tsx @@ -42,17 +42,4 @@ describe("GIVEN a link", () => { cy.findByTestId(/TearOutIcon/i).should("not.exist"); }); - - it('WHEN target is "_blank" AND IconComponent is provided, THEN should render the provided IconComponent', () => { - const CustomIcon = () =>
Custom Icon
; - - cy.mount( - - Action - , - ); - - cy.findByTestId(/CustomIcon/i).should("exist"); - cy.findByTestId(/TearOutIcon/i).should("not.exist"); - }); }); diff --git a/packages/core/src/__tests__/__e2e__/semantic-icon-provider/SemanticIconProvider.cy.tsx b/packages/core/src/__tests__/__e2e__/semantic-icon-provider/SemanticIconProvider.cy.tsx index 99ed2125986..ef53b554f15 100644 --- a/packages/core/src/__tests__/__e2e__/semantic-icon-provider/SemanticIconProvider.cy.tsx +++ b/packages/core/src/__tests__/__e2e__/semantic-icon-provider/SemanticIconProvider.cy.tsx @@ -22,7 +22,7 @@ const TestComponent = () => { ); }; -describe("SemanticIconProvider Tests", () => { +describe("SemanticIconProvider", () => { it("should use default icons when provider is not wrapped", () => { cy.mount(); cy.get('[data-testid="ChevronDownIcon"]').should("exist"); @@ -31,7 +31,7 @@ describe("SemanticIconProvider Tests", () => { cy.get('[data-testid="UserSolidIcon"]').should("exist"); }); - it("should override only specific icons when provider is partially wrapped", () => { + it("should support overriding only specific icons", () => { cy.mount( { cy.get('[data-testid="UserSolidIcon"]').should("exist"); }); - it("should override all icons when provider is fully wrapped", () => { + it("should support overriding all icons", () => { cy.mount( (function Avatar( const targetWindow = useWindow(); const { UserIcon } = useIcon(); - const fallbackIcon = fallbackIconProp || ( - - ); + const fallbackIcon = + fallbackIconProp === undefined ? ( + + ) : ( + fallbackIconProp + ); useComponentCssInjection({ testId: "salt-avatar", diff --git a/packages/core/stories/avatar/avatar.qa.stories.tsx b/packages/core/stories/avatar/avatar.qa.stories.tsx index 8a6a6c5b56e..47926dfb401 100644 --- a/packages/core/stories/avatar/avatar.qa.stories.tsx +++ b/packages/core/stories/avatar/avatar.qa.stories.tsx @@ -1,4 +1,5 @@ import { Avatar } from "@salt-ds/core"; +import { SaltShakerIcon } from "@salt-ds/icons"; import type { Meta, StoryFn } from "@storybook/react"; import { QAContainer, @@ -18,6 +19,7 @@ export const AllVariantsGrid: StoryFn = (props) => ( + } /> ); @@ -45,6 +47,7 @@ export const NoStyleInjectionGrid: StoryFn = ( + } /> ); diff --git a/packages/core/stories/link/link.qa.stories.tsx b/packages/core/stories/link/link.qa.stories.tsx index a88a2a43cc7..dec369dd273 100644 --- a/packages/core/stories/link/link.qa.stories.tsx +++ b/packages/core/stories/link/link.qa.stories.tsx @@ -1,4 +1,5 @@ import { Link } from "@salt-ds/core"; +import { UserIcon } from "@salt-ds/icons"; import type { Meta, StoryFn } from "@storybook/react"; import { QAContainer, @@ -40,6 +41,16 @@ export const AllVariantsGrid: StoryFn = (props) => ( > Forced visited + + Custom icon + + + No icon + ); @@ -84,6 +95,16 @@ export const NoStyleInjectionGrid: StoryFn = ( Strong and small truncation example + + Custom icon + + + No icon + ); diff --git a/packages/core/stories/semantic-icon-provider/semantic-icon-provider.mdx b/packages/core/stories/semantic-icon-provider/semantic-icon-provider.mdx index 0e0793b7ed0..39bbc84c3c0 100644 --- a/packages/core/stories/semantic-icon-provider/semantic-icon-provider.mdx +++ b/packages/core/stories/semantic-icon-provider/semantic-icon-provider.mdx @@ -9,7 +9,8 @@ The SemanticIconProvider introduces a centralized approach to managing and custo It extends the existing icon management framework by offering a context-based solution for overriding and customizing icons as needed. -#Usage +# Usage + To use the SemanticIconProvider, replace any existing icon management setup with this provider and provide any custom icon mappings as required. ```js static diff --git a/packages/lab/src/breadcrumbs/internal/BreadcrumbsCollapsed.tsx b/packages/lab/src/breadcrumbs/internal/BreadcrumbsCollapsed.tsx index a90bcc5134e..7d3b7c45ce8 100644 --- a/packages/lab/src/breadcrumbs/internal/BreadcrumbsCollapsed.tsx +++ b/packages/lab/src/breadcrumbs/internal/BreadcrumbsCollapsed.tsx @@ -34,7 +34,7 @@ export const BreadcrumbsCollapsed = ({ return ""; }); - const { OverflowIcon } = useIcon() || {}; + const { OverflowIcon } = useIcon(); const key = keys ? keys.join("") : ""; const { ref, shouldFocusOnMount } = useFocusMenuRemount(key); diff --git a/packages/lab/src/calendar/CalendarNavigation.tsx b/packages/lab/src/calendar/CalendarNavigation.tsx index c697f7fa8da..df65e2a947b 100644 --- a/packages/lab/src/calendar/CalendarNavigation.tsx +++ b/packages/lab/src/calendar/CalendarNavigation.tsx @@ -8,9 +8,9 @@ import { Tooltip, type TooltipProps, makePrefixer, + useIcon, useListControlContext, } from "@salt-ds/core"; -import { ChevronLeftIcon, ChevronRightIcon } from "@salt-ds/icons"; import { clsx } from "clsx"; import { type ComponentPropsWithRef, @@ -283,6 +283,8 @@ export const CalendarNavigation = forwardRef< window: targetWindow, }); + const { NextIcon, PreviousIcon } = useIcon(); + const { moveToPreviousMonth, moveToNextMonth, @@ -376,7 +378,7 @@ export const CalendarNavigation = forwardRef< onClick={handleNavigatePrevious} focusableWhenDisabled={true} > - +
@@ -428,7 +430,7 @@ export const CalendarNavigation = forwardRef< onClick={handleNavigateNext} focusableWhenDisabled={true} > - +
diff --git a/packages/lab/src/date-picker/DatePickerRangeInput.tsx b/packages/lab/src/date-picker/DatePickerRangeInput.tsx index 8162dfe143a..f0c5c8995b4 100644 --- a/packages/lab/src/date-picker/DatePickerRangeInput.tsx +++ b/packages/lab/src/date-picker/DatePickerRangeInput.tsx @@ -1,5 +1,10 @@ -import { Button, makePrefixer, useControlled, useForkRef } from "@salt-ds/core"; -import { CalendarIcon } from "@salt-ds/icons"; +import { + Button, + makePrefixer, + useControlled, + useForkRef, + useIcon, +} from "@salt-ds/core"; import { clsx } from "clsx"; import { type KeyboardEvent, @@ -43,6 +48,8 @@ export const DatePickerRangeInput = forwardRef< ...rest } = props; + const { CalendarIcon } = useIcon(); + const { state: { selectedDate, disabled, readOnly, cancelled, locale, timeZone }, helpers: { setSelectedDate }, diff --git a/packages/lab/src/date-picker/DatePickerSingleInput.tsx b/packages/lab/src/date-picker/DatePickerSingleInput.tsx index 6f7b2b4990f..c8e8881c3c1 100644 --- a/packages/lab/src/date-picker/DatePickerSingleInput.tsx +++ b/packages/lab/src/date-picker/DatePickerSingleInput.tsx @@ -1,5 +1,10 @@ -import { Button, makePrefixer, useControlled, useForkRef } from "@salt-ds/core"; -import { CalendarIcon } from "@salt-ds/icons"; +import { + Button, + makePrefixer, + useControlled, + useForkRef, + useIcon, +} from "@salt-ds/core"; import { clsx } from "clsx"; import { type KeyboardEvent, @@ -39,6 +44,8 @@ export const DatePickerSingleInput = forwardRef< ...rest } = props; + const { CalendarIcon } = useIcon(); + const { state: { selectedDate, disabled, readOnly, cancelled, locale, timeZone }, helpers: { setSelectedDate }, diff --git a/packages/lab/src/dropdown/DropdownButton.tsx b/packages/lab/src/dropdown/DropdownButton.tsx index dab5e43f0a5..6b59becd49d 100644 --- a/packages/lab/src/dropdown/DropdownButton.tsx +++ b/packages/lab/src/dropdown/DropdownButton.tsx @@ -87,7 +87,7 @@ export const DropdownButton = forwardRef(function DropdownButton( window: targetWindow, }); const { ExpandIcon } = useIcon(); - const Icon = IconComponent ?? ExpandIcon; + const Icon = IconComponent === undefined ? ExpandIcon : IconComponent; const { inFormField } = useFormFieldLegacyProps(); // FIXME: use polymorphic button // We don't want the 'button' tag to be shown in the DOM to trigger some accessibility testing diff --git a/packages/lab/src/query-input/internal/ValueSelector.tsx b/packages/lab/src/query-input/internal/ValueSelector.tsx index 8bc489e0b99..808d463e3b1 100644 --- a/packages/lab/src/query-input/internal/ValueSelector.tsx +++ b/packages/lab/src/query-input/internal/ValueSelector.tsx @@ -60,7 +60,7 @@ export function ValueSelector(props: ValueSelectorProps) { setHighlightedCategoryIndex, setHighlightedIndex, } = props; - console.log("Testtttttt"); + const selectedCategoryValues = useMemo(() => { if (!selectedCategory) { return []; diff --git a/packages/lab/src/tabs-next/OverflowMenu.tsx b/packages/lab/src/tabs-next/OverflowMenu.tsx index 950b29dd006..b59387cfb8e 100644 --- a/packages/lab/src/tabs-next/OverflowMenu.tsx +++ b/packages/lab/src/tabs-next/OverflowMenu.tsx @@ -18,7 +18,7 @@ export const OverflowMenu = forwardRef( const { tabs, ...rest } = props; const { ref, overflowCount, isOverflowing } = useOverflowMenu(); - const { OverflowIcon } = useIcon() || {}; + const { OverflowIcon } = useIcon(); const handleRef = useForkRef(ref, forwardedRef); const itemVisibility = useOverflowContext( (context) => context.itemVisibility, diff --git a/site/docs/components/semantic-icon-provider/examples.mdx b/site/docs/components/semantic-icon-provider/examples.mdx index f738163cfc0..09efa16f654 100644 --- a/site/docs/components/semantic-icon-provider/examples.mdx +++ b/site/docs/components/semantic-icon-provider/examples.mdx @@ -20,7 +20,7 @@ Use the `SemanticIconProvider` to replace any existing icon management setup wit -## iconMap +## Icon map The `SemanticIconProvider` allows you to specify an `iconMap` to manage which icon components are used for various icon names. This provides flexibility to customize the icon set used in your application.