asma-ui-core 3.68.0 → 3.68.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Readme.md CHANGED
@@ -1,76 +1,517 @@
1
- # How to create and use the Design System
1
+ # asma-ui-core
2
2
 
3
- ## Component composition.
3
+ The ASMA design system — the shared React component library that ASMA frontends build their UI from.
4
+ Form controls, selects and comboboxes, dialogs, menus, tooltips, tables, date/time pickers, ~180
5
+ icons, and the colour and typography tokens behind them.
4
6
 
5
- - When creating components that consists of several components, each component should be styled.
6
- - Each components that is used to create a "composed" component should be styled, and remember to check if the component alread is styled.
7
- - When creating a "composed component" evalute if it's really needed to create a composed component, or just document how to use the styled components.
8
- - MUI often has documentation on how to create composed components.
7
+ **This library has no MUI and no Base UI.** Every component is a native implementation on plain HTML,
8
+ ARIA and Tailwind. That rewrite cut the dependency footprint from ~324 KB to ~57 KB gzipped see
9
+ [Bundle size](#bundle-size-and-the-mui-removal).
9
10
 
10
- ## Documentation and testing
11
+ - **New here?** Start with [Quick start](#quick-start), then browse [What's in the box](#whats-in-the-box).
12
+ - **Upgrading an app from an older version?** Read [Migrating from the MUI-era version](#migrating-from-the-mui-era-version) first — some things break silently.
13
+ - **Something looks wrong?** [Troubleshooting](#troubleshooting) covers the usual suspects.
11
14
 
12
- - All components shall be documented and tested in Storybook before PR is created.
15
+ ---
13
16
 
14
- ## Changelog
17
+ ## Contents
15
18
 
16
- The project uses "changesets" to automatically bump version and create a changelog.
19
+ - [Requirements](#requirements)
20
+ - [Quick start](#quick-start)
21
+ - [What's in the box](#whats-in-the-box)
22
+ - [Figma ↔ React matching](#figma--react-matching)
23
+ - [Styling API](#styling-api)
24
+ - [Theming](#theming)
25
+ - [Bundle size and the MUI removal](#bundle-size-and-the-mui-removal)
26
+ - [Migrating from the MUI-era version](#migrating-from-the-mui-era-version)
27
+ - [Troubleshooting](#troubleshooting)
28
+ - [Contributing to the library](#contributing-to-the-library)
17
29
 
18
- For each new feature or change run "npx changeset". Choose patch for a change or bug fix, minor for a new feature or major version for a breaking change. This creates a markdown file in .changeset folder. Commit to brach/PR as normal.
30
+ ---
19
31
 
20
- A PR can have several changesets.
32
+ ## Requirements
21
33
 
22
- The pipeline will automatically bump version and create changelog after merge to master.
34
+ | | |
35
+ | --- | --- |
36
+ | React | 18 or 19 (peer dependency) |
37
+ | Tailwind CSS | v3 or v4 — both are in use across ASMA apps; wiring differs slightly, see below |
38
+ | Bundler | Anything ESM-native (Vite is what every ASMA app uses) |
39
+ | Package | Public on npm as `asma-ui-core` — no registry setup needed |
23
40
 
24
- ## Index file
41
+ The library ships prebuilt ESM in `dist/` with TypeScript declarations. Its own runtime dependencies
42
+ (`@floating-ui/react`, `@tanstack/react-table`, `@dnd-kit/*`, `date-fns`, `notistack`,
43
+ `react-day-picker`, `clsx`) install transitively — you do not declare them yourself.
25
44
 
26
- - Index files shall not be edited manually.
27
- - ctix is used for automatically create index files for exporting componens. Run "npm run create-index"
28
- - 'src/styles/index.ccss' is imported to 'src/theme/customMuiColors.ts' so exporting works with ctix.
45
+ ---
29
46
 
30
- ## Using MUI theme in App
47
+ ## Quick start
31
48
 
32
- ```tsx
33
- import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles';
34
- import { defaultTheme, customPalettes } from 'asma-ui-core';
35
-
36
- const dataTheme = window.document.body.attributes.getNamedItem('data-theme');
37
-
38
- const getTheme = () => {
39
- switch (dataTheme?.value) {
40
- case 'fretex':
41
- return customPalettes.fretex.primary;
42
- default:
43
- return null;
44
- }
45
- };
46
-
47
- const theme = createTheme(defaultTheme, {
48
- palette: {
49
- ...defaultTheme.palette,
50
- primary: {
51
- ...getTheme(),
49
+ ### 1. Install
50
+
51
+ ```bash
52
+ pnpm add asma-ui-core
53
+ ```
54
+
55
+ ### 2. Import the stylesheet
56
+
57
+ `dist/style.css` carries the design tokens, the component styles and a required reset for native
58
+ popover elements. **Nothing renders correctly without it.** Import it *before* Tailwind so your own
59
+ utilities can win.
60
+
61
+ **Tailwind v4:**
62
+
63
+ ```css
64
+ /* src/styles/index.css */
65
+ @config '../../tailwind.config.ts';
66
+
67
+ @import 'asma-ui-core/dist/style.css';
68
+ @import 'tailwindcss';
69
+ ```
70
+
71
+ **Tailwind v3:**
72
+
73
+ ```scss
74
+ // src/styles/index.scss
75
+ @use 'asma-ui-core/dist/style.css' as core;
76
+ @use 'tailwindcss/base';
77
+ @use 'tailwindcss/components';
78
+ @use 'tailwindcss/utilities';
79
+ ```
80
+
81
+ ### 3. Extend your Tailwind config with the shared tokens
82
+
83
+ The design tokens live in `tw-configs/twConfigs.json` — colours, shadows, animations, keyframes and
84
+ the font stack. Spread them into your own config rather than redefining them; a token change belongs
85
+ in this library, not in your app.
86
+
87
+ ```ts
88
+ // tailwind.config.ts
89
+ import twConfigs from 'asma-ui-core/tw-configs/twConfigs.json'
90
+ import type { Config } from 'tailwindcss'
91
+
92
+ const { boxShadow, animation, keyframes, colors, fontFamily } = twConfigs
93
+
94
+ export default {
95
+ content: ['index.html', 'src/**/*.{tsx,ts,jsx,js}'],
96
+ darkMode: 'media',
97
+ important: true,
98
+ plugins: [],
99
+ theme: {
100
+ extend: { animation, boxShadow, colors, keyframes },
101
+ fontFamily,
52
102
  },
53
- role: customPalettes.role,
54
- },
55
- });
56
-
57
- <React.Fragment>
58
- <ThemeProvider theme={createMuiTheme(theme)}>
59
- <... />
60
- </ThemeProvider>
61
- </React.Fragment>
103
+ } satisfies Config
104
+ ```
105
+
106
+ Two notes on this config:
107
+
108
+ - **`important: true` is recommended, not decorative.** This library's own compiled CSS is built with
109
+ it, so its utilities emit as `!important`. If your app's utilities are *not*, you will lose class
110
+ conflicts against the library in confusing ways. Match it.
111
+ - ASMA apps additionally import `twScreens` from `asma-types` and pass it as `theme.extend.screens`
112
+ to share breakpoints. That package is internal — outside ASMA, just use your own breakpoints.
113
+
114
+ ### 4. Render something
115
+
116
+ ```tsx
117
+ import { StyledButton, StyledInputField } from 'asma-ui-core'
118
+
119
+ export const Example = () => (
120
+ <form className='flex flex-col gap-4'>
121
+ <StyledInputField label='Full name' fullWidth />
122
+ <StyledButton variant='contained' type='submit'>
123
+ Save
124
+ </StyledButton>
125
+ </form>
126
+ )
127
+ ```
128
+
129
+ > **Pass `fullWidth` when you want a field to fill its container.** Without it, `StyledInputField`
130
+ > is pinned to 235px by design. This is the single most common surprise for new users.
131
+
132
+ ### 5. Add the snackbar provider (optional)
133
+
134
+ There is no theme provider — that went away with emotion. The one provider most apps mount is the
135
+ snackbar host, needed only if you use toasts:
136
+
137
+ ```tsx
138
+ import { SnackbarProvider, enqueueSnackbar } from 'asma-ui-core'
139
+
140
+ <SnackbarProvider>
141
+ <App />
142
+ </SnackbarProvider>
143
+
144
+ // anywhere below it
145
+ enqueueSnackbar('Saved', { variant: 'alert', severity: 'success' })
62
146
  ```
63
147
 
64
- ## Using Components in App
148
+ It wraps [notistack](https://notistack.com) with the ASMA variants (`alert`, `info`, `default`) and
149
+ re-parents the toast stack into the topmost open modal `<dialog>`, so toasts stay visible and
150
+ clickable above a `StyledDialog`.
151
+
152
+ ---
153
+
154
+ ## What's in the box
155
+
156
+ Every component is exported from the package root. Browse them interactively with Storybook (see
157
+ [below](#browsing-the-components)); this table is for discovery.
158
+
159
+ | Area | Components |
160
+ | --- | --- |
161
+ | **Buttons & actions** | `StyledButton`, `StyledLink`, `CopyButton`, `CopyWrapper` |
162
+ | **Text inputs** | `StyledInputField`, `StyledTextarea`, `StyledSearchField`, `StyledLabel` |
163
+ | **Selects** | `StyledSelect` + `StyledSelectItem` (single), `StyledSelectAutocomplete` (combobox), `StyledDynamicSelect` (chip multi-select), `Listbox` (headless) |
164
+ | **Toggles** | `StyledCheckbox`, `StyledRadio` / `StyledRadioGroup`, `StyledSwitch`, `StyledSlider` |
165
+ | **Form scaffolding** | `StyledFormControl`, `StyledFormControlLabel`, `StyledFormGroup`, `StyledFormLabel`, `StyledFormHelperText`, `StyledInputLabel` |
166
+ | **Date & time** | `StyledDatePicker`, `StyledTimePicker`, plus `setMidnightTime` / `setZeroTime` helpers |
167
+ | **Overlays** | `StyledDialog` (+ `Title` / `Content` / `Actions`), `MinimizableDialog`, `StyledDrawer`, `StyledPopover`, `StyledTooltip`, `createDialogStack` |
168
+ | **Menus** | `StyledMenu`, `StyledMenuList`, `StyledMenuItem`, `StyledFilterMenu`, `StyledFilterButton` |
169
+ | **Navigation** | `StyledTabs`, `StyledTab`, `StyledAccordion` (+ `Summary` / `Details`) |
170
+ | **Data display** | `StyledTable`, `VirtualList`, `StyledTypography`, `StyledBadge`, `StyledChip`, `StyledInteractiveChip`, `StyledAIDisclosure` |
171
+ | **Feedback** | `StyledAlert`, `StyledSnackbar`, `SnackbarProvider`, `StyledLoading`, `StyledEmptyPage`, `StyledFilteredEmptyState` |
172
+ | **Layout / widgets** | `StyledWidget`, `StyledWidgetHeader`, `StyledWidgetTitle`, `StyledModuleTitle`, `DynamicToolbar` |
173
+ | **Primitives** | `Paper`, `Stack`, `Container`, `Avatar`, `Skeleton`, `Fade`, `Popper`, `ClickAwayListener` |
174
+ | **Hooks & utils** | `usePopupState` (+ `bindTrigger` / `bindPopover` / `bindPopper`), `useSnackbar`, `cn`, `omit`, `prepareForSlot` |
175
+
176
+ ### Icons
177
+
178
+ Around 180 icons ship on a dedicated subpath so you only pay for the ones you use — each is its own
179
+ module of roughly 0.5 KB:
65
180
 
66
181
  ```tsx
67
- import { StyledButton } from 'asma-ui-core';
182
+ import { CloseIcon } from 'asma-ui-core/icons'
183
+ ```
184
+
185
+ ### Conventions worth knowing before you start
186
+
187
+ - **Body text is 16px (`text-base`)** on fields, selects, dropdown options and table bodies. The
188
+ `size` prop changes height and padding, not font size. This matches the Figma spec — resist
189
+ forcing `text-sm`. The Figma source for each component is linked from JSDoc (`@figmaNode` /
190
+ `@figmaProp`); see [Figma ↔ React matching](#figma--react-matching).
191
+ - **Text colour depends on the surface.** Dropdown option rows are `text-delta-700`; the selected
192
+ value in a closed field is `text-delta-800`; placeholders and floating labels are `text-delta-500`.
193
+ If you pass custom `renderOption` / `renderLabel` / `renderValue` content, match the surface it
194
+ renders into.
195
+ - **`dataTest` is required** on interactive form controls (`StyledCheckbox`, `StyledRadio`,
196
+ `StyledSwitch`, …) — it drives end-to-end test selectors. Their `onChange` signature is
197
+ `(event, checked)`.
198
+ - **Reach for `StyledDynamicSelect`** before hand-rolling a chip multi-select. It already handles tag
199
+ chips with delete, `+N` overflow, checkbox option rows, a growing field and the trigger icon. It
200
+ renders a chip group for ≤5 options and a searchable autocomplete for 6+.
201
+ - **Accessibility is part of the component contract** — roles, keyboard interaction and focus
202
+ management ship with each component rather than being left to the call site.
203
+
204
+ ### Browsing the components
205
+
206
+ ```bash
207
+ git clone https://github.com/Carasent-ASMA/asma-ui-core.git
208
+ cd asma-ui-core && pnpm install && pnpm storybook
209
+ ```
210
+
211
+ Storybook runs on `:6006` with docs, controls and an accessibility addon for every component.
212
+
213
+ ---
214
+
215
+ ## Figma ↔ React matching
216
+
217
+ The React components were matched to the ASMA Design System in Figma using **Figma node IDs** plus
218
+ **JSDoc comments** on the component and its props. That is the in-source contract for "this React
219
+ component is this Figma component, and this prop is this Figma property" — not tribal knowledge, and
220
+ not a separate mapping spreadsheet.
68
221
 
69
- ...
222
+ The Design System file is [`wXrXt5uKNNzV2DnQCgyYZH`](https://www.figma.com/design/wXrXt5uKNNzV2DnQCgyYZH).
70
223
 
71
- <>
72
- <StyledButton>
73
- Text
74
- </StyledButton>
75
- </>
224
+ ### How to read the mapping
225
+
226
+ Open any component source. Two JSDoc tags do the work:
227
+
228
+ | Tag | Where | Meaning |
229
+ | --- | --- | --- |
230
+ | `@figmaNode <fileKey>#<node-id>` | Once, on the component or its props type | The Figma component this React type maps to |
231
+ | `@figmaProp <FigmaProperty> = <value mapping>` | On each prop that has a Figma counterpart | How React values map onto Figma variant values |
232
+ | `@figmaProp none — <reason>` | On a prop with no Figma counterpart | Unmapped on purpose (`behavioral`, `a11y`, `ref`, `test hook`) — not forgotten |
233
+
234
+ Turn a node id into a Figma URL by replacing `-` with `:` in the node id:
235
+
236
+ `wXrXt5uKNNzV2DnQCgyYZH#13431-18852` →
237
+ https://www.figma.com/design/wXrXt5uKNNzV2DnQCgyYZH?node-id=13431-18852
238
+
239
+ ```tsx
240
+ /**
241
+ * @figmaNode wXrXt5uKNNzV2DnQCgyYZH#13431-18852
242
+ */
243
+ export type StyledButtonProps = …
244
+ interface commonProps {
245
+ /** @figmaProp Size = medium→"Medium" (h40, text16/24) | small→"Small" (h32, text14/20) */
246
+ size?: 'large' | 'small' | 'medium'
247
+ /** @figmaProp none — test hook */
248
+ dataTest: string
249
+ }
250
+ interface buttonStandartVariantsProps {
251
+ /** @figmaProp Type = contained→"Primary (Contained)" | outlined→"Secondary (Outlined)" | text→"Tertiary" */
252
+ variant?: 'contained' | 'outlined' | 'text' | …
253
+ /** @figmaProp Danger = true→"on" | false→"off" */
254
+ error?: boolean
255
+ }
76
256
  ```
257
+
258
+ Figma **State** (Enabled / Hovered / Focused / Pressed / Disabled) is usually **not** a React prop —
259
+ it is derived at runtime from native pseudo-classes and attributes. The JSDoc says so when that is
260
+ the case.
261
+
262
+ A component with no Design System counterpart is marked `@figmaNode none` with a reason, so the
263
+ absence is explicit.
264
+
265
+ ### Why this exists
266
+
267
+ The tags were how the Figma-to-React parity pass was done: pull the node (size, radius, padding,
268
+ tokens, every variant × state), annotate every prop, then align the implementation until it matches.
269
+ The same tags are what you use when something "looks off" — open the node, compare the `@figmaProp`
270
+ mapping, and you know whether the call site is using the wrong prop or the component is drifting
271
+ from design.
272
+
273
+ The per-component ledger (node id, spec, states covered, remaining deltas) lives in
274
+ [`visual-tests/FIGMA-PARITY.md`](visual-tests/FIGMA-PARITY.md).
275
+
276
+ ---
277
+
278
+ ## Styling API
279
+
280
+ ### `className` is the primary escape hatch
281
+
282
+ Pass Tailwind utilities directly. The whole library is built around this.
283
+
284
+ ### `sx` works, but **flat keys only**
285
+
286
+ `sx` survives as a compatibility shim for the MUI-era API. [`resolveSx`](src/helpers/sx.ts) flattens
287
+ MUI's spacing shorthand and colour aliases into plain `CSSProperties`, so flat keys behave as you'd
288
+ expect: `mt`, `px`, `bgcolor`, `color`, `width`, `flex`, `minWidth`. Numeric spacing still uses MUI's
289
+ 8px unit, so `mt: 2` is `16px`.
290
+
291
+ Anything **nested is dropped at runtime** with a `console.warn` and has no effect:
292
+
293
+ ```tsx
294
+ // ❌ silently does nothing
295
+ // logs: resolveSx: unsupported nested/responsive sx key "…" dropped
296
+ sx={{ '& .MuiInputBase-root': { minHeight: 80 }, '&:hover': { color: 'red' } }}
297
+
298
+ // ✅
299
+ className='min-h-20 hover:text-error-500'
300
+ ```
301
+
302
+ That covers nested selectors, pseudo-classes, responsive arrays/objects and theme-callback functions.
303
+ `slotProps.input.sx` is likewise never read — the input slot takes `style` / `className` / `ref` only.
304
+
305
+ **Prefer `className` for new code.** `sx` exists to keep migrated call sites compiling.
306
+
307
+ ### Two ordering footguns
308
+
309
+ These explain most "my override doesn't work" reports, so they are worth reading once up front.
310
+
311
+ **`cn` is plain clsx — it does not dedupe conflicting utilities.** tailwind-merge was deliberately
312
+ removed. If a component hardcodes `px-0` and you pass `p-4`, *both* land in the class list, and
313
+ Tailwind's own output order (axis utilities after shorthands) means the component's `px-0` wins. Your
314
+ override silently does nothing.
315
+
316
+ Where a default is meant to be overridable, the component gates it with
317
+ [`consumerOverrides`](src/helpers/classOverride.ts) — already applied to `StyledMenuList` padding,
318
+ the `StyledTabs` container inset, and `StyledChip` width. If you hit a default that is not yet gated,
319
+ that is a library fix worth raising, not something to hack around at the call site.
320
+
321
+ **The library's compiled CSS is `!important`.** Its utilities emit as `min-width: 0 !important` and
322
+ similar, which beat your *inline* `style` / `sx` / `slotProps`. Override with a class, not an inline
323
+ style. (If you are authoring components here: a layout default on an element consumers are expected
324
+ to size inline must be an inline-style default set before `resolveSx(...)`, never a utility class.)
325
+
326
+ ---
327
+
328
+ ## Theming
329
+
330
+ Themes are CSS custom properties switched by a `data-theme` attribute on a wrapping element, usually
331
+ `<body>`. There is no JS theme object and no provider:
332
+
333
+ ```html
334
+ <body data-theme="fretex">
335
+ ```
336
+
337
+ | `data-theme` | Theme |
338
+ | --- | --- |
339
+ | absent, or `default` | ASMA default (blue) |
340
+ | `fretex` | Fretex |
341
+ | `greenish` | Jade / green |
342
+
343
+ Each theme maps the semantic token scale — `alpha`, `beta`, `gama`, `delta`, `error` — onto concrete
344
+ palette values, and the Tailwind colour names reference those tokens. **Always use the semantic
345
+ names** (`text-delta-700`, `bg-alpha-500`); a raw palette colour like `text-blue-500` will not follow
346
+ the active theme.
347
+
348
+ ---
349
+
350
+ ## Bundle size and the MUI removal
351
+
352
+ MUI is an all-or-nothing dependency. Its runtime engine (`@mui/system`, `@mui/styled-engine`,
353
+ `@mui/utils`, `@emotion/*`, `stylis`) is shared by every MUI component, so keeping even one — the
354
+ Autocomplete, say — ships the entire ~143 KB gzipped infrastructure. There is no partial removal. The
355
+ same held for `@base-ui/react`, which was carrying ~143 KB gzipped to serve four simple components.
356
+
357
+ So both went, along with four smaller dependencies that were cheaper to inline than to keep:
358
+
359
+ | Removed | Was (gzipped) | Replaced by |
360
+ | --- | --- | --- |
361
+ | `@mui/material` | ~143 KB | Native HTML + ARIA components styled with Tailwind |
362
+ | `@base-ui/react` | ~143 KB | Native checkbox, switch, radio, accordion, dialog, tabs, slider, chip |
363
+ | `@emotion/react` + `@emotion/styled` | ~16 KB | Tailwind and CSS modules; flat `sx` via `resolveSx` |
364
+ | `lodash-es` | ~15 KB | Small native helpers |
365
+ | `tailwind-merge` | ~3 KB | `clsx`-only `cn()` plus `consumerOverrides` |
366
+ | `@react-input/mask` | ~3 KB | Dependency-free `useInputMask` |
367
+ | `material-ui-popup-state` | ~1 KB | In-repo `usePopupState` hook |
368
+
369
+ **~324 KB gzipped of dependencies became ~57 KB.** The only meaningful addition was
370
+ `@floating-ui/react` (~3 KB gzipped), which now powers tooltips, popovers, menus, drawers, selects
371
+ and the combobox.
372
+
373
+ ### Measured output
374
+
375
+ From a clean `pnpm build` (600 preserved ESM modules):
376
+
377
+ | Artefact | Raw | Gzipped |
378
+ | --- | --- | --- |
379
+ | Library JS, whole package | 512 KB | **129 KB** |
380
+ | Library CSS | — | 22.5 KB |
381
+ | `index.js` barrel entry alone | 32 KB | 6.6 KB |
382
+
383
+ Down from roughly 1.2 MB before the rewrite.
384
+
385
+ **Your app downloads far less than 129 KB.** The build preserves ESM modules instead of emitting one
386
+ blob, so that figure is the whole-library footprint, not a per-consumer one. The barrel is pure
387
+ re-exports and module bodies load on demand. Icons are the clearest case: 41.8 KB gzipped in total,
388
+ but emitted as one module per icon, so importing one costs ~0.5 KB rather than the whole block.
389
+ Remaining subtrees, gzipped: non-icon components 45.1 KB, table 19.2 KB, date/time 11.9 KB.
390
+
391
+ Third-party runtime dependencies are deliberately **not** bundled into `dist` — they stay as bare
392
+ imports resolved by your bundler (or, inside ASMA, by a shared import map so several micro-frontends
393
+ load one copy). The numbers above are this library's own code only.
394
+
395
+ ---
396
+
397
+ ## Migrating from the MUI-era version
398
+
399
+ *Skip this section if you are adopting the library fresh.*
400
+
401
+ Published versions `^3.43.x` and earlier wrapped MUI. The rewrite deliberately preserved the public
402
+ prop surface, so **your code still compiles — it just renders wrong in specific places.** The
403
+ compiler catches almost none of it. Audit every input, select, chip and table call site.
404
+
405
+ 1. **Dead `sx` selectors.** Every `'& .Mui*'`, nested, pseudo or responsive `sx` key stops applying.
406
+ Watch the console for `resolveSx: unsupported nested/responsive sx key`.
407
+ 2. **Chip `startAdornment` must be a real array.** A single wrapped element (`<div>{chips}</div>`)
408
+ fails the internal `Array.isArray` check, so the field stays a fixed 40px row and chips overlap.
409
+ Pass `[...chips]` and drop `multiline`.
410
+ 3. **Fields pin to 235px without `fullWidth`**, and `StyledFormControl` defaults to `inline-flex`, so
411
+ a form that used to fill its container now collapses. Pass `fullWidth` on **both**.
412
+ 4. **Text moved to 16px** on fields, selects and table bodies. Intended — re-baseline your visual
413
+ snapshots rather than forcing `text-sm`.
414
+ 5. **Direct `@mui/material` imports must be swapped** for the `Styled*` equivalents. `Checkbox` →
415
+ `StyledCheckbox`, `FormControlLabel` → `StyledFormControlLabel`, `Radio` / `Switch` / `Tooltip`
416
+ likewise. Note the `(event, checked)` handler shape and required `dataTest`.
417
+ 6. **Some `slotProps` shapes narrowed** — these *do* fail `tsc`. `StyledMenu`'s `slotProps.root` is
418
+ gone; only `paper` remains.
419
+
420
+ > **Breaking change:** `@mui/material` is no longer a dependency or peer dependency. An app that
421
+ > imports MUI directly must now declare it itself.
422
+
423
+ **Verifying a bump:** `tsc --noEmit` is necessary but far from sufficient. Also confirm the browser
424
+ console is free of `resolveSx` warnings, and visually check every touched field in its empty,
425
+ one-chip, many-chip, disabled, error and read-only states.
426
+
427
+ ASMA engineers: the full audit procedure, detection greps and per-recipe worked fixes live in the
428
+ `ui-core-mui-free-migration` skill in the `asma-modules` monorepo
429
+ (`.github/skills/ui-core-mui-free-migration/SKILL.md`).
430
+
431
+ ---
432
+
433
+ ## Troubleshooting
434
+
435
+ | Symptom | Cause and fix |
436
+ | --- | --- |
437
+ | Console: `resolveSx: unsupported nested/responsive sx key … dropped` | Nested `sx` is not supported. Move it to `className`. |
438
+ | My `className` override does nothing | `cn` is plain clsx with no dedupe, so the component's hardcoded utility wins on source order. The default needs a `consumerOverrides` gate — raise it. |
439
+ | My inline `style` / `sx` is ignored | The library's CSS is `!important`. Override with a class instead. |
440
+ | Every field is ~235px and the form collapsed | Pass `fullWidth` on both `StyledInputField` **and** the wrapping `StyledFormControl`. |
441
+ | Chips overlap the input, field won't grow | `startAdornment` must be an **array**, not a single wrapped element. Remove `multiline`. |
442
+ | Popover or calendar shows a black UA border | `dist/style.css` isn't imported. It carries the required `[popover]` reset. |
443
+ | Nothing is styled at all | Same — the stylesheet import is missing, or lands after Tailwind. |
444
+ | Text is 16px where it used to be 14px | Intended Figma parity. Don't force `text-sm`. |
445
+ | Select/menu inside a `StyledDialog` can't be clicked | Fixed in later versions — modal `<dialog>` marks outside content inert, so the popover must portal into the dialog. Upgrade. |
446
+ | Stray dark tooltip bubble on hover | `title={cond && 'text'}` yielded `false`. Fixed in later versions; upgrade. |
447
+ | A library fix isn't showing after rebuilding | Apps consume the prebuilt `dist`, not `src`. See the note below. |
448
+
449
+ **The `dist` trap (monorepo work only).** If you are developing this library alongside a consuming
450
+ app, your change is invisible until you rebuild (`pnpm --filter asma-ui-core build`) and restart the
451
+ app's dev server with `vite --force`. Worse, with peer dependencies pnpm injects a *snapshot copy*
452
+ into its virtual store rather than symlinking, so rebuilding `dist` never reaches the app and
453
+ `pnpm install` reports "Already up to date". Re-sync explicitly:
454
+
455
+ ```bash
456
+ RESOLVED=$(readlink -f node_modules/asma-ui-core)
457
+ rsync -a --delete shared/asma-ui-core/dist/ "$RESOLVED/dist/"
458
+ ```
459
+
460
+ Check the `dist` mtime before debugging anything else.
461
+
462
+ ---
463
+
464
+ ## Contributing to the library
465
+
466
+ ```bash
467
+ pnpm install
468
+ pnpm storybook # component workbench on :6006
469
+ pnpm build # ts:check + vite build into dist/
470
+ pnpm ts:check # types only
471
+ pnpm lint # eslint (lint:fix to autofix)
472
+ pnpm test-storybook # vitest against the stories
473
+ pnpm vrt # visual regression vs committed baselines (needs Docker)
474
+ pnpm vrt:accept # re-baseline after an intended visual change
475
+ pnpm vrt:report # open the last Playwright report
476
+ ```
477
+
478
+ Node 24 is required (see `engines`).
479
+
480
+ ### Component conventions
481
+
482
+ - Compose from existing styled components; check whether one already exists before styling from
483
+ scratch.
484
+ - Question whether a new "composed component" is needed at all — often documenting how to combine
485
+ the existing parts is enough.
486
+ - Every component is documented and tested in Storybook **before** the PR opens.
487
+ - New or restyled components get the `@figmaNode` / `@figmaProp` JSDoc contract described in
488
+ [Figma ↔ React matching](#figma--react-matching) — including `@figmaProp none` when a prop has no
489
+ Design System counterpart.
490
+ - Accessibility ships with the component: role, keyboard behaviour and focus management, not a
491
+ follow-up ticket.
492
+
493
+ ### The export surface is frozen
494
+
495
+ `src/api-surface.test.ts` snapshots every runtime export of the barrel. If that snapshot changes you
496
+ have made a breaking API change, and it must be a deliberate reviewed decision rather than an
497
+ incidental side effect of a refactor. `src/index.ts` is maintained by hand — add new exports there
498
+ explicitly.
499
+
500
+ ### Visual regression
501
+
502
+ Storybook stories are captured and compared inside a pinned Playwright Docker image, so local and CI
503
+ runs share one code path. A diff fails the build until a human confirms it is an intended design
504
+ change, at which point `pnpm vrt:accept` rewrites the baselines. Per-component Figma parity decisions
505
+ are tracked in [`visual-tests/FIGMA-PARITY.md`](visual-tests/FIGMA-PARITY.md).
506
+
507
+ ### Changelog and releases
508
+
509
+ The project uses [changesets](https://github.com/changesets/changesets). For each feature or fix:
510
+
511
+ ```bash
512
+ npx changeset
513
+ ```
514
+
515
+ Choose `patch` for a fix, `minor` for a feature, `major` for a breaking change, and commit the
516
+ generated markdown file with your PR. A PR may carry several changesets. On merge to master the
517
+ pipeline bumps the version, writes the changelog and publishes to npm.