diff --git a/src/icons/AccessTime/AccessTimeIcon.tsx b/src/icons/AccessTime/AccessTimeIcon.tsx index 82ce8860f..bbceb3a7d 100644 --- a/src/icons/AccessTime/AccessTimeIcon.tsx +++ b/src/icons/AccessTime/AccessTimeIcon.tsx @@ -1,26 +1,8 @@ -import { DEFAULT_FILL_NONE, DEFAULT_HEIGHT, DEFAULT_WIDTH } from '../../constants/constants'; -import { IconProps } from '../types'; +import { createIcon } from '../createIcon'; -export const AccessTimeIcon = ({ - width = DEFAULT_WIDTH, - height = DEFAULT_HEIGHT, - fill = DEFAULT_FILL_NONE, - ...props -}: IconProps): JSX.Element => { - return ( - - - - ); -}; +export const AccessTimeIcon = createIcon({ + displayName: 'AccessTimeIcon', + d: 'M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2M12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8m.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z' +}); export default AccessTimeIcon; diff --git a/src/icons/Add/AddIcon.tsx b/src/icons/Add/AddIcon.tsx index 563551257..de85f276b 100644 --- a/src/icons/Add/AddIcon.tsx +++ b/src/icons/Add/AddIcon.tsx @@ -1,24 +1,11 @@ -import { DEFAULT_FILL_NONE, DEFAULT_HEIGHT, DEFAULT_WIDTH } from '../../constants/constants'; -import { IconProps } from '../types'; +import { createIcon } from '../createIcon'; -export const AddIcon = ({ - width = DEFAULT_WIDTH, - height = DEFAULT_HEIGHT, - fill = DEFAULT_FILL_NONE, - ...props -}: IconProps): JSX.Element => { - return ( - - - - ); -}; +export const AddIcon = createIcon({ + displayName: 'AddIcon', + d: 'M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z', + defaultProps: { + 'data-testid': 'add-icon-svg' + } +}); export default AddIcon; diff --git a/src/icons/createIcon/createIcon.tsx b/src/icons/createIcon/createIcon.tsx new file mode 100644 index 000000000..65cb7e4a6 --- /dev/null +++ b/src/icons/createIcon/createIcon.tsx @@ -0,0 +1,95 @@ +import React from 'react'; +import { DEFAULT_FILL_NONE, DEFAULT_HEIGHT, DEFAULT_WIDTH } from '../../constants/constants'; +import { IconProps } from '../types'; + +/** + * Configuration options for creating an SVG icon component with `createIcon`. + */ +export interface CreateIconOptions { + /** + * The icon SVG viewBox. + * @default '0 0 24 24' + */ + viewBox?: string; + + /** + * The SVG path or elements to render inside the SVG. + */ + path?: React.ReactNode | ((props: IconProps) => React.ReactNode); + + /** + * Shorthand SVG path `d` attribute when providing a single path string. + */ + d?: string; + + /** + * The display name of the icon component (for React DevTools and testing). + */ + displayName?: string; + + /** + * Default props to override standard defaults (e.g. custom default fill or dimensions). + */ + defaultProps?: Partial & Record; +} + +/** + * Factory function to create reusable, consistent SVG icon components. + * Standardizes prop handling (width, height, fill, viewBox, title), default values, + * and ref forwarding across icons. + * + * @param options - Configuration options for the icon (viewBox, path, d, displayName, defaultProps). + * @returns A React forwardRef component rendering the SVG icon. + */ +export function createIcon(options: CreateIconOptions): React.ForwardRefExoticComponent< + IconProps & React.RefAttributes +> { + const { + viewBox = '0 0 24 24', + d: pathDefinition, + path: pathElement, + displayName, + defaultProps = {} + } = options; + + const Comp = React.forwardRef((props, ref) => { + const mergedProps = { ...defaultProps, ...props }; + const { + width = DEFAULT_WIDTH, + height = DEFAULT_HEIGHT, + fill = DEFAULT_FILL_NONE, + title, + ...rest + } = mergedProps; + + let content: React.ReactNode = null; + if (typeof pathElement === 'function') { + content = pathElement(mergedProps); + } else if (pathElement) { + content = pathElement; + } else if (pathDefinition) { + content = ; + } + + return ( + + {title && {title}} + {content} + + ); + }); + + Comp.displayName = displayName || 'Icon'; + + return Comp; +} + +export default createIcon; diff --git a/src/icons/createIcon/index.ts b/src/icons/createIcon/index.ts new file mode 100644 index 000000000..cdb278409 --- /dev/null +++ b/src/icons/createIcon/index.ts @@ -0,0 +1 @@ +export { createIcon, default, type CreateIconOptions } from './createIcon'; diff --git a/src/icons/index.ts b/src/icons/index.ts index 8299cb93e..ee59fd13c 100644 --- a/src/icons/index.ts +++ b/src/icons/index.ts @@ -51,6 +51,7 @@ export * from './ContentClassIcons'; export * from './ContentFilter'; export * from './Copy'; export * from './CreateNew'; +export * from './createIcon'; export * from './Credential'; export * from './CrossCircle'; export * from './Dashboard'; diff --git a/src/index.tsx b/src/index.tsx index 33a25a51f..22b5132df 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -164,3 +164,9 @@ export { type Team as TeamPickerRecord, type TeamSearchFieldProps } from './custom/DashboardWidgets/GettingStartedWidget/TeamSearchField'; + +export { + createIcon, + type CreateIconOptions +} from './icons/createIcon'; +