lawgic-dev-kit
v0.45.0
Published
Componentes de UI para Lawgic
Downloads
3,346
Readme
lawgic-dev-kit
The shared React component library behind Lawgic's frontends. Both apps consume it as a published npm package:
lawgic-pi— the customer-facing applawgic-management— the internal staff app
If a component is used by both — or clearly would be — it belongs here.
When does a component belong in this library?
This is the rule. Apply it before writing anything new.
It belongs in the library when it is used by more than one app, or clearly would be, and carries no app-specific data, routing, or domain vocabulary.
It stays local to the app when it is bound to that app's routes, Redux store, API services, or business language — marca, expediente, oficio, ticket, reporte.
When a component is nearly shared, split it. The presentational shell comes here; the data-bound wrapper stays in the app. DataTable and ToolHeader follow this split without living in the library yet — their skeleton is kept identical by hand between the two apps, while each app's own copy injects its own manager, filters and search through slots. See T-102-incompatible-components.md for the current state and why the skeleton has not moved here.
Some concrete calls that follow from the rule:
- A component that fetches its own data does not belong here. If it needs data, it takes it as a prop or the caller passes a rendered node into a slot.
- A component that imports from
next/link,next/imageornext/navigationdoes not belong here. The library is framework-agnostic; take arenderLinkprop instead. - A component whose only difference between the two apps is styling tokens belongs here, with the difference resolved to a literal value.
- Two components sharing a name but not a purpose are not duplicates. Do not merge them just because the names collide.
Anything that could not be unified is written down in T-102-incompatible-components.md at the workspace root, with the reason and what would have to change first. Read it before attempting to share Sidebar, SearchBar, MultiValueTextInput, HeaderFilter, FullLogo or status.ts.
Layout of the library
src/
components/
atoms/ smallest units — Button, Chip, Checkbox, TextInput, IconButton
molecules/ compositions of atoms — DateInput, Dropdown, SideModal, Tooltip, AlertBanner
organisms/ full sections — EmptyDialog, ImageViewer, PageShell
hooks/ useTheme, useFilterToggler, useImageLoadStatus
types/ shared contracts: pagination, filters, table, modal, toast
themes/ the LawgicTheme shape and its default
providers/ ThemeProvider
contexts/ themeContextComponent folder pattern
Every new component follows the shape of atoms/Button/:
Button/
Button.tsx component body, default export at the bottom
Button.types.ts the exported props interface
Button.styles.ts className maps and resolvers — only when there is a real map
Button.stories.tsx Storybook story, required
index.ts export { default as Button } from './Button';
export type { ButtonProps } from './Button.types';New folders re-export their props types from index.ts. Consumers need them at call sites, and several older components predate the convention and do not — do not copy that omission.
Barrels
The export chain is <Component>/index.ts → atoms|molecules|organisms/index.ts → components/index.ts → src/index.ts. Adding a component means adding one line to its layer's barrel.
The layer barrels are inconsistent about style — atoms/index.ts uses export * from "./X/index"; while molecules/index.ts uses export * from "./X";. Match the file you are editing rather than "fixing" it.
Imports inside src/components
Always relative: ../FloatingMenu, ../../atoms/Button. The @/* alias exists in tsconfig but is never used inside the component tree — do not introduce it.
Never deep-import through node_modules. A path like node_modules/lawgic-dev-kit/dist/... bypasses the exports map and breaks on the next build.
Build and publish
npm run lab # Storybook on :6006 — the only visual verification this repo has
npm run build:lib # the real library build
npm run lint
npx tsc --noEmit -p tsconfig.app.jsonnpm run build builds Storybook, not the library. It is a trap. The library build is build:lib, which runs vite build, then build:types, then scripts/build-consumable-css.mjs.
Type-checking must name the project explicitly: tsconfig.app.json is what build:types uses, and a bare tsc --noEmit picks a different one.
Publishing
azure-pipeline-prod.yml triggers on push to main, runs build:lib, then npm publish.
There is no npm version step. Bump version in package.json manually, as a commit on your branch, before it merges. A stale version fails the publish on a duplicate.
Both apps depend on a caret range, so mind what the bump means: on 0.x, ^0.25.4 resolves >=0.25.4 <0.26.0. A patch bump reaches both apps on their next install with no action; a minor bump does not, until someone raises the range in each app. Use that deliberately — a minor bump is the right choice when you want the apps to opt in rather than drift.
Installing
The library depends on FontAwesome Pro, which lives on a private registry. You need an .npmrc with the FontAwesome registry and a token before npm install will work. Do not commit that file.
Styling
Tailwind v4, CSS-first. src/index.css does @import 'tailwindcss', then @config '../tailwind.config.ts', then declares the dark variant.
The 10px base
html { font-size: 62.5% }, so the numeric scale keys are pixel values: spacing[24] is 2.4rem, which renders as 24px. p-24 means 24 pixels, not 6rem. Both apps replicate this exact scale, so classes port between the three repos unchanged.
Three traps that cost real debugging time
1. The apps do not scan this library for Tailwind classes. Their content globs cover only their own ./src/**. Library styles reach them solely through import 'lawgic-dev-kit/styles.css'. A class that does not resolve against this repo's tailwind.config.ts renders unstyled, with no build error anywhere.
App-only tokens are the usual cause. text-muted, border-divider, z-modal, z-dropdown, z-sidebar and z-filter-bar exist in lawgic-management and nowhere else — inside the library, use the literal value.
After adding a component, confirm its classes actually shipped:
grep -cF 'max-w-\[1600px\]' dist/lawgic-dev-kit.css # 1
grep -cF 'max-w-[1600px]' dist/lawgic-dev-kit.css # 0 — wrong, and this is the trapTwo separate things bite here, and together they have already produced one false alarm during this migration:
- Tailwind escapes brackets in the emitted selector, so the CSS literally contains
.max-w-\[1600px\]. Your pattern has to include the backslashes. - Use
grep -F. Without it,[1600px]is a regex character class and matches nothing you meant.
Get either wrong and you get a confident 0 for a class that shipped perfectly well. When a count comes back zero, re-check the pattern before you believe it — and confirm against a class you know is there.
The check can also lie in the other direction. Tailwind v4 scans the whole repo, this file included, so a class name written in prose here is enough to emit its rule. text-muted and border-divider were both in the stylesheet before any component used them, purely because the paragraph above names them. So a non-zero count proves the class compiles, not that your component uses it — to prove the component, grep src/ too, or pick a class nothing else mentions.
2. This library compiles with an older Tailwind than the apps. package.json declares tailwindcss: "latest", a non-reproducible spec that currently resolves to 4.0.17, while the apps run 4.2 and 4.3. Any utility introduced in 4.1 or later emits nothing here.
EmptyDialog hit this: it arrived carrying justify-center-safe, which works in the app and produces no rule at all from the library. The workaround is the arbitrary-property form, which compiles on any 4.x:
[justify-content:safe_center] instead of justify-center-safePinning the library to ^4.1.12 is the real fix, but it regenerates the CSS of every component at once with no visual diff surface to check it against — it needs its own ticket.
3. User-facing strings should be props, not t() calls. useTheme() returns a t injected by each app. lawgic-pi wires real i18next with full catalogues; lawgic-management wires a hardcoded map of about a dozen keys. A key the map does not know renders as the raw key, silently.
So: layout and presentational components take their copy as ReactNode props. Reach for t() only when the key already exists in both bridges. Spanish literals are an acceptable default for aria-labels and fixed copy — the products are Spanish-only.
Theme
<ThemeProvider theme={{ defaultColorScheme: 'blue', t }}>ThemeProvider also owns dark mode: it watches prefers-color-scheme and toggles dark on <html>. Components opt in with dark: variants.
Conventions
- No comments in source. No
//, no block comments, no JSDoc, no TODO markers. Rationale belongs in the PR description and the commit message. Comments that already exist stay — do not delete them as drive-by cleanup. - English for everything technical — identifiers, filenames, branches, commits. Neutral Mexican Spanish for anything a user reads, using tuteo ("puedes", "tu marca"), never voseo.
- Small, atomic commits. One component per commit, including its story and its barrel line. Imperative one-line messages:
Add the AlertBanner molecule, notadded stuff. - A story for every new component. There are no tests and no PR pipeline here; Storybook is the only executable verification this repo has. Treat a missing story as a missing test.
- No
.test.tsx/.spec.tsxfiles unless someone asks for them.
