@ng-solid/mcp 22.0.0
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/GUIDE.md +292 -0
- package/README.md +159 -0
- package/data/design-system.json +24204 -0
- package/data/icons.json +1 -0
- package/data/skills/ns-solid-components/SKILL.md +140 -0
- package/data/skills/ns-solid-forms/SKILL.md +183 -0
- package/data/skills/ns-solid-icons/SKILL.md +95 -0
- package/data/skills/ns-solid-install/SKILL.md +120 -0
- package/data/skills/ns-solid-style-contract/SKILL.md +84 -0
- package/data/skills/ns-solid-theming/SKILL.md +123 -0
- package/index.d.ts +3 -0
- package/index.js +38 -0
- package/lib/data.d.ts +26 -0
- package/lib/data.js +51 -0
- package/lib/install-skills.d.ts +1 -0
- package/lib/install-skills.js +159 -0
- package/lib/preset-validator.d.ts +44 -0
- package/lib/preset-validator.js +172 -0
- package/lib/prompts.d.ts +2 -0
- package/lib/prompts.js +173 -0
- package/lib/resources.d.ts +2 -0
- package/lib/resources.js +328 -0
- package/lib/server.d.ts +3 -0
- package/lib/server.js +31 -0
- package/lib/style-contract.d.ts +37 -0
- package/lib/style-contract.js +167 -0
- package/lib/template-parser.d.ts +29 -0
- package/lib/template-parser.js +230 -0
- package/lib/tools.components.d.ts +44 -0
- package/lib/tools.components.js +836 -0
- package/lib/tools.d.ts +9 -0
- package/lib/tools.guides.d.ts +15 -0
- package/lib/tools.guides.js +103 -0
- package/lib/tools.icons.d.ts +7 -0
- package/lib/tools.icons.js +106 -0
- package/lib/tools.js +18 -0
- package/lib/tools.tokens.d.ts +7 -0
- package/lib/tools.tokens.js +278 -0
- package/lib/types.d.ts +349 -0
- package/lib/types.js +11 -0
- package/package.json +52 -0
- package/skills/ns-solid-components/SKILL.md +77 -0
- package/skills/ns-solid-forms/SKILL.md +166 -0
- package/skills/ns-solid-icons/SKILL.md +95 -0
- package/skills/ns-solid-install/SKILL.md +119 -0
- package/skills/ns-solid-style-contract/SKILL.md +84 -0
- package/skills/ns-solid-theming/SKILL.md +123 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ns-solid-icons
|
|
3
|
+
description: Render Lucide icons with @ng-solid/icons' <ns-icon> component — registering symbols with provideIcons/provideIconsPatch and resolving camelCase symbol names.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Iconos con @ng-solid/icons
|
|
7
|
+
|
|
8
|
+
## El componente `<ns-icon>`
|
|
9
|
+
|
|
10
|
+
```html
|
|
11
|
+
<ns-icon name="alarm-clock" [size]="20" />
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`NsIconsComponent` (selector `ns-icon`) acepta:
|
|
15
|
+
|
|
16
|
+
- `name: string` — el nombre **kebab-case** del icono Lucide (`alarm-clock`,
|
|
17
|
+
no `alarmClock`).
|
|
18
|
+
- `size: number` (default `24`) — acepta el atributo plano (`numberAttribute`),
|
|
19
|
+
así que `<ns-icon name="x" size="16">` también funciona.
|
|
20
|
+
- `color: string` (default `'currentColor'`).
|
|
21
|
+
- `strokeWidth: number` (default `2`).
|
|
22
|
+
- `viewBox: string` (default `'0 0 24 24'`).
|
|
23
|
+
|
|
24
|
+
Internamente resuelve el SVG contra un `Map` poblado por
|
|
25
|
+
`provideIcons`/`provideIconsPatch`; si el nombre no fue registrado, emite un
|
|
26
|
+
`console.warn` y no renderiza el path.
|
|
27
|
+
|
|
28
|
+
## Registrar iconos — dos providers, dos momentos distintos
|
|
29
|
+
|
|
30
|
+
**`provideIcons(icons)`** — en el bootstrap de la app (`appConfig.providers`
|
|
31
|
+
o `bootstrapApplication`). Usar para los iconos que se necesitan siempre:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { provideIcons, alarmClock, x } from '@ng-solid/icons';
|
|
35
|
+
|
|
36
|
+
export const appConfig: ApplicationConfig = {
|
|
37
|
+
providers: [
|
|
38
|
+
// ...
|
|
39
|
+
provideIcons([alarmClock, x])
|
|
40
|
+
]
|
|
41
|
+
};
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**`provideIconsPatch(icons)`** — a nivel de `Route.providers`, para iconos
|
|
45
|
+
que sólo hace falta cargar cuando se navega a esa ruta (lazy):
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
const routes: Routes = [
|
|
49
|
+
{
|
|
50
|
+
path: 'products',
|
|
51
|
+
providers: [provideIconsPatch([alarmClock])],
|
|
52
|
+
loadComponent: () =>
|
|
53
|
+
import('./products.component').then((m) => m.ProductsComponent)
|
|
54
|
+
}
|
|
55
|
+
];
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`provideIconsPatch` registra además `NsIconsPatchService`, que
|
|
59
|
+
`NsIconsComponent` detecta e invoca (`doPatch()`) antes de intentar resolver
|
|
60
|
+
el icono, para que el registro parcial se aplique aunque el componente ya
|
|
61
|
+
esté instanciado.
|
|
62
|
+
|
|
63
|
+
## Los símbolos se importan en camelCase
|
|
64
|
+
|
|
65
|
+
El catálogo generado (`packages/icons/src/lib/generated/index.ts`) exporta
|
|
66
|
+
un símbolo por icono con el nombre kebab-case convertido a camelCase:
|
|
67
|
+
|
|
68
|
+
| Icono (`name` en `<ns-icon>`) | Símbolo importado de `@ng-solid/icons` |
|
|
69
|
+
| --------------------------------- | -------------------------------------- |
|
|
70
|
+
| `alarm-clock` | `alarmClock` |
|
|
71
|
+
| `a-arrow-down` | `aArrowDown` |
|
|
72
|
+
| `align-horizontal-justify-center` | `alignHorizontalJustifyCenter` |
|
|
73
|
+
| `circle-check` | `circleCheck` |
|
|
74
|
+
|
|
75
|
+
Cada símbolo es un objeto `{ name, icon }` (ver `NsIconDefinition`) — nunca
|
|
76
|
+
pases un string al provider, pasa el símbolo importado.
|
|
77
|
+
|
|
78
|
+
## Catálogo
|
|
79
|
+
|
|
80
|
+
<!-- ns:icon-count -->
|
|
81
|
+
|
|
82
|
+
- Fuente: Lucide (`LUCIDE_SOURCE_URL` en `packages/icons/src/lib/generated/metadata.ts`).
|
|
83
|
+
- Licencia: **ISC** (`LUCIDE_SOURCE_LICENSE`).
|
|
84
|
+
- Usa la tool `search_icons` del MCP para encontrar el `name`/símbolo correcto
|
|
85
|
+
por palabra clave en vez de adivinar el nombre exacto de Lucide.
|
|
86
|
+
|
|
87
|
+
## Errores comunes
|
|
88
|
+
|
|
89
|
+
- Registrar el símbolo pero usar un `name` distinto en el template (deben
|
|
90
|
+
coincidir exactamente, kebab-case).
|
|
91
|
+
- Olvidar `provideIcons`/`provideIconsPatch` por completo — el icono no
|
|
92
|
+
truena, simplemente no renderiza y deja un warning en consola.
|
|
93
|
+
- Confundir `provideIcons` (bootstrap, global) con `provideIconsPatch`
|
|
94
|
+
(`Route.providers`, por ruta) — usar el segundo en el bootstrap no falla,
|
|
95
|
+
pero pierde el propósito de carga diferida.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ns-solid-install
|
|
3
|
+
description: Install and bootstrap @ng-solid/design-system (Arvisfy) plus @ng-solid/icons in an Angular 21 standalone application, including the Tailwind bridge and theme provider.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Instalación de @ng-solid/design-system
|
|
7
|
+
|
|
8
|
+
## Paquetes y peer dependencies
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pnpm add @ng-solid/design-system @ng-solid/icons
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`@ng-solid/design-system` declara estos peer dependencies (ver
|
|
15
|
+
`packages/design-system/package.json`):
|
|
16
|
+
|
|
17
|
+
- `@angular/core`, `@angular/common`, `@angular/forms`,
|
|
18
|
+
`@angular/platform-browser`, `@angular/router` en `^21.0.0`.
|
|
19
|
+
- `@ng-solid/icons` en `^22.0.5`.
|
|
20
|
+
- `tailwindcss` en `^4.0.0`, marcado **opcional** (`peerDependenciesMeta`) —
|
|
21
|
+
sólo hace falta si la app consume el puente de utilidades semánticas.
|
|
22
|
+
|
|
23
|
+
Versiones publicadas actualmente:
|
|
24
|
+
|
|
25
|
+
<!-- ns:versions -->
|
|
26
|
+
|
|
27
|
+
Si tu app usa Tailwind, añade también el plugin de PostCSS:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pnpm add -D tailwindcss @tailwindcss/postcss
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Entrypoint único — nada de subpaths
|
|
34
|
+
|
|
35
|
+
`@ng-solid/design-system` tiene **un solo entry point público**
|
|
36
|
+
(`ng-package.json` → `entryFile: src/index.ts`, sin `secondaryEntrypoints`).
|
|
37
|
+
Todo se importa desde la raíz del paquete:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { NsButtonComponent, NsInputComponent } from '@ng-solid/design-system';
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Nunca inventes rutas como `@ng-solid/design-system/button` — no existen. La
|
|
44
|
+
única excepción es el asset CSS publicado explícitamente como
|
|
45
|
+
`@ng-solid/design-system/themes/tailwind.css` (ver más abajo), que no es un
|
|
46
|
+
entry point de JS/TS.
|
|
47
|
+
|
|
48
|
+
## Puente de Tailwind
|
|
49
|
+
|
|
50
|
+
Si la app importa el puente semántico, hazlo una sola vez en el stylesheet
|
|
51
|
+
global, después de las capas de Tailwind:
|
|
52
|
+
|
|
53
|
+
```css
|
|
54
|
+
@import '@ng-solid/design-system/themes/tailwind.css';
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Ese fichero define el bridge `@theme inline` que mapea los tokens de
|
|
58
|
+
Tailwind a `--ns-semantic-*`, además de registrar localmente la fuente
|
|
59
|
+
variable Inter (sin peticiones de red en runtime).
|
|
60
|
+
|
|
61
|
+
## Registrar el tema en `appConfig`
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { Arvisfy, provideNsTheme } from '@ng-solid/design-system';
|
|
65
|
+
import { ApplicationConfig } from '@angular/core';
|
|
66
|
+
|
|
67
|
+
export const appConfig: ApplicationConfig = {
|
|
68
|
+
providers: [provideNsTheme({ preset: Arvisfy, colorScheme: 'system' })]
|
|
69
|
+
};
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`colorScheme: 'system'` sigue `prefers-color-scheme`. Ver la skill
|
|
73
|
+
`ns-solid-theming` para `defineNsPreset`, `NsThemeService.setScheme` y las
|
|
74
|
+
reglas de validación del preset.
|
|
75
|
+
|
|
76
|
+
## Standalone: `imports` es obligatorio
|
|
77
|
+
|
|
78
|
+
Todos los componentes son **standalone**. Angular no los resuelve
|
|
79
|
+
automáticamente: hay que declararlos explícitamente en el `imports` del
|
|
80
|
+
componente (o del `NgModule`) que los usa. Omitir esto es el error más común
|
|
81
|
+
al generar código con un agente:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { Component } from '@angular/core';
|
|
85
|
+
import { NsButtonComponent } from '@ng-solid/design-system';
|
|
86
|
+
|
|
87
|
+
@Component({
|
|
88
|
+
selector: 'app-toolbar',
|
|
89
|
+
standalone: true,
|
|
90
|
+
imports: [NsButtonComponent], // obligatorio — <ns-button> no funciona sin esto
|
|
91
|
+
template: `<ns-button variant="primary">Guardar</ns-button>`
|
|
92
|
+
})
|
|
93
|
+
export class ToolbarComponent {}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Esto vale también para directivas de composición (`NsFieldDirective`,
|
|
97
|
+
`NsTooltipDirective`, `NsDropdownItemDirective`, etc.) y para componentes
|
|
98
|
+
usados vía `contentChildren`/proyección (`ns-card-header`, `ns-tab`,
|
|
99
|
+
`ns-accordion-item`…): todos deben aparecer en `imports`.
|
|
100
|
+
|
|
101
|
+
## Iconos
|
|
102
|
+
|
|
103
|
+
`@ng-solid/icons` se instala aparte y se registra con `provideIcons(...)` en
|
|
104
|
+
el bootstrap. Ver la skill `ns-solid-icons` para el detalle completo
|
|
105
|
+
(símbolos camelCase, `provideIconsPatch` para rutas con lazy loading).
|
|
106
|
+
|
|
107
|
+
## Extender el preset
|
|
108
|
+
|
|
109
|
+
No copies `Arvisfy`; extiéndelo con `defineNsPreset` (ver `ns-solid-theming`
|
|
110
|
+
para las reglas de validación completas):
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { Arvisfy, defineNsPreset } from '@ng-solid/design-system';
|
|
114
|
+
|
|
115
|
+
export const ProductTheme = defineNsPreset(Arvisfy, {
|
|
116
|
+
name: 'product',
|
|
117
|
+
primitive: { color: { brand: { 500: '#0052ff' } } }
|
|
118
|
+
});
|
|
119
|
+
```
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ns-solid-style-contract
|
|
3
|
+
description: Follow ng-solid's enforced style architecture — ns-* BEM class naming, zero Tailwind utilities in markup, and the @reference-only SCSS contract checked by pnpm quality:styles.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Contrato de estilos de ng-solid
|
|
7
|
+
|
|
8
|
+
Este contrato lo enforcea en CI
|
|
9
|
+
`tools/scripts/check-design-system-styles.mjs` (`pnpm quality:styles`) sobre
|
|
10
|
+
`packages/design-system/src/lib` y `apps/demo/src`. Un PR que lo viole falla
|
|
11
|
+
el build, tanto en la librería como en aplicaciones que sigan la misma
|
|
12
|
+
convención.
|
|
13
|
+
|
|
14
|
+
## 1. Clases de template: sólo `ns-*` BEM
|
|
15
|
+
|
|
16
|
+
Toda clase en `class="…"`, `[class.x]` o claves de objeto `[ngClass]` debe
|
|
17
|
+
matchear:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
ns-block__element--modifier
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Regex real usada por el gate:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
/^ns-[a-z0-9]+(?:-[a-z0-9]+)*(?:(?:__[a-z0-9]+(?:-[a-z0-9]+)*)?(?:--[a-z0-9]+(?:-[a-z0-9]+)*)?)?$/
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Válido: `ns-button`, `ns-button__icon`, `ns-button--danger`,
|
|
30
|
+
`ns-card__header--sticky`. Inválido: `btn-primary`, `NsButton`,
|
|
31
|
+
`ns_button`, `flex`.
|
|
32
|
+
|
|
33
|
+
## 2. Cero utilidades Tailwind en markup
|
|
34
|
+
|
|
35
|
+
Ninguna clase de template puede empezar con un prefijo de utilidad Tailwind
|
|
36
|
+
(`flex`, `grid`, `gap-`, `p-`/`m-` + variantes, `text-`, `bg-`, `border`,
|
|
37
|
+
`rounded`, `shadow`, `absolute`/`relative`/`fixed`/`sticky`, `inset`/`top-`/
|
|
38
|
+
`bottom-`/`start-`/`end-`, `z-`, `w-`/`h-`, `min-`/`max-`, `overflow`,
|
|
39
|
+
`opacity`, `font-`, `items-`/`justify-`/`content-`). Tailwind se compone
|
|
40
|
+
**sólo** desde SCSS con `@apply`, nunca directamente en el HTML.
|
|
41
|
+
|
|
42
|
+
## 3. Nada de `styles:` inline
|
|
43
|
+
|
|
44
|
+
Un componente no puede declarar estilos inline en el decorador
|
|
45
|
+
(`styles: [...]`). Usa `styleUrl` (o `styleUrls`) apuntando a un `.scss`
|
|
46
|
+
hermano.
|
|
47
|
+
|
|
48
|
+
## 4. SCSS de componente: `@reference`, sin `@use`/`@import`, sin valores crudos
|
|
49
|
+
|
|
50
|
+
Cada `.scss` de componente (fuera de `lib/theme/`) debe:
|
|
51
|
+
|
|
52
|
+
- Incluir literalmente `@reference` (al bridge de Tailwind:
|
|
53
|
+
`@reference '../theme/tailwind.css';` o la ruta relativa que corresponda).
|
|
54
|
+
- **No** usar `@use` ni `@import` — extender Tailwind sólo vía `@reference`.
|
|
55
|
+
- **No** contener valores crudos: hex (`#fff`), `rgb()`/`rgba()`, literales
|
|
56
|
+
`px`/`rem`/`em`/`ms`/`s`, o `var(--x, fallback)` con valor por defecto.
|
|
57
|
+
Toda decisión visual se lee de un token `--ns-component-*` ya resuelto por
|
|
58
|
+
el preset (ver la skill `ns-solid-theming`) — sin fallback embebido, porque
|
|
59
|
+
el preset garantiza que la variable siempre está definida.
|
|
60
|
+
|
|
61
|
+
Ejemplo correcto:
|
|
62
|
+
|
|
63
|
+
```scss
|
|
64
|
+
@reference '../theme/tailwind.css';
|
|
65
|
+
|
|
66
|
+
.ns-button {
|
|
67
|
+
@apply inline-flex items-center;
|
|
68
|
+
background: var(--ns-component-button-background);
|
|
69
|
+
color: var(--ns-component-button-foreground);
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 5. Prohibido reintroducir Bootstrap / `@popperjs`
|
|
74
|
+
|
|
75
|
+
El gate falla si `package.json` o `pnpm-lock.yaml` vuelven a traer
|
|
76
|
+
`bootstrap` o `@popperjs/*` — el design-system fue migrado fuera de esas
|
|
77
|
+
dependencias deliberadamente.
|
|
78
|
+
|
|
79
|
+
## Validar un snippet antes de proponerlo
|
|
80
|
+
|
|
81
|
+
Usa la tool del MCP `validate_style_contract` (o `validate_component_usage`
|
|
82
|
+
para un uso completo de componente) contra cualquier markup/SCSS que generes
|
|
83
|
+
antes de entregarlo — es la misma lógica que corre `pnpm quality:styles`,
|
|
84
|
+
expuesta como función pura.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ns-solid-theming
|
|
3
|
+
description: Work with the Arvisfy three-tier token architecture in @ng-solid/design-system — extending the preset, validating tokens and switching color scheme at runtime.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Theming con Arvisfy
|
|
7
|
+
|
|
8
|
+
## Los tres niveles de tokens
|
|
9
|
+
|
|
10
|
+
`packages/design-system/src/lib/theme/presets/arvisfy.preset.ts` define el
|
|
11
|
+
preset en tres tiers, cada uno sólo puede referenciar al tier anterior:
|
|
12
|
+
|
|
13
|
+
1. **`primitive`** — únicos valores crudos permitidos (hex, `rem`, pesos de
|
|
14
|
+
fuente, escalas numéricas). Ejemplo real: `primitive.color.klein.500 =
|
|
15
|
+
'#3665F3'`.
|
|
16
|
+
2. **`semantic`** — referencia primitivos con la sintaxis `{primitive.…}`, y
|
|
17
|
+
se divide en `common` (independiente de esquema) y
|
|
18
|
+
`colorScheme.light` / `colorScheme.dark` (un valor distinto por esquema).
|
|
19
|
+
3. **`components`** — referencia semánticos con `{semantic.…}`. Es el único
|
|
20
|
+
tier que las hojas de estilo de componente pueden consumir, y sólo a
|
|
21
|
+
través de la variable CSS resultante.
|
|
22
|
+
|
|
23
|
+
Conteo actual del preset Arvisfy:
|
|
24
|
+
|
|
25
|
+
<!-- ns:token-count -->
|
|
26
|
+
|
|
27
|
+
## Regla de oro del SCSS de componente
|
|
28
|
+
|
|
29
|
+
El SCSS de un componente **sólo** puede leer variables `--ns-component-*`.
|
|
30
|
+
Nunca `--ns-primitive-*` ni `--ns-semantic-*` directamente, y nunca un valor
|
|
31
|
+
crudo (hex, `rgb()`, `px`/`rem`/`ms` literal) — eso lo enforcea
|
|
32
|
+
`pnpm quality:styles` (ver la skill `ns-solid-style-contract`).
|
|
33
|
+
|
|
34
|
+
La conversión de un path de token a variable CSS es determinista
|
|
35
|
+
(`tokenPathToCssVariable` en `theme.utils.ts`): camelCase se separa en
|
|
36
|
+
kebab-case y los puntos se vuelven guiones, con el tier como prefijo:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
tier: 'component', path: 'button.background'
|
|
40
|
+
-> --ns-component-button-background
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Extender el preset — nunca copiarlo
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { Arvisfy, defineNsPreset } from '@ng-solid/design-system';
|
|
47
|
+
|
|
48
|
+
export const ProductTheme = defineNsPreset(Arvisfy, {
|
|
49
|
+
name: 'product',
|
|
50
|
+
primitive: {
|
|
51
|
+
color: { brand: { 500: '#0052ff' } }
|
|
52
|
+
},
|
|
53
|
+
semantic: {
|
|
54
|
+
colorScheme: {
|
|
55
|
+
light: { color: { primary: '{primitive.color.brand.500}' } },
|
|
56
|
+
dark: { color: { primary: '{primitive.color.brand.500}' } }
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
components: {
|
|
60
|
+
button: { borderColor: '{semantic.color.primary}' }
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`defineNsPreset(base, overrides)` hace un merge profundo (`overrides` gana
|
|
66
|
+
por clave) y llama a `validateNsPreset` sobre el resultado antes de
|
|
67
|
+
devolverlo. `NsThemeService` también valida el preset activo en su
|
|
68
|
+
constructor y en cada `setPreset`.
|
|
69
|
+
|
|
70
|
+
## Reglas que aplica `validateNsPreset`
|
|
71
|
+
|
|
72
|
+
Extraídas de `packages/design-system/src/lib/theme/utils/theme.utils.ts`:
|
|
73
|
+
|
|
74
|
+
- **Nombre del preset**: debe matchear `/^[a-z][a-z0-9-]*$/i` (falla si no).
|
|
75
|
+
- **Claves de token**: cada segmento del path debe matchear
|
|
76
|
+
`/^(?:[a-z][a-zA-Z0-9]*|[0-9]+)$/` — sin guiones ni mayúsculas iniciales.
|
|
77
|
+
- **Valores primitivos seguros**: ningún string primitivo puede contener
|
|
78
|
+
`;`, `{` o `}`, ni estar vacío.
|
|
79
|
+
- **Referencias `semantic.*`**: todo valor de `semantic.common` /
|
|
80
|
+
`semantic.colorScheme.{light,dark}` debe tener la forma exacta
|
|
81
|
+
`{primitive.<path>}` y ese `<path>` debe existir en `primitive`.
|
|
82
|
+
- **Referencias `components.*`**: todo valor debe tener la forma
|
|
83
|
+
`{semantic.<path>}`, y ese `<path>` debe existir en `semantic.common`, o
|
|
84
|
+
en **ambos** `colorScheme.light` y `colorScheme.dark` a la vez — un token
|
|
85
|
+
de componente que sólo resuelve en un esquema es un preset inválido.
|
|
86
|
+
|
|
87
|
+
Cualquier violación lanza una excepción síncrona (`throw new Error(...)`),
|
|
88
|
+
no un warning: un preset inválido rompe el arranque de la app.
|
|
89
|
+
|
|
90
|
+
## Cambiar de esquema en runtime
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { inject } from '@angular/core';
|
|
94
|
+
import { NsThemeService } from '@ng-solid/design-system';
|
|
95
|
+
|
|
96
|
+
const theme = inject(NsThemeService);
|
|
97
|
+
theme.setScheme('dark'); // 'light' | 'dark' | 'system'
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
- `'system'` delega en `prefers-color-scheme` y reacciona a cambios en vivo
|
|
101
|
+
vía `matchMedia('(prefers-color-scheme: dark)')`.
|
|
102
|
+
- Una elección explícita (`'light'` / `'dark'`) se persiste en
|
|
103
|
+
`localStorage` bajo la clave `ns-color-scheme` (configurable con
|
|
104
|
+
`provideNsTheme({ storageKey: '...' })`, o desactivable con
|
|
105
|
+
`storageKey: false`).
|
|
106
|
+
- `NsThemeService` expone `preset`, `colorScheme`, `resolvedColorScheme`
|
|
107
|
+
(siempre `'light' | 'dark'`, nunca `'system'`) y `scheme` como signals de
|
|
108
|
+
sólo lectura.
|
|
109
|
+
- Al aplicar el tema, el servicio escribe todas las custom properties en
|
|
110
|
+
`documentElement.style` y además `dataset.nsPreset` / `dataset.nsColorScheme`
|
|
111
|
+
y `style.colorScheme`, para que el navegador ajuste scrollbars/controles
|
|
112
|
+
nativos.
|
|
113
|
+
|
|
114
|
+
## Configurar el provider
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
provideNsTheme({
|
|
118
|
+
preset: Arvisfy,
|
|
119
|
+
colorScheme: 'system', // default
|
|
120
|
+
storageKey: 'ns-color-scheme', // default; false para desactivar persistencia
|
|
121
|
+
toast: { placement: 'top-end' } // default de NsToastService
|
|
122
|
+
});
|
|
123
|
+
```
|