Skip to content

Commit a3b9f06

Browse files
feat: icons now respect 1em as the default (#311)
1 parent 72c87d2 commit a3b9f06

File tree

3 files changed

+151
-28
lines changed

3 files changed

+151
-28
lines changed

docs/src/routes/icons/examples/icon-example.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export default component$(() => {
1515
{/* <Hugeicons.Abacus /> */}
1616
<AkarIcons.Air class="size-6 text-green-500" />
1717
<AkarIcons.Airpods />
18+
<AkarIcons.AlignLeft />
1819
</div>
1920
</div>
2021

libs/tools/utils/icons/transform/shared.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,16 @@ export function buildSVGElement(
6565
varName: string,
6666
children?: string
6767
): string {
68+
const hasWidth = attrs.some((attr) => attr.startsWith("width="));
69+
const hasHeight = attrs.some((attr) => attr.startsWith("height="));
70+
71+
if (!hasWidth) {
72+
attrs.push('width="1em"');
73+
}
74+
if (!hasHeight) {
75+
attrs.push('height="1em"');
76+
}
77+
6878
attrs.push('viewBox="0 0 24 24"');
6979
attrs.push(`dangerouslySetInnerHTML={${varName}}`);
7080

libs/tools/vite/icons.unit.ts

Lines changed: 140 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,118 @@ describe("icons", () => {
138138
expect(result.code).toContain('className={cn("icon")}');
139139
});
140140

141+
describe("default size attributes", () => {
142+
it("should add default width and height of 1em when not specified", () => {
143+
const code = `
144+
import { Lucide } from "@qds.dev/ui";
145+
146+
function App() {
147+
return <Lucide.Check class="icon" />;
148+
}
149+
`;
150+
const result = transform(code, "test.tsx");
151+
expect(result).toBeTruthy();
152+
expect(result.code).toContain('width="1em"');
153+
expect(result.code).toContain('height="1em"');
154+
expect(result.code).toContain('viewBox="0 0 24 24"');
155+
});
156+
157+
it("should not add default width when width is explicitly provided", () => {
158+
const code = `
159+
import { Lucide } from "@qds.dev/ui";
160+
161+
function App() {
162+
return <Lucide.Check width={24} />;
163+
}
164+
`;
165+
const result = transform(code, "test.tsx");
166+
expect(result).toBeTruthy();
167+
expect(result.code).toContain("width={24}");
168+
expect(result.code).not.toContain('width="1em"');
169+
// Should still add default height
170+
expect(result.code).toContain('height="1em"');
171+
});
172+
173+
it("should not add default height when height is explicitly provided", () => {
174+
const code = `
175+
import { Lucide } from "@qds.dev/ui";
176+
177+
function App() {
178+
return <Lucide.Check height={24} />;
179+
}
180+
`;
181+
const result = transform(code, "test.tsx");
182+
expect(result).toBeTruthy();
183+
expect(result.code).toContain("height={24}");
184+
expect(result.code).not.toContain('height="1em"');
185+
// Should still add default width
186+
expect(result.code).toContain('width="1em"');
187+
});
188+
189+
it("should not add defaults when both width and height are explicitly provided", () => {
190+
const code = `
191+
import { Lucide } from "@qds.dev/ui";
192+
193+
function App() {
194+
return <Lucide.Check width={24} height={24} />;
195+
}
196+
`;
197+
const result = transform(code, "test.tsx");
198+
expect(result).toBeTruthy();
199+
expect(result.code).toContain("width={24}");
200+
expect(result.code).toContain("height={24}");
201+
expect(result.code).not.toContain('width="1em"');
202+
expect(result.code).not.toContain('height="1em"');
203+
});
204+
205+
it("should add default sizes with expression props", () => {
206+
const code = `
207+
import { Lucide } from "@qds.dev/ui";
208+
209+
function App() {
210+
return <Lucide.Check className="icon" aria-label="Check" />;
211+
}
212+
`;
213+
const result = transform(code, "test.tsx");
214+
expect(result).toBeTruthy();
215+
expect(result.code).toContain('width="1em"');
216+
expect(result.code).toContain('height="1em"');
217+
expect(result.code).toContain('className="icon"');
218+
expect(result.code).toContain('aria-label="Check"');
219+
});
220+
221+
it("should respect string width values", () => {
222+
const code = `
223+
import { Lucide } from "@qds.dev/ui";
224+
225+
function App() {
226+
return <Lucide.Check width="2rem" />;
227+
}
228+
`;
229+
const result = transform(code, "test.tsx");
230+
expect(result).toBeTruthy();
231+
expect(result.code).toContain('width="2rem"');
232+
expect(result.code).not.toContain('width="1em"');
233+
expect(result.code).toContain('height="1em"');
234+
});
235+
236+
it("should respect expression width values", () => {
237+
const code = `
238+
import { Lucide } from "@qds.dev/ui";
239+
240+
function App() {
241+
const iconSize = "2em";
242+
return <Lucide.Check width={iconSize} />;
243+
}
244+
`;
245+
const result = transform(code, "test.tsx");
246+
expect(result).toBeTruthy();
247+
expect(result.code).toContain("width={iconSize}");
248+
expect(result.code).not.toContain('width="1em"');
249+
expect(result.code).toContain('height="1em"');
250+
});
251+
});
252+
141253
it("should convert title prop to children", () => {
142254
const code = `
143255
import { Lucide } from "@qds.dev/ui";
@@ -149,7 +261,7 @@ describe("icons", () => {
149261
const result = transform(code, "test.tsx");
150262
expect(result).toBeTruthy();
151263
expect(result.code).toContain(
152-
'<svg viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_check}><title>Checked item</title></svg>'
264+
'<svg width="1em" height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_check}><title>Checked item</title></svg>'
153265
);
154266
});
155267

@@ -165,7 +277,7 @@ describe("icons", () => {
165277
const result = transform(code, "test.tsx");
166278
expect(result).toBeTruthy();
167279
expect(result.code).toContain(
168-
'<svg viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_check}><title>{label}</title></svg>'
280+
'<svg width="1em" height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_check}><title>{label}</title></svg>'
169281
);
170282
});
171283

@@ -184,7 +296,7 @@ describe("icons", () => {
184296
const result = transform(code, "test.tsx");
185297
expect(result).toBeTruthy();
186298
expect(result.code).toContain(
187-
'<svg viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_check}><title>Checked item</title><desc>Extra a11y</desc></svg>'
299+
'<svg width="1em" height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_check}><title>Checked item</title><desc>Extra a11y</desc></svg>'
188300
);
189301
});
190302

@@ -199,7 +311,7 @@ describe("icons", () => {
199311
const result = transform(code, "test.tsx");
200312
expect(result).toBeTruthy();
201313
expect(result.code).toContain(
202-
'<svg viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_check}><desc>This icon indicates completion</desc></svg>'
314+
'<svg width="1em" height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_check}><desc>This icon indicates completion</desc></svg>'
203315
);
204316
});
205317

@@ -215,7 +327,7 @@ describe("icons", () => {
215327
const result = transform(code, "test.tsx");
216328
expect(result).toBeTruthy();
217329
expect(result.code).toContain(
218-
'<svg viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_check}><desc>{desc}</desc></svg>'
330+
'<svg width="1em" height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_check}><desc>{desc}</desc></svg>'
219331
);
220332
});
221333

@@ -230,7 +342,7 @@ describe("icons", () => {
230342
const result = transform(code, "test.tsx");
231343
expect(result).toBeTruthy();
232344
expect(result.code).toContain(
233-
'<svg viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_check}><title>Check mark</title><desc>Indicates completion</desc></svg>'
345+
'<svg width="1em" height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_check}><title>Check mark</title><desc>Indicates completion</desc></svg>'
234346
);
235347
});
236348

@@ -247,7 +359,7 @@ describe("icons", () => {
247359
const result = transform(code, "test.tsx");
248360
expect(result).toBeTruthy();
249361
expect(result.code).toContain(
250-
'<svg viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_check}><title>{iconTitle}</title><desc>{iconDesc}</desc></svg>'
362+
'<svg width="1em" height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_check}><title>{iconTitle}</title><desc>{iconDesc}</desc></svg>'
251363
);
252364
});
253365

@@ -472,7 +584,7 @@ describe("icons", () => {
472584
const result = transform(code, "test.tsx");
473585
expect(result).toBeTruthy();
474586
expect(result.code).toContain(
475-
'<svg viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_check}><title>Check</title><desc>Description</desc></svg>'
587+
'<svg width="1em" height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_check}><title>Check</title><desc>Description</desc></svg>'
476588
);
477589
});
478590

@@ -592,7 +704,7 @@ describe("icons", () => {
592704
"import __qds_i_lucide_check from 'virtual:icons/lucide/check'"
593705
);
594706
expect(result.code).toContain(
595-
'<svg width={24} class="text-green-500" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_check} />'
707+
'<svg width={24} class="text-green-500" height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_check} />'
596708
);
597709
});
598710

@@ -610,7 +722,7 @@ describe("icons", () => {
610722
"import __qds_i_lucide_x from 'virtual:icons/lucide/x'"
611723
);
612724
expect(result.code).toContain(
613-
'<svg width={24} class="text-red-500" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_x} />'
725+
'<svg width={24} class="text-red-500" height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_x} />'
614726
);
615727
});
616728

@@ -628,7 +740,7 @@ describe("icons", () => {
628740
"import __qds_i_lucide_heart from 'virtual:icons/lucide/heart'"
629741
);
630742
expect(result.code).toContain(
631-
'<svg width={24} class="text-red-500 fill-current" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_heart} />'
743+
'<svg width={24} class="text-red-500 fill-current" height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_heart} />'
632744
);
633745
});
634746

@@ -662,16 +774,16 @@ describe("icons", () => {
662774
"import __qds_i_lucide_star from 'virtual:icons/lucide/star'"
663775
);
664776
expect(result.code).toContain(
665-
'<svg width={24} class="text-green-500" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_check} />'
777+
'<svg width={24} class="text-green-500" height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_check} />'
666778
);
667779
expect(result.code).toContain(
668-
'<svg width={24} class="text-red-500" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_x} />'
780+
'<svg width={24} class="text-red-500" height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_x} />'
669781
);
670782
expect(result.code).toContain(
671-
'<svg width={24} class="text-red-500 fill-current" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_heart} />'
783+
'<svg width={24} class="text-red-500 fill-current" height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_heart} />'
672784
);
673785
expect(result.code).toContain(
674-
'<svg width={24} class="text-yellow-500" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_star} />'
786+
'<svg width={24} class="text-yellow-500" height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_star} />'
675787
);
676788
});
677789

@@ -689,7 +801,7 @@ describe("icons", () => {
689801
"import __qds_i_heroicons_check_circle from 'virtual:icons/heroicons/check-circle'"
690802
);
691803
expect(result.code).toContain(
692-
'<svg width={24} class="text-green-500" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_heroicons_check_circle} />'
804+
'<svg width={24} class="text-green-500" height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_heroicons_check_circle} />'
693805
);
694806
});
695807

@@ -707,7 +819,7 @@ describe("icons", () => {
707819
"import __qds_i_tabler_check from 'virtual:icons/tabler/check'"
708820
);
709821
expect(result.code).toContain(
710-
'<svg width={24} class="text-green-500" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_tabler_check} />'
822+
'<svg width={24} class="text-green-500" height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_tabler_check} />'
711823
);
712824
});
713825

@@ -729,7 +841,7 @@ describe("icons", () => {
729841
expect(result.code).toContain("__qds_i_akaricons_airpods");
730842

731843
expect(result.code).toContain(
732-
'<svg viewBox="0 0 24 24" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_akaricons_airpods} />'
844+
'<svg viewBox="0 0 24 24" width="1em" height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_akaricons_airpods} />'
733845
);
734846

735847
expect(result.code).not.toContain("__qds_i_akar-icons_airpods");
@@ -753,7 +865,7 @@ describe("icons", () => {
753865
expect(result.code).toContain("__qds_i_materialsymbols_ac_unit_rounded");
754866

755867
expect(result.code).toContain(
756-
'<svg class="text-blue-500" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_materialsymbols_ac_unit_rounded} />'
868+
'<svg class="text-blue-500" width="1em" height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_materialsymbols_ac_unit_rounded} />'
757869
);
758870

759871
expect(result.code).not.toContain("__qds_i_material-symbols_ac-unit-rounded");
@@ -772,7 +884,7 @@ describe("icons", () => {
772884

773885
// Should include consumer's viewBox and width
774886
expect(result.code).toContain(
775-
'<svg viewBox="0 0 32 32" width={32} viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_check} />'
887+
'<svg viewBox="0 0 32 32" width={32} height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_check} />'
776888
);
777889
});
778890

@@ -830,28 +942,28 @@ describe("icons", () => {
830942

831943
// Should contain all the transformed icons
832944
expect(result.code).toContain(
833-
'<svg width={24} class="text-green-500" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_check} />'
945+
'<svg width={24} class="text-green-500" height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_check} />'
834946
);
835947
expect(result.code).toContain(
836-
'<svg width={24} class="text-red-500" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_x} />'
948+
'<svg width={24} class="text-red-500" height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_x} />'
837949
);
838950
expect(result.code).toContain(
839-
'<svg width={24} class="text-red-500 fill-current" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_heart} />'
951+
'<svg width={24} class="text-red-500 fill-current" height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_heart} />'
840952
);
841953
expect(result.code).toContain(
842-
'<svg width={24} class="text-yellow-500" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_star} />'
954+
'<svg width={24} class="text-yellow-500" height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_star} />'
843955
);
844956
expect(result.code).toContain(
845-
'<svg width={24} class="text-gray-500" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_search} />'
957+
'<svg width={24} class="text-gray-500" height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_search} />'
846958
);
847959
expect(result.code).toContain(
848-
'<svg width={24} class="text-green-500" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_heroicons_check_circle} />'
960+
'<svg width={24} class="text-green-500" height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_heroicons_check_circle} />'
849961
);
850962
expect(result.code).toContain(
851-
'<svg width={24} class="text-red-500" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_heroicons_x_circle} />'
963+
'<svg width={24} class="text-red-500" height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_heroicons_x_circle} />'
852964
);
853965
expect(result.code).toContain(
854-
'<svg width={24} class="text-green-500" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_tabler_check} />'
966+
'<svg width={24} class="text-green-500" height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_tabler_check} />'
855967
);
856968

857969
// Validate JSX syntax using oxc-parser

0 commit comments

Comments
 (0)