Skip to content

Commit e0ef804

Browse files
committed
feat: nest output inside icons layer
1 parent b800e51 commit e0ef804

File tree

4 files changed

+98
-30
lines changed

4 files changed

+98
-30
lines changed

.changeset/famous-trees-carry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'phosphor-icons-tailwindcss': minor
3+
---
4+
5+
support adding rules to CSS layer, default to `icons`

README.md

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -64,22 +64,26 @@ For all available icon names and weight, visit [phosphoricons.com][phosphor].
6464
The output CSS look something like this:
6565

6666
```css
67-
.ph {
68-
--ph-url: none;
69-
width: 1em;
70-
height: 1em;
71-
background-color: currentcolor;
72-
color: inherit;
73-
mask-image: var(--ph-url);
74-
mask-size: 100% 100%;
75-
mask-repeat: no-repeat;
67+
@layer icons.base {
68+
.ph {
69+
--ph-url: none;
70+
width: 1em;
71+
height: 1em;
72+
background-color: currentcolor;
73+
color: inherit;
74+
mask-image: var(--ph-url);
75+
mask-size: 100% 100%;
76+
mask-repeat: no-repeat;
77+
}
7678
}
7779

78-
.ph-\[info\] {
79-
url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xMjgsMjRBMTA0LDEwNCwwLDEsMCwyMzIsMTI4LDEwNC4xMSwxMDQuMTEsMCwwLDAsMTI4LDI0Wm0wLDE5MmE4OCw4OCwwLDEsMSw4OC04OEE4OC4xLDg4LjEsMCwwLDEsMTI4LDIxNlptMTYtNDBhOCw4LDAsMCwxLTgsOCwxNiwxNiwwLDAsMS0xNi0xNlYxMjhhOCw4LDAsMCwxLDAtMTYsMTYsMTYsMCwwLDEsMTYsMTZ2NDBBOCw4LDAsMCwxLDE0NCwxNzZaTTExMiw4NGExMiwxMiwwLDEsMSwxMiwxMkExMiwxMiwwLDAsMSwxMTIsODRaIi8+PC9zdmc+);
80-
}
80+
@layer icons {
81+
.ph-\[info\] {
82+
url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xMjgsMjRBMTA0LDEwNCwwLDEsMCwyMzIsMTI4LDEwNC4xMSwxMDQuMTEsMCwwLDAsMTI4LDI0Wm0wLDE5MmE4OCw4OCwwLDEsMSw4OC04OEE4OC4xLDg4LjEsMCwwLDEsMTI4LDIxNlptMTYtNDBhOCw4LDAsMCwxLTgsOCwxNiwxNiwwLDAsMS0xNi0xNlYxMjhhOCw4LDAsMCwxLDAtMTYsMTYsMTYsMCwwLDEsMTYsMTZ2NDBBOCw4LDAsMCwxLDE0NCwxNzZaTTExMiw4NGExMiwxMiwwLDEsMSwxMiwxMkExMiwxMiwwLDAsMSwxMTIsODRaIi8+PC9zdmc+');
83+
}
8184

82-
/* ...truncated... */
85+
/* ...truncated... */
86+
}
8387
```
8488

