refactor(libs): graduate Icon / LayoutStateService / brand tokens to libs/shared/* (#99)
## Summary
Per ADR-0020 §"Shared-libs graduation": the three primitives that `portal-shell` (today) and `portal-admin` (next) will both share move out of `apps/portal-shell/src/` and into the workspace libs. Mechanical move — no behaviour change, prepares the ground for the admin-app PR.
## Graduated
| Primitive | Old location | New location |
|---|---|---|
| `Icon` component (+ `IconName` type) | `apps/portal-shell/src/app/components/icon/` | [`libs/shared/ui/src/lib/icon/`](libs/shared/ui/src/lib/icon/) |
| `LayoutStateService` (+ `ThemeMode` type) | `apps/portal-shell/src/app/state/` | [`libs/shared/state/src/lib/`](libs/shared/state/src/lib/) |
| Brand-palette `@theme` block | `apps/portal-shell/src/styles.css` | [`libs/shared/tokens/src/brand-tokens.css`](libs/shared/tokens/src/brand-tokens.css) |
## Notable changes
- **Icon selector renamed `app-icon` → `lib-icon`.** Required by the lib's ESLint `@angular-eslint/component-selector` rule (prefix `lib` for shared libs). All ~20 usages in `portal-shell` templates updated in one sweep.
- **New `shared-state` library** generated via `nx g @nx/angular:library libs/shared/state`. Mirrors the existing `feature-auth` / `shared-ui` shape (vite + Angular + spec setup). `tsconfig.base.json` path alias `shared-state` added by the generator.
- **Lib tags rebalanced** to satisfy the module-boundary lint rule:
- `shared-state`: new lib → `scope:shared, type:shared`.
- `shared-ui`: was `scope:portal-shell` (scaffold default) → `scope:shared, type:shared`.
- **`shared-tokens` is now CSS-only.** The placeholder TS function is gone; `index.ts` is an empty `export {}` with a TODO note. Vitest config flips `passWithNoTests: true` so the test target stops failing on the empty lib.
- **`portal-shell`'s `styles.css`** drops the inline `@theme {}` block and instead `@import`s `../../../libs/shared/tokens/src/brand-tokens.css`. Both apps will read the same source.
- Placeholder scaffolded files in `shared-ui/src/lib/shared-ui/` removed.
## Verification
- `nx run-many -t lint test build` across `portal-shell, shared-ui, shared-state, shared-tokens` — green.
- Tests redistributed: **portal-shell 28**, **shared-state 9**, **shared-ui 3**, **shared-tokens 0** = 40 total (same as before the move).
- Production build: **128.7 KB gzip** initial per locale (was 128.5 KB on `main` — noise-level delta).
- Brand-color CSS variables (`--color-brand-primary-500: #12546c`, …) still inlined in the produced `styles-*.css`.
- `dist/.../browser/{en,fr}/` emitted as expected.
## What this PR explicitly does NOT do
- Move other components (Header, Sidebar, Footer, ThemeSwitcher, LocaleSwitcher). Those are app-specific shell concerns; if `portal-admin` ends up needing them, they graduate in their own PR.
- Set up `portal-admin`. That's the next PR on the admin track — and now imports from `shared-ui` / `shared-state` / `shared-tokens` will Just Work.
- Refactor `feature-auth`'s tagging. It still says `scope:portal-shell`; revisit when the auth implementation actually lands and the dual-audience design (per ADR-0008) makes the right scope obvious.
## Test plan
- [x] Per-project run-many — green.
- [x] Production build emits both locales with brand tokens applied.
- [ ] Manual: `pnpm exec nx serve portal-shell` — the dev experience is unchanged.
- [ ] Manual: serve-static + locale switcher / theme switcher / sidebar collapse / navigation — all the behaviours the moved primitives drive still work in production.
---------
Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #99
This commit was merged in pull request #99.
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Brand palette — APF charte graphique.
|
||||
*
|
||||
* primary: #12546c (dark teal) — header bar, sidebar active items,
|
||||
* primary buttons, headings on light bg
|
||||
* accent: #f7a919 (warm orange) — CTAs, highlights, focus rings on
|
||||
* branded surfaces
|
||||
*
|
||||
* The two scales below are hand-tuned (each step roughly halves the
|
||||
* delta from the previous toward white / black). Tailwind v4 surfaces
|
||||
* them automatically as utility classes — `bg-brand-primary-700`,
|
||||
* `text-brand-accent-500`, `ring-brand-primary-500`, etc.
|
||||
*
|
||||
* Use the `500` step as the canonical brand value; the lighter / darker
|
||||
* steps are for hover / focus / muted text without dropping out of the
|
||||
* brand family.
|
||||
*
|
||||
* Owned by `libs/shared/tokens` so every app in the workspace
|
||||
* (`portal-shell`, `portal-admin`) reads from the same source. To
|
||||
* consume, `@import` this file from the app's entry stylesheet.
|
||||
*/
|
||||
@theme {
|
||||
--color-brand-primary-50: #eaf3f7;
|
||||
--color-brand-primary-100: #cae0e8;
|
||||
--color-brand-primary-200: #9ac4d2;
|
||||
--color-brand-primary-300: #5fa2b6;
|
||||
--color-brand-primary-400: #2f7f96;
|
||||
--color-brand-primary-500: #12546c;
|
||||
--color-brand-primary-600: #0f475c;
|
||||
--color-brand-primary-700: #0c394a;
|
||||
--color-brand-primary-800: #082a36;
|
||||
--color-brand-primary-900: #051c24;
|
||||
--color-brand-primary-950: #020e12;
|
||||
|
||||
--color-brand-accent-50: #fef5e0;
|
||||
--color-brand-accent-100: #fce6b3;
|
||||
--color-brand-accent-200: #fad17a;
|
||||
--color-brand-accent-300: #f8bb47;
|
||||
--color-brand-accent-400: #f7a919;
|
||||
--color-brand-accent-500: #de9415;
|
||||
--color-brand-accent-600: #b97a11;
|
||||
--color-brand-accent-700: #93600d;
|
||||
--color-brand-accent-800: #6e4709;
|
||||
--color-brand-accent-900: #4a3006;
|
||||
--color-brand-accent-950: #271903;
|
||||
}
|
||||
@@ -1 +1,7 @@
|
||||
export * from './lib/shared-tokens';
|
||||
// `shared-tokens` is currently CSS-only — see `./brand-tokens.css`,
|
||||
// which apps import from their entry stylesheet (e.g.
|
||||
// `apps/portal-shell/src/styles.css`). The TS module exists so the
|
||||
// Nx lib boilerplate / package layout stays uniform with the other
|
||||
// shared libs; expand when a typed token API (e.g. CSS-variable
|
||||
// name constants for runtime lookups) is genuinely needed.
|
||||
export {};
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { sharedTokens } from './shared-tokens';
|
||||
|
||||
describe('sharedTokens', () => {
|
||||
it('should work', () => {
|
||||
expect(sharedTokens()).toEqual('shared-tokens');
|
||||
});
|
||||
});
|
||||
@@ -1,3 +0,0 @@
|
||||
export function sharedTokens(): string {
|
||||
return 'shared-tokens';
|
||||
}
|
||||
@@ -12,6 +12,9 @@ export default defineConfig(() => ({
|
||||
globals: true,
|
||||
environment: 'node',
|
||||
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||
// CSS-only library — no TS to test yet. Pass instead of failing
|
||||
// the gate; revisit when a typed token API lands.
|
||||
passWithNoTests: true,
|
||||
reporters: ['default'],
|
||||
coverage: {
|
||||
reportsDirectory: '../../../coverage/libs/shared/tokens',
|
||||
|
||||
Reference in New Issue
Block a user