stroke_morph_icons
Lucide, Tabler and
Heroicons icon data as ready-to-use
stroke_morph constants.
dependencies:
stroke_morph_icons: ^0.1.0
import 'package:stroke_morph_icons/lucide.dart';
MorphIcon(icon: open ? MorphLucideIcons.x : MorphLucideIcons.menu)
One dependency, one import. Each pack library re-exports
stroke_morph, so naming an icon also
brings MorphIcon, MorphController and the rest of the engine into scope —
importing package:stroke_morph/stroke_morph.dart alongside it is redundant.
| Library | Class | Icons | Source |
|---|---|---|---|
package:stroke_morph_icons/lucide.dart |
MorphLucideIcons |
1 756 | lucide-static v1.28.0 |
package:stroke_morph_icons/tabler.dart |
MorphTablerIcons |
5 130 | @tabler/icons v3.46.0 (outline) |
package:stroke_morph_icons/heroicons.dart |
MorphHeroicons |
324 | heroicons v2.2.0 (24×24 outline) |
Each pack is one class of static consts, so the import needs no prefix and the
packs cannot collide.
All three are stroke packs drawn on the same 24×24 grid, so they share a
coordinate space: any icon morphs into any other, across packs, with no
fitIcon re-grid.
MorphIcon(from: MorphLucideIcons.menu, to: MorphHeroicons.xMark, progress: t)
Lucide and Tabler publish exactly the [tag, attrs] shape morphicons consumes,
so those constants are a transcription of the upstream data, not a conversion.
Heroicons publishes SVG files instead, and tool/svg_to_nodes.dart turns them
into that same shape first — still a transcription, one step longer. Both
generators are checked in — see below.
Only outline styles are here. Heroicons' solid, mini and micro styles are
filled silhouettes, several with fill-rule holes; stroke_morph would morph
their outline quite happily, but the result is not the icon. Tabler's filled
style is left out for the same reason. Heroicons outline is drawn at
stroke-width 1.5, so MorphIcon(strokeWidth: 1.5) reproduces the pack's own
weight — the default 2 is Lucide's.
Names are camelCased from the upstream name — arrow-right becomes
arrowRight. The five in Lucide and Tabler that collide with Dart keywords
take an Icon suffix:
importIcon, switchIcon, functionIcon, libraryIcon, factoryIcon. The
same escape covers anything that cannot be a static member (byName,
hashCode, toString, runtimeType, noSuchMethod).
Tree-shaking
Every icon is its own static const on the pack class — a field the compiler
retains one by one — so an app links only the ones it names. A const map is
the opposite: it is retained whole the moment anything touches it, dragging the
entire pack into the bundle. There is exactly one such map, and naming it is
your opt-in:
MorphIcon(icon: MorphLucideIcons.byName[nameFromServer]); // retains ALL 1 756
Use it for galleries and name-driven lookups. For a fixed set of icons, name the constants directly.
Regenerating
npm pack lucide-static @tabler/icons heroicons # anywhere, then extract
# Lucide and Tabler ship the node JSON already.
dart run tool/gen_icons.dart --pack lucide --version 1.28.0 \
--input path/to/icon-nodes.json
dart run tool/gen_icons.dart --pack tabler --version 3.46.0 \
--input path/to/tabler-nodes-outline.json
# Heroicons ships SVG, so it needs the transcription pass first.
dart run tool/svg_to_nodes.dart --input path/to/heroicons/24/outline \
--output /tmp/heroicons-outline.json
dart run tool/gen_icons.dart --pack heroicons --version 2.2.0 \
--input /tmp/heroicons-outline.json
dart format . # generated files are house-style too
Both tools fail loudly rather than guessing. gen_icons.dart throws with both
upstream names if two would produce the same Dart identifier. svg_to_nodes.dart
stops on anything its scan does not fully understand — an unknown tag, a <g>,
a transform, a filled path, a viewBox that is not 0 0 24 24 — naming the
file. An off-grid pack is not an error in itself; it just needs a fitIcon
decision from whoever adds it, which is exactly what the stop is for.
Licence
This package is MIT. Lucide is ISC; Tabler Icons and Heroicons are MIT. All
three licences travel with the icon data — see the repository LICENSE and the
upstream projects.