diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a2c0383 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.cache +coverage/ +dist/ +node_modules/ +public/ \ No newline at end of file diff --git a/LICENSE b/LICENSE index f19fc72..f4470fa 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 creativeLabs Łukasz Holeczek +Copyright (c) 2024 creativeLabs Łukasz Holeczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..5f5e9ec --- /dev/null +++ b/jest.config.js @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2013-present, creativeLabs Lukasz Holeczek. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict' + +module.exports = { + moduleNameMapper: { + '\\.(css|scss)$': '/test/styleMock.js', + }, + preset: 'ts-jest', + testEnvironment: 'jsdom', + testPathIgnorePatterns: ['dist/'], +} diff --git a/package.json b/package.json index d8b75a1..2c5a151 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@coreui/icons-react", - "version": "2.1.0", + "version": "2.3.0", "description": "Official React component for CoreUI Icons", "keywords": [ "coreui", @@ -13,48 +13,47 @@ "component", "react" ], - "homepage": "https://icons.coreui.io", + "homepage": "https://coreui.io/react/docs/components/icon/", "bugs": { - "url": "https://github.com/coreui/coreui-icons/issues" + "url": "https://github.com/coreui/coreui-icons-react/issues" }, "repository": { "type": "git", - "url": "https://github.com/coreui/coreui-icons.git" + "url": "https://github.com/coreui/coreui-icons-react.git" }, "license": "MIT", "author": "The CoreUI Team (https://github.com/orgs/coreui/people)", "main": "dist/index.js", - "module": "dist/index.es.js", - "jsnext:main": "dist/index.es.js", + "module": "dist/index.esm.js", + "jsnext:main": "dist/index.esm.js", "types": "dist/index.d.ts", "files": [ "dist/", "src/" ], "scripts": { - "build": "rollup -c --bundleConfigAsCjs", + "build": "rollup --config", "test": "jest --coverage", "test:update": "jest --coverage --updateSnapshot" }, "devDependencies": { - "@rollup/plugin-commonjs": "^24.0.1", - "@rollup/plugin-node-resolve": "^15.0.1", - "@rollup/plugin-typescript": "^11.0.0", - "@testing-library/jest-dom": "^5.16.5", - "@testing-library/react": "^13.4.0", - "@types/react": "^18.0.27", - "@types/react-dom": "^18.0.6", - "classnames": "^2.3.2", - "jest": "^29.4.1", - "jest-canvas-mock": "^2.4.0", - "jest-environment-jsdom": "^29.4.1", + "@rollup/plugin-commonjs": "^26.0.3", + "@rollup/plugin-node-resolve": "^15.3.1", + "@rollup/plugin-typescript": "^11.1.6", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.1.0", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "classnames": "^2.5.1", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", "prop-types": "^15.8.1", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "rollup": "^3.14.0", - "rollup-plugin-import-css": "^3.0.3", - "ts-jest": "^29.0.5", - "typescript": "^4.9.5" + "react": "^18.3.1", + "react-dom": "^18.3.1", + "rollup": "^4.29.1", + "rollup-plugin-import-css": "^3.5.8", + "ts-jest": "^29.2.5", + "typescript": "^5.7.2" }, "peerDependencies": { "react": ">=17", diff --git a/rollup.config.js b/rollup.config.mjs similarity index 100% rename from rollup.config.js rename to rollup.config.mjs diff --git a/src/CIcon.css b/src/CIcon.css index 1ce713d..037a3bf 100644 --- a/src/CIcon.css +++ b/src/CIcon.css @@ -75,4 +75,17 @@ width: 0.875rem; height: 0.875rem; font-size: 0.875rem; +} + +.visually-hidden, +.visually-hidden-focusable:not(:focus):not(:focus-within) { + position: absolute!important; + width: 1px!important; + height: 1px!important; + padding: 0!important; + margin: -1px!important; + overflow: hidden!important; + clip: rect(0,0,0,0)!important; + white-space: nowrap!important; + border: 0!important; } \ No newline at end of file diff --git a/src/CIcon.tsx b/src/CIcon.tsx index 5c6cb17..166f941 100644 --- a/src/CIcon.tsx +++ b/src/CIcon.tsx @@ -3,7 +3,7 @@ import React, { HTMLAttributes, forwardRef, useState, useMemo } from 'react' import classNames from 'classnames' import './CIcon.css' -export interface CIconProps extends HTMLAttributes { +export interface CIconProps extends Omit, 'content'> { /** * A string of all className you want applied to the component. */ @@ -17,7 +17,7 @@ export interface CIconProps extends HTMLAttributes { /** * Use for replacing default CIcon component classes. Prop is overriding the 'size' prop. */ - customClassName?: string | object | string[] // eslint-disable-line @typescript-eslint/ban-types + customClassName?: string | string[] /** * Name of the icon placed in React object or SVG content. */ @@ -53,6 +53,10 @@ export interface CIconProps extends HTMLAttributes { * If defined component will be rendered using 'use' tag. */ use?: string + /** + * The viewBox attribute defines the position and dimension of an SVG viewport. + */ + viewBox?: string /** * Title tag content. */ @@ -98,20 +102,18 @@ export const CIcon = forwardRef( useMemo(() => setChange(change + 1), [_icon, JSON.stringify(_icon)]) - const iconName = useMemo( - () => - _icon && typeof _icon === 'string' && _icon.includes('-') ? toCamelCase(_icon) : _icon, - [change], - ) - const titleCode = title ? `${title}` : '' const code = useMemo(() => { + const iconName = + _icon && typeof _icon === 'string' && _icon.includes('-') ? toCamelCase(_icon) : _icon + if (Array.isArray(_icon)) { return _icon } - if (typeof _icon === 'string' && React['icons']) { - return React['icons'][iconName] + + if (typeof _icon === 'string' && (React as { [key: string]: any })['icons']) { + return (React as { [key: string]: any })[iconName as string] } }, [change]) @@ -127,8 +129,6 @@ export const CIcon = forwardRef( return rest['viewBox'] || `0 0 ${scale}` })() - // render - const _className = customClassName ? classNames(customClassName) : classNames( @@ -140,30 +140,37 @@ export const CIcon = forwardRef( className, ) - return use ? ( - - - - ) : ( - + return ( + <> + {use ? ( + + ) : ( + + )} + {title && {title}} + ) }, ) @@ -190,8 +197,9 @@ CIcon.propTypes = { '8xl', '9xl', ]), - title: PropTypes.any, - use: PropTypes.any, + title: PropTypes.string, + use: PropTypes.string, + viewBox: PropTypes.string, width: PropTypes.number, } diff --git a/src/CIconSvg.tsx b/src/CIconSvg.tsx new file mode 100644 index 0000000..b404ca7 --- /dev/null +++ b/src/CIconSvg.tsx @@ -0,0 +1,104 @@ +import React, { Children, HTMLAttributes, forwardRef } from 'react' +import PropTypes from 'prop-types' +import classNames from 'classnames' +import './CIcon.css' + +export interface CIconSvgProps extends Omit, 'content'> { + /** + * A string of all className you want applied to the component. + */ + className?: string + /** + * Use for replacing default CIcon component classes. Prop is overriding the 'size' prop. + */ + customClassName?: string | string[] + /** + * The height attribute defines the vertical length of an icon. + */ + height?: number + /** + * Size of the icon. Available sizes: 'sm', 'lg', 'xl', 'xxl', '3xl...9xl', 'custom', 'custom-size'. + */ + size?: + | 'custom' + | 'custom-size' + | 'sm' + | 'lg' + | 'xl' + | 'xxl' + | '3xl' + | '4xl' + | '5xl' + | '6xl' + | '7xl' + | '8xl' + | '9xl' + /** + * Title tag content. + */ + title?: string + /** + * The width attribute defines the horizontal length of an icon. + */ + width?: number +} + +export const CIconSvg = forwardRef( + ({ children, className, customClassName, height, size, title, width, ...rest }, ref) => { + const _className = customClassName + ? classNames(customClassName) + : classNames( + 'icon', + { + [`icon-${size}`]: size, + [`icon-custom-size`]: height || width, + }, + className, + ) + + return ( + <> + {Children.map(children, (child) => { + if (React.isValidElement(child)) { + return React.cloneElement(child as React.ReactElement, { + 'aria-hidden': true, + className: _className, + focusable: 'false', + ref: ref, + role: 'img', + ...rest, + }) + } + + return + })} + {title && {title}} + + ) + }, +) + +CIconSvg.propTypes = { + className: PropTypes.string, + customClassName: PropTypes.string, + height: PropTypes.number, + size: PropTypes.oneOf([ + 'custom', + 'custom-size', + 'sm', + 'lg', + 'xl', + 'xxl', + '3xl', + '4xl', + '5xl', + '6xl', + '7xl', + '8xl', + '9xl', + ]), + title: PropTypes.string, + width: PropTypes.number, +} + +CIconSvg.displayName = 'CIconSvg' diff --git a/src/__tests__/CIcon.spec.tsx b/src/__tests__/CIcon.spec.tsx index ee714bb..566d127 100644 --- a/src/__tests__/CIcon.spec.tsx +++ b/src/__tests__/CIcon.spec.tsx @@ -1,22 +1,14 @@ import React from 'react' import { render } from '@testing-library/react' -import '@testing-library/jest-dom/extend-expect' +import '@testing-library/jest-dom' import CIcon from './../' -// import { cifAu } from './../../../icons/js/flag/cif-au' - describe('CIcon', () => { it('renders svg with class="icon"', () => { const { container } = render() expect(container.firstChild).toHaveClass('icon') }) - // it('renders svg with icon', () => { - // const { container } = render() - // expect(container.firstChild).toContain(cifAu[1]) - // // expect(render()).toContain(cifAu) - // }) - it('renders svg with size', () => { const { container } = render() expect(container.firstChild).toHaveClass('icon-xl') @@ -32,8 +24,8 @@ describe('CIcon', () => { expect(container.firstChild).toHaveClass('icon-test') }) - // it('renders with ', () => { - // const { container } = render() - // expect(container.firstChild?.firstChild).toContain('') - // }) + it('renders svg with custom className', () => { + const { container } = render() + expect(container.firstChild).toHaveClass('icon-custom-test') + }) }) diff --git a/src/index.ts b/src/index.ts index 5ebb4bc..13835ba 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,5 @@ import { CIcon } from './CIcon' +import { CIconSvg } from './CIconSvg' +export { CIcon, CIconSvg } export default CIcon diff --git a/tsconfig.json b/tsconfig.json index ed928e2..7f4d467 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,7 +15,6 @@ "noImplicitThis": true, "noImplicitAny": true, "strictNullChecks": true, - "suppressImplicitAnyIndexErrors": true, "noUnusedLocals": true, "noUnusedParameters": true, "esModuleInterop": true