Skip to content

Commit 463be70

Browse files
authored
Merge pull request #601 from FortAwesome/feat/layers-support
Add support for Layers with FontAwesomeLayers component
2 parents 09b5a26 + 982f649 commit 463be70

File tree

11 files changed

+477
-26
lines changed

11 files changed

+477
-26
lines changed

package.json

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
"require": "./dist/index.cjs",
1515
"default": "./dist/index.js"
1616
},
17-
"./components/rsc/CustomPrefixProvider": {
18-
"types": "./dist/components/rsc/CustomPrefixProvider.d.ts",
19-
"import": "./dist/components/rsc/CustomPrefixProvider.js",
20-
"require": "./dist/components/rsc/CustomPrefixProvider.cjs",
21-
"default": "./dist/components/rsc/CustomPrefixProvider.js"
17+
"./CustomPrefixProvider": {
18+
"types": "./dist/CustomPrefixProvider.d.ts",
19+
"import": "./dist/CustomPrefixProvider.js",
20+
"require": "./dist/CustomPrefixProvider.cjs",
21+
"default": "./dist/CustomPrefixProvider.js"
2222
},
2323
"./package.json": "./package.json"
2424
},
@@ -65,6 +65,7 @@
6565
"test": "jest",
6666
"test:watch": "jest --watch",
6767
"test:prepublish": "npm run install.6 && npm run test && npm run install.7 && npm run test",
68+
"validate": "npm run lint && npm run format:check && npm run validate-types && npm run test",
6869
"validate-types": "tsc --noEmit",
6970
"install.6": "npm --no-save install @fortawesome/[email protected] @fortawesome/[email protected]",
7071
"install.7": "npm --no-save install @fortawesome/[email protected] @fortawesome/[email protected]",

src/components/FontAwesomeIcon.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import React from 'react'
1+
import React, { RefAttributes, SVGAttributes } from 'react'
22

33
import {
44
icon as faIcon,
55
parse as faParse,
66
} from '@fortawesome/fontawesome-svg-core'
77

8-
import { convert } from '../converter'
8+
import { makeReactConverter } from '../converter'
99
import { useAccessibilityId } from '../hooks/useAccessibilityId'
1010
import { Logger } from '../logger'
1111
import { FontAwesomeIconProps } from '../types/icon-props'
@@ -100,7 +100,11 @@ export const FontAwesomeIcon = React.forwardRef<
100100
}
101101

102102
const { abstract } = renderedIcon
103-
const extraProps: Partial<FontAwesomeIconProps> = { ref }
103+
const extraProps: Omit<
104+
SVGAttributes<SVGSVGElement>,
105+
'children' | 'mask' | 'transform'
106+
> &
107+
RefAttributes<SVGSVGElement> = { ref }
104108

105109
for (const key of typedObjectKeys(allProps)) {
106110
// Skip default props
@@ -115,9 +119,7 @@ export const FontAwesomeIcon = React.forwardRef<
115119
extraProps[key] = allProps[key]
116120
}
117121

118-
return convertCurry(abstract[0], extraProps)
122+
return makeReactConverter(abstract[0], extraProps)
119123
})
120124

