feat: icon registry with lucide replacing radix icons - #883
feat: icon registry with lucide replacing radix icons#883rohanchkrabrty wants to merge 4 commits into
Conversation
Covers the breaking changes (lucide peer dependency, the eight icons that change shape, the 16x16 / strokeWidth 1.5 base props, client-component registration, the nine removed in-house names), the new override API, and the recommendation to import icons from @raystack/apsara/icons. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LuBECZxRRYnorQ9nvJ1U5E
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This comment was marked as outdated.
This comment was marked as outdated.
|
Out of 243 icons only 26 of icons are used by Apsara's own components. The other 217 are a curated catalog for consumers, and the curation is what creates a problem: lucide has 1773 icons, so anyone needing one outside our 243 has to either PR Proposal: ship 26, export the factory
// src/icons.ts — the app's single place for icons
import { createIcon } from '@raystack/apsara/icons';
import { Rocket, Trash2 } from 'lucide-react';
export const RocketIcon = createIcon('Rocket', Rocket);
export const TrashIcon = createIcon('Trash', Trash2);Correct sizing by default,
Implementation: at 26 icons, we can drop the codegen too The map + codegen design earns its keep at 243 icons. At 26 it's machinery we don't need: the JSON map, the generator script, gitignored generated files, the // icons/icons.tsx — this file IS the map
import { Check, ChevronDown, X /* ...23 more */ } from 'lucide-react';
import { createIcon } from './create-icon';
export const CheckIcon = createIcon('CheckIcon', Check);
export const ChevronDownIcon = createIcon('ChevronDownIcon', ChevronDown);
// ...Every property of the current design survives:
And we gain: icon code is visible in the repo and reviewable in PRs, Where this goes later: native multi-library support falls out for free If we ever want Apsara to officially support two or three icon libraries (say lucide and radix) and let people switch through Theme, this architecture already is that feature — a "native" library is just a pre-made, tested override map promoted from user-space into the package. The PR's own migration guide proves it: its copy-paste radix map is exactly this file, just living in the docs. Each library ships as one committed file behind its own entry point, exporting a set — icons and their tuning together, since libraries don't share prop semantics (lucide is stroke-based in a 24-unit box, radix is fill-based in 15): // @raystack/apsara/icons/radix
export const radixIconSet: IconSet = {
icons: { CheckIcon: RadixCheck, XIcon: RadixX /* ...24 more */ },
iconProps: { width: 16, height: 16 } // no strokeWidth: silent on what radix doesn't need,
// so lucide-based custom icons keep their stroke default
};Theme grows one prop, and switching becomes: import { radixIconSet } from '@raystack/apsara/icons/radix';
<Theme iconSet={radixIconSet}>
<App />
</Theme>Each alternate library is an optional peer dependency — apps that never import the set never pull it into the bundle. And because a set is plain data, the existing layering keeps working: per-name At 26 names, a third library is 26 hand-written lines and a render test. At 243 it would be another codegen target and another 243 names to reconcile on every swap — one more reason the small catalog is the version of this design that scales in the direction we'd actually grow. Docs: one page in the Theme section The current docs are shaped around the big catalog — an Icons section with a Usage page and a searchable 243-icon gallery. With 26 icons and one recipe, the content collapses to a single "Icons" page, and it belongs in the Theme section, since after this change everything interesting about icons flows through Theme:
The migration guide stays its own document (release-specific, will age out), and gets shorter: "restore the radix appearance" becomes one line with What we give up: the ready-made catalog and the big gallery page. The docs become the 26 built-in names plus the recipe above. In exchange the public API drops from 243 names to 26 plus one factory, users own their icon set with no gap, and the next library migration touches a fraction of the surface. |
|
We should also use lucide sparkle icon instead of custom Copilot. |
Summary
packages/raystack/icons/icon-map.json. Codegen turns it into 243 per-icon modules, so changing icon library later is a one-file edit. The generated modules are gitignored and rebuilt byprepare,prebuild,predev,pretestand CI.@radix-ui/react-iconsas the default library, added as a narrow peer dependency (>=0.500.0 <0.600.0). Eight icons draw a different shape, and icons now render at 16×16 withstrokeWidth={1.5}— which is the design's 1px stroke, since the prop counts units of lucide's 24-unit viewBox.<Theme icons={…}>and set props for all of them with<Theme iconProps={…}>. Maps are partial and layer per name across nested<Theme>s, the context holds overrides only so unused icons stay tree-shakeable, and every icon rendersdata-icon="<Name>"for styling from CSS without a re-render.@raystack/apsara/iconsstays supported and is now the recommended path for icons: it reaches no component module, so one icon costs one icon even when a bundler ignores"sideEffects": false. The nine legacy in-house names are removed rather than aliased, since the release is breaking anyway.pnpm check:icon-mapin the test and both release workflows — it catches a dropped lucide export, which the build cannot, because lucide is an external and the build does not fail on type errors.