8589
## Configuration
@@ -95,6 +99,7 @@ import phosphorIcons from "phosphor-icons-tailwindcss";
9599
export default {
96100
plugins: [phosphorIcons({
97101
prefix: 'ph', // for the icon classes
102+
layer: 'icons', // for the CSS layer
98103
customProperty: '--ph-url',
99104
})],
100105
};
@@ -106,6 +111,7 @@ Similarly, for Tailwind 4:
106111
@import 'tailwindcss';
107112
@plugin 'phosphor-icons-tailwindcss' {
108113
prefix: ph;
114+
layer: icons;
109115
custom-property: --ph-url; /* use the kebab-case alias to avoid auto-format by stylelint / prettier */
110116
}
111117
```
@@ -114,6 +120,36 @@ Similarly, for Tailwind 4:
114120

115121
You may notice this library utilizes Tailwind's support for [arbitrary value](https://tailwindcss.com/docs/adding-custom-styles#using-arbitrary-values), i.e `ph-[info]` instead of `ph-info` to map to the regular info icon. This is to avoid unnecessary parsing during development, especially for [Taliwind language server](https://github.com/tailwindlabs/tailwindcss-intellisense). Arbitrary value syntax allows parsing ad-hoc only the icons actually being used. Otherwise, parsing 9000+ icons may cause slow-down that negatively impacts developer experience.
116122

123+
## CSS layer
124+
125+
By default, the plugin generates CSS in the `icons` [layer](https://developer.mozilla.org/en-US/docs/Web/CSS/@layer). This is to ensure the generated CSS is
126+
isolated and can be easily overridden by other utility classes, or extended upon. You can change the
127+
layer by specifying `layer` option in the config object, as discussed in [Configuration](#configuration),
128+
or passing `null` to skip any layering.
129+
130+
### Tailwind 4
131+
132+
Sorting works differently in Tailwind 4, so the generated CSS is nested inside `@layer utilities`, that is:
133+
134+
```css
135+
@layer utilities {
136+
@layer icons.base {
137+
.ph {
138+
/* truncated base rule */
139+
}
140+
}
141+
@layer icons {
142+
.ph-[info] {
143+
/* truncated specifier rule */
144+
}
145+
}
146+
}
147+
```
148+
149+
> [!NOTE]
150+
> Notice the base rule is inside a sub-layer: this is to ensure specifier classes always have higher
151+
> specificity, no matter if they are declare before or after the base rule in your CSS source.
152+
117153
## Usage with Tailwind `@apply` directive
118154

119155
You may utilize `@apply` to extend your use case beyond just for icons. This is helpful if you want

src/plugin.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ export interface Options {
88
* ```
99
*/
1010
prefix: string;
11+
/**
12+
* The layer to output CSS rules into.
13+
* Default to `icons`.
14+
* Set to null skip layering.
15+
*
16+
* Note: The base class will be output to the sub-layer `.base`
17+
*/
18+
layer: string | null;
1119
/*
1220
* CSS custom property for icon URL.
1321
* Default to `--ph-url`

src/plugin.js

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,37 @@ export default createPlugin.withOptions(
1111
function (options = {}) {
1212
const prefix = options.prefix ?? 'ph';
1313
const customProperty = options['custom-property'] ?? options['customProperty'] ?? '--ph-url';
14+
let layer = options.layer;
15+
if (layer === undefined) layer = 'icons';
1416

1517
return function (api) {
16-
api.addComponents({
17-
[`.${prefix}`]: {
18-
[customProperty]: 'none',
19-
width: '1em',
20-
height: '1em',
21-
backgroundColor: 'currentcolor',
22-
color: 'inherit',
23-
maskImage: `var(${customProperty})`,
24-
maskSize: '100% 100%',
25-
maskRepeat: 'no-repeat',
26-
27-
'&:is(span,i)': {
28-
display: 'inline-block',
29-
},
18+
const declarations = {
19+
[customProperty]: 'none',
20+
width: '1em',
21+
height: '1em',
22+
backgroundColor: 'currentcolor',
23+
color: 'inherit',
24+
maskImage: `var(${customProperty})`,
25+
maskSize: '100% 100%',
26+
maskRepeat: 'no-repeat',
27+
28+
'&:is(span,i)': {
29+
display: 'inline-block',
3030
},
31-
});
31+
};
32+
if (layer) {
33+
api.addUtilities({
34+
[`.${prefix}`]: {
35+
[`@layer ${layer}.base`]: declarations,
36+
},
37+
});
38+
} else {
39+
api.addUtilities({
40+
[`.${prefix}`]: declarations,
41+
});
42+
}
3243

33-
api.matchComponents({
44+
api.matchUtilities({
3445
[prefix]: (icon) => {
3546
// syntax: <name>[--weight]
3647
let [name = '', weight = 'regular'] = icon.split('--');
@@ -52,9 +63,17 @@ export default createPlugin.withOptions(
5263
}
5364
}
5465

55-
return {
66+
const declarations = {
5667
[customProperty]: url,
5768
};
69+
70+
if (layer) {
71+
return {
72+
[`@layer ${layer}`]: declarations,
73+
};
74+
}
75+
76+
return declarations;
5877
},
5978
});
6079
};

0 commit comments

Comments
 (0)