|
1 | | -import React, { CSSProperties, RefAttributes, SVGAttributes } from 'react' |
| 1 | +import React from 'react' |
2 | 2 |
|
3 | 3 | import { |
4 | 4 | icon as faIcon, |
5 | 5 | parse as faParse, |
6 | 6 | } from '@fortawesome/fontawesome-svg-core' |
7 | | -import type { |
8 | | - FaSymbol, |
9 | | - FlipProp, |
10 | | - IconProp, |
11 | | - PullProp, |
12 | | - RotateProp, |
13 | | - SizeProp, |
14 | | - Transform, |
15 | | -} from '@fortawesome/fontawesome-svg-core' |
16 | 7 |
|
17 | 8 | import { convert } from '../converter' |
| 9 | +import { useAccessibilityId } from '../hooks/useAccessibilityId' |
18 | 10 | import { Logger } from '../logger' |
| 11 | +import { FontAwesomeIconProps } from '../types/icon-props' |
19 | 12 | import { getClassListFromProps } from '../utils/get-class-list-from-props' |
20 | 13 | import { normalizeIconArgs } from '../utils/normalize-icon-args' |
21 | 14 | import { typedObjectKeys } from '../utils/typed-object-keys' |
@@ -51,168 +44,79 @@ const DEFAULT_PROPS = { |
51 | 44 | transform: undefined, |
52 | 45 | swapOpacity: false, |
53 | 46 | widthAuto: false, |
54 | | -} |
| 47 | +} as const satisfies Partial<FontAwesomeIconProps> |
55 | 48 |
|
56 | 49 | const DEFAULT_PROP_KEYS = new Set(Object.keys(DEFAULT_PROPS)) |
57 | 50 |
|
58 | | -export interface FontAwesomeIconProps |
59 | | - extends Omit<SVGAttributes<SVGSVGElement>, 'children' | 'mask' | 'transform'>, |
60 | | - RefAttributes<SVGSVGElement> { |
61 | | - /** |
62 | | - * The icon to render. |
63 | | - * @see {@link https://docs.fontawesome.com/web/use-with/react/add-icons} |
64 | | - */ |
65 | | - icon: IconProp |
66 | | - /** |
67 | | - * Grab the Mask utility when you want to layer two icons but have the inner icon cut out from the icon below so the parent element’s background shows through. |
68 | | - * @see {@link https://docs.fontawesome.com/web/use-with/react/style#mask} |
69 | | - */ |
70 | | - mask?: IconProp | undefined |
71 | | - maskId?: string | undefined |
72 | | - className?: string | undefined |
73 | | - color?: string | undefined |
74 | | - spin?: boolean | undefined |
75 | | - spinPulse?: boolean | undefined |
76 | | - spinReverse?: boolean | undefined |
77 | | - pulse?: boolean | undefined |
78 | | - beat?: boolean | undefined |
79 | | - fade?: boolean | undefined |
80 | | - beatFade?: boolean | undefined |
81 | | - bounce?: boolean | undefined |
82 | | - shake?: boolean | undefined |
83 | | - border?: boolean | undefined |
84 | | - /** |
85 | | - * @deprecated |
86 | | - * @since 7.0.0 |
87 | | - * |
88 | | - * Starting in FontAwesome 7.0.0, all icons are fixed width by default. |
89 | | - * This property will be removed in a future version. |
90 | | - * |
91 | | - * If you want to remove the fixed width to replicate the behavior of |
92 | | - * previous versions, you can set the new `widthAuto` property to `true`. |
93 | | - * |
94 | | - * @see {@link FontAwesomeIconProps.widthAuto} |
95 | | - */ |
96 | | - fixedWidth?: boolean | undefined |
97 | | - inverse?: boolean | undefined |
98 | | - listItem?: boolean | undefined |
99 | | - flip?: FlipProp | boolean | undefined |
100 | | - size?: SizeProp | undefined |
101 | | - pull?: PullProp | undefined |
102 | | - /** |
103 | | - * The rotation property is used to rotate the icon by 90, 180, or 270 degrees. |
104 | | - * |
105 | | - * @see {@link https://docs.fontawesome.com/web/use-with/react/style#rotation} |
106 | | - */ |
107 | | - rotation?: RotateProp | undefined |
108 | | - /** |
109 | | - * Custom rotation is used to rotate the icon by a specific number of degrees, |
110 | | - * rather than the standard 90, 180, or 270 degrees available in the `rotation` property. |
111 | | - * |
112 | | - * To use this feature, set `rotateBy` to `true` and provide a CSS variable `--fa-rotate-angle` |
113 | | - * with the desired rotation angle in degrees. |
114 | | - * |
115 | | - * @example |
116 | | - * ```tsx |
117 | | - * <FontAwesomeIcon |
118 | | - * icon="fa-solid fa-coffee" |
119 | | - * rotateBy |
120 | | - * style={{ '--fa-rotate-angle': '329deg' }} |
121 | | - * /> |
122 | | - * ``` |
123 | | - * |
124 | | - * @see {@link https://docs.fontawesome.com/web/use-with/react/style#custom-rotation} |
125 | | - * @since 7.0.0 |
126 | | - */ |
127 | | - rotateBy?: boolean | undefined |
128 | | - transform?: string | Transform | undefined |
129 | | - symbol?: FaSymbol | undefined |
130 | | - style?: CSSProperties | undefined |
131 | | - tabIndex?: number | undefined |
132 | | - title?: string | undefined |
133 | | - titleId?: string | undefined |
134 | | - /** |
135 | | - * When using Duotone icons, this property will swap the opacity of the two colors. |
136 | | - * The first color will be rendered with the opacity of the second color, and vice versa |
137 | | - * |
138 | | - * @see {@link https://docs.fontawesome.com/web/use-with/react/style#duotone-icons} |
139 | | - */ |
140 | | - swapOpacity?: boolean | undefined |
141 | | - /** |
142 | | - * When set to `true`, the icon will automatically adjust its width to |
143 | | - * only the interior symbol and not the entire Icon Canvas. |
144 | | - * |
145 | | - * @see {@link https://docs.fontawesome.com/web/style/icon-canvas} |
146 | | - * @since 7.0.0 |
147 | | - */ |
148 | | - widthAuto?: boolean | undefined |
149 | | -} |
150 | | - |
151 | | -export const FontAwesomeIcon = React.forwardRef( |
152 | | - ( |
153 | | - props: FontAwesomeIconProps, |
154 | | - ref: React.Ref<SVGSVGElement>, |
155 | | - ): React.JSX.Element | null => { |
156 | | - const allProps: FontAwesomeIconProps = { ...DEFAULT_PROPS, ...props } |
157 | | - |
158 | | - const { |
159 | | - icon: iconArgs, |
160 | | - mask: maskArgs, |
161 | | - symbol, |
162 | | - title, |
163 | | - titleId, |
164 | | - maskId, |
165 | | - transform, |
166 | | - } = allProps |
167 | | - |
168 | | - const iconLookup = normalizeIconArgs(iconArgs) |
169 | | - |
170 | | - if (!iconLookup) { |
171 | | - logger.error('Icon lookup is undefined', iconArgs) |
172 | | - return null |
173 | | - } |
174 | | - |
175 | | - const classList = getClassListFromProps(allProps) |
176 | | - |
177 | | - const transformProps = |
178 | | - typeof transform === 'string' ? faParse.transform(transform) : transform |
179 | | - |
180 | | - const normalizedMaskArgs = normalizeIconArgs(maskArgs) |
181 | | - |
182 | | - const renderedIcon = faIcon(iconLookup, { |
183 | | - ...(classList.length > 0 && { classes: classList }), |
184 | | - ...(transformProps && { transform: transformProps }), |
185 | | - ...(normalizedMaskArgs && { mask: normalizedMaskArgs }), |
186 | | - symbol, |
187 | | - title, |
188 | | - titleId, |
189 | | - maskId, |
190 | | - }) |
191 | | - |
192 | | - if (!renderedIcon) { |
193 | | - logger.error('Could not find icon', iconLookup) |
194 | | - return null |
| 51 | +/** |
| 52 | + * FontAwesomeIcon component. |
| 53 | + */ |
| 54 | +export const FontAwesomeIcon = React.forwardRef< |
| 55 | + SVGSVGElement, |
| 56 | + FontAwesomeIconProps |
| 57 | +>((props, ref): React.JSX.Element | null => { |
| 58 | + const allProps: FontAwesomeIconProps = { ...DEFAULT_PROPS, ...props } |
| 59 | + |
| 60 | + const { |
| 61 | + icon: iconArgs, |
| 62 | + mask: maskArgs, |
| 63 | + symbol, |
| 64 | + title, |
| 65 | + titleId: titleIdFromProps, |
| 66 | + maskId: maskIdFromProps, |
| 67 | + transform, |
| 68 | + } = allProps |
| 69 | + |
| 70 | + const maskId = useAccessibilityId(maskIdFromProps, Boolean(maskArgs)) |
| 71 | + const titleId = useAccessibilityId(titleIdFromProps, Boolean(title)) |
| 72 | + |
| 73 | + const iconLookup = normalizeIconArgs(iconArgs) |
| 74 | + |
| 75 | + if (!iconLookup) { |
| 76 | + logger.error('Icon lookup is undefined', iconArgs) |
| 77 | + return null |
| 78 | + } |
| 79 | + |
| 80 | + const classList = getClassListFromProps(allProps) |
| 81 | + |
| 82 | + const transformProps = |
| 83 | + typeof transform === 'string' ? faParse.transform(transform) : transform |
| 84 | + |
| 85 | + const normalizedMaskArgs = normalizeIconArgs(maskArgs) |
| 86 | + |
| 87 | + const renderedIcon = faIcon(iconLookup, { |
| 88 | + ...(classList.length > 0 && { classes: classList }), |
| 89 | + ...(transformProps && { transform: transformProps }), |
| 90 | + ...(normalizedMaskArgs && { mask: normalizedMaskArgs }), |
| 91 | + symbol, |
| 92 | + title, |
| 93 | + titleId, |
| 94 | + maskId, |
| 95 | + }) |
| 96 | + |
| 97 | + if (!renderedIcon) { |
| 98 | + logger.error('Could not find icon', iconLookup) |
| 99 | + return null |
| 100 | + } |
| 101 | + |
| 102 | + const { abstract } = renderedIcon |
| 103 | + const extraProps: Partial<FontAwesomeIconProps> = { ref } |
| 104 | + |
| 105 | + for (const key of typedObjectKeys(allProps)) { |
| 106 | + // Skip default props |
| 107 | + if (DEFAULT_PROP_KEYS.has(key)) { |
| 108 | + continue |
195 | 109 | } |
196 | 110 |
|
197 | | - const { abstract } = renderedIcon |
198 | | - const extraProps: Partial<FontAwesomeIconProps> = { ref } |
199 | | - |
200 | | - for (const key of typedObjectKeys(allProps)) { |
201 | | - // Skip default props |
202 | | - if (DEFAULT_PROP_KEYS.has(key)) { |
203 | | - continue |
204 | | - } |
205 | | - |
206 | | - // Add all other props to the extraProps object |
207 | | - // @ts-expect-error since `key` can be any of the keys in FontAwesomeIconProps, |
208 | | - // TypeScript widens the type of the `obj[key]` lookups to a union of all possible values, |
209 | | - // which will not correctly overlap each other. |
210 | | - extraProps[key] = allProps[key] |
211 | | - } |
| 111 | + // Add all other props to the extraProps object |
| 112 | + // @ts-expect-error since `key` can be any of the keys in FontAwesomeIconProps, |
| 113 | + // TypeScript widens the type of the `obj[key]` lookups to a union of all possible values, |
| 114 | + // which will not correctly overlap each other. |
| 115 | + extraProps[key] = allProps[key] |
| 116 | + } |
212 | 117 |
|
213 | | - return convertCurry(abstract[0], extraProps) |
214 | | - }, |
215 | | -) |
| 118 | + return convertCurry(abstract[0], extraProps) |
| 119 | +}) |
216 | 120 |
|
217 | 121 | FontAwesomeIcon.displayName = 'FontAwesomeIcon' |
218 | 122 |
|
|
0 commit comments