121125
FontAwesomeIcon.displayName = 'FontAwesomeIcon'
122-
123-
const convertCurry = convert.bind(null, React.createElement)
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
import React, { useMemo } from 'react'
2+
3+
import {
4+
counter as faCounter,
5+
text as faText,
6+
parse as faParse,
7+
Transform,
8+
SizeProp,
9+
} from '@fortawesome/fontawesome-svg-core'
10+
11+
import { makeReactConverter } from '../converter'
12+
import { CSSVariables } from '../types/css-variables'
13+
import { LAYER_CLASSES, STYLE_CLASSES } from '../utils/constants'
14+
import { withPrefix } from '../utils/get-class-list-from-props'
15+
16+
type Attributes = React.HTMLAttributes<HTMLSpanElement>
17+
18+
interface FontAwesomeLayersProps extends Attributes {
19+
children: React.ReactNode
20+
className?: string | undefined
21+
size?: SizeProp | undefined
22+
}
23+
24+
const DEFAULT_CLASSNAMES = `${LAYER_CLASSES.default} ${STYLE_CLASSES.fixedWidth}`
25+
26+
/**
27+
* React Component that allows you to stack multiple Font Awesome icons on top of each other,
28+
* or to layer with text or a counter.
29+
*
30+
* @see https://docs.fontawesome.com/web/style/layer
31+
*
32+
* @example
33+
* ```tsx
34+
* import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/react-fontawesome'
35+
* import { faBookmark, faCircle, faCheck, faHeart, faMoon, faPlay, faStar, faSun } from '@fortawesome/free-solid-svg-icons'
36+
*
37+
* // React versions of the examples from the FontAwesome Web Docs
38+
* export const Examples = () => (
39+
* <div className="fa-4x">
40+
* <FontAwesomeLayers>
41+
* <FontAwesomeIcon icon={faCircle} color="tomato" />
42+
* <FontAwesomeIcon icon={faCheck} inverse transform="shrink-6" />
43+
* </FontAwesomeLayers>
44+
* <FontAwesomeLayers>
45+
* <FontAwesomeIcon icon={faBookmark} />
46+
* <FontAwesomeIcon icon={faHeart} color="tomato" transform="shrink-10 up-2" />
47+
* </FontAwesomeLayers>
48+
* <FontAwesomeLayers>
49+
* <FontAwesomeIcon icon={faPlay} transform="rotate--90 grow-4" />
50+
* <FontAwesomeIcon icon={faSun} inverse transform="shrink-10 up-2" />
51+
* <FontAwesomeIcon icon={faMoon} inverse transform="shrink-11 down-4.2 left-4" />
52+
* <FontAwesomeIcon icon={faStar} inverse transform="shrink-11 down-4.2 right-4" />
53+
* </FontAwesomeLayers>
54+
* </div>
55+
* )
56+
* ```
57+
*
58+
* For examples using Text or Counter components:
59+
* @see {@link LayersText}
60+
* @see {@link LayersCounter}
61+
*/
62+
const FontAwesomeLayers = ({
63+
children,
64+
className,
65+
size,
66+
...attributes
67+
}: FontAwesomeLayersProps) => {
68+
const prefixedDefaultClasses = withPrefix(DEFAULT_CLASSNAMES)
69+
const classes = className
70+
? `${prefixedDefaultClasses} ${className}`
71+
: prefixedDefaultClasses
72+
73+
const element = (
74+
<span {...attributes} className={classes}>
75+
{children}
76+
</span>
77+
)
78+
79+
if (size) {
80+
return <div className={withPrefix(`fa-${size}`)}>{element}</div>
81+
}
82+
83+
return element
84+
}
85+
86+
/**
87+
* Text component to be used within a `FontAwesomeLayers` component.
88+
*
89+
* @see https://docs.fontawesome.com/web/style/layer
90+
*
91+
* @example
92+
* ```tsx
93+
* import { FontAwesomeLayers, LayersText } from '@fortawesome/react-fontawesome'
94+
* import { faCalendar, faCertificate } from '@fortawesome/free-solid-svg-icons'
95+
*
96+
* // React versions of the examples from the FontAwesome Web Docs
97+
* export const Examples = () => (
98+
* <div className="fa-4x">
99+
* <FontAwesomeLayers>
100+
* <FontAwesomeIcon icon={faCalendar} />
101+
* <LayersText
102+
* text="27"
103+
* inverse
104+
* style={{ fontWeight: '900' }}
105+
* transform="shrink-8 down-3"
106+
* />
107+
* </FontAwesomeLayers>
108+
* <FontAwesomeLayers>
109+
* <FontAwesomeIcon icon={faCertificate} />
110+
* <LayersText
111+
* text="NEW"
112+
* inverse
113+
* style={{ fontWeight: '900' }}
114+
* transform="shrink-11.5 rotate--30"
115+
* />
116+
* </FontAwesomeLayers>
117+
* </div>
118+
* )
119+
* ```
120+
*/
121+
const LayersText = ({
122+
text,
123+
className,
124+
inverse,
125+
transform,
126+
style,
127+
...attributes
128+
}: Attributes & {
129+
text: string
130+
className?: string | undefined
131+
inverse?: boolean | undefined
132+
transform?: string | Transform | undefined
133+
style?: (React.CSSProperties & CSSVariables) | undefined
134+
}) => {
135+
const textAbstractElement = useMemo(() => {
136+
const textObject = faText(text, {
137+
classes: [
138+
...(className?.split(' ') || []),
139+
...(inverse ? [STYLE_CLASSES.inverse] : []),
140+
],
141+
transform:
142+
typeof transform === 'string'
143+
? faParse.transform(transform)
144+
: transform,
145+
})
146+
147+
return textObject.abstract[0]
148+
}, [text, transform, className, inverse])
149+
150+
return makeReactConverter(textAbstractElement, { ...attributes, style })
151+
}
152+
153+
/**
154+
* Counter component to be used within a `FontAwesomeLayers` component.
155+
*
156+
* @see https://docs.fontawesome.com/web/style/layer
157+
*
158+
* @example
159+
* ```tsx
160+
* import { FontAwesomeLayers, LayersCounter } from '@fortawesome/react-fontawesome'
161+
* import { faEnvelope } from '@fortawesome/free-solid-svg-icons'
162+
*
163+
* // React version of the example from the FontAwesome Web Docs
164+
* export const Example = ({ count = 1419 }) => (
165+
* <FontAwesomeLayers size="4x">
166+
* <FontAwesomeIcon icon={faEnvelope} />
167+
* <LayersCounter count={count.toLocaleString()} style={{ backgroundColor: 'tomato' }} />
168+
* </FontAwesomeLayers>
169+
* )
170+
* ```
171+
*/
172+
const LayersCounter = ({
173+
count,
174+
className,
175+
style,
176+
...attributes
177+
}: Attributes & {
178+
count: number | string
179+
className?: string | undefined
180+
style?: (React.CSSProperties & CSSVariables) | undefined
181+
}) => {
182+
const counterAbstractElement = useMemo(
183+
() =>
184+
faCounter(count, {
185+
classes: className?.split(' '),
186+
}).abstract[0],
187+
[count, className],
188+
)
189+
190+
return makeReactConverter(counterAbstractElement, { ...attributes, style })
191+
}
192+
193+
export { FontAwesomeLayers, LayersText, LayersCounter }

0 commit comments

Comments
 (